Skip to content

Commit

Permalink
Completed Dropdown component
Browse files Browse the repository at this point in the history
  • Loading branch information
iancharlesdouglas committed Jun 17, 2023
1 parent 5edf4e0 commit 422bd07
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 39 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|-|-|-|-|
|Initial components|-|-|-|

## [0.0.2](https://github.com/iancharlesdouglas/carbon-icons-qwik/releases/tag/0.0.2) - 2023-06-17

|Added|Fixed|Changed|Removed|
|-|-|-|-|
|Dropdown compoenent|-|-|-|

2 changes: 1 addition & 1 deletion COMPONENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
|-|-|-|
|Button|0.0.1-1||
|Checkbox|0.0.1-1||
|Dropdown|0.0.2||
|FluidForm|0.0.1-1||
|Form|0.0.1-1||
|Grid|0.0.1-1|Incl. Row, Column. Grid is CSS grid|
Expand Down Expand Up @@ -35,7 +36,6 @@
- DefinitionTooltip
- Dialog
- Disclosure
- Dropdown
- ErrorBoundary
- ExpandableSearch
- FileUploader
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "carbon-components-qwik",
"version": "0.0.1-2.2",
"version": "0.0.2",
"description": "Carbon Design System components implemented as Qwik components",
"license": "Apache-2.0",
"main": "./lib/index.qwik.mjs",
Expand Down
89 changes: 58 additions & 31 deletions src/components/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ import {
useStore,
useTask$,
QwikMouseEvent,
useVisibleTask$,
} from '@builder.io/qwik';
import { usePrefix } from '../../internal/hooks/use-prefix';
import { formContext } from '../../internal/contexts/form-context';
import classNames from 'classnames';
import _ from 'lodash';
import { ListBox } from '../list-box/list-box';
import { ListBoxMenu } from '../list-box/list-box-menu';
import { ListBoxDimensions, ListBoxMenu } from '../list-box/list-box-menu';
import { Checkmark, WarningAltFilled, WarningFilled } from 'carbon-icons-qwik';
import { ListBoxMenuIcon } from '../list-box/list-box-menu-icon';
import { ListBoxMenuItem } from '../list-box/list-box-menu-item';
import { uniqueId } from '../../internal/unique/unique-id';
import { KeyCodes } from '../../internal/key-codes';

/**
* Item with a label property
*/
type Labelled = {
label: string;
};

/**
* List item type
*/
export type Item = string | { label: string };
export type Item = string | Labelled;

/**
* Function that takes an item and returns a string representation of it
Expand All @@ -46,6 +52,15 @@ export const defaultItemToString: ItemToString = (item: Item) => {
return item?.label;
};

const itemsEqual = (item1: Item, item2: Item) => {
if (typeof item1 === 'string' && typeof item2 === 'string') {
return item1 === item2;
}
const labelledItem1 = item1 as Labelled;
const labelledItem2 = item2 as Labelled;
return labelledItem1.label === labelledItem2.label;
};

const ariaNormalize = (
isOpen: boolean,
disabled: boolean,
Expand All @@ -64,7 +79,7 @@ const ariaNormalize = (
let selectedId: string | undefined;
let selectedOption: Item | undefined;
if (items && selectedItem) {
selectedIndex = items.findIndex((item) => item === selectedItem);
selectedIndex = items.findIndex((item) => itemsEqual(item, selectedItem));
selectedOption = selectedItem;
} else if (items && initialSelectedItem) {
const initialItems = Array.isArray(initialSelectedItem) ? initialSelectedItem : [initialSelectedItem];
Expand Down Expand Up @@ -168,6 +183,7 @@ export const Dropdown = component$((props: DropdownProps) => {
const selectedOption = useSignal(modifiedSelectedItem);
const highlightedOption = useSignal<Item>();
const listBoxElement = useSignal<Element>();
const listBoxDimensions = useStore<ListBoxDimensions>({ height: 0, itemHeight: 0, visibleRows: 0 });
const comboboxElement = useSignal<Element>();
const {
ariaLabel,
Expand All @@ -189,24 +205,6 @@ export const Dropdown = component$((props: DropdownProps) => {
warnText,
} = props;

useVisibleTask$(() => {
console.log('vis task - listbox ref', listBoxElement.value);

// const listBoxDiv = document.getElementById(`${prefix}--list-box__menu`);
// console.log('listBoxDiv height', listBoxDiv?.clientHeight);
// if (listBoxElement.value) {
// console.log('dropdown vis task - listbox height', listBoxElement.value.clientHeight);
// }
});

useTask$(
({ track }) => {
track(listBoxElement);
console.log('tracked listboxel. ref.', listBoxElement.value);
},
{ eagerness: 'load' }
);

type Keys = {
typed: string[];
reset: boolean;
Expand All @@ -233,11 +231,6 @@ export const Dropdown = component$((props: DropdownProps) => {
}
});

useTask$(({ track }) => {
track(props);
console.log('props task');
});

const inline = type === 'inline';
const showWarning = !invalid && warn;

Expand Down Expand Up @@ -347,6 +340,7 @@ export const Dropdown = component$((props: DropdownProps) => {
state.isOpen = false;
} else if (event.keyCode !== KeyCodes.Tab) {
state.isOpen = true;
event.stopPropagation();
}
break;
}
Expand All @@ -370,6 +364,28 @@ export const Dropdown = component$((props: DropdownProps) => {
}
break;
}
case KeyCodes.PageDown: {
if (listBoxDimensions.visibleRows) {
if (highlightedOption.value && items) {
const currentIndex = items.indexOf(highlightedOption.value);
if (currentIndex > -1 && currentIndex + listBoxDimensions.visibleRows <= items.length) {
highlightedOption.value = items[currentIndex + listBoxDimensions.visibleRows];
}
}
}
break;
}
case KeyCodes.PageUp: {
if (listBoxDimensions.visibleRows) {
if (highlightedOption.value && items) {
const currentIndex = items.indexOf(highlightedOption.value);
if (currentIndex > -1 && currentIndex - listBoxDimensions.visibleRows >= 0) {
highlightedOption.value = items[currentIndex - listBoxDimensions.visibleRows];
}
}
}
break;
}
case KeyCodes.Escape: {
state.isOpen = false;
break;
Expand Down Expand Up @@ -428,22 +444,33 @@ export const Dropdown = component$((props: DropdownProps) => {
state.isOpen = false;
}
})}
preventdefault:keydown
>
<span class={`${prefix}--list-box__label`}>
{(selectedOption.value && (RenderSelectedItem ? <RenderSelectedItem item={selectedOption.value} /> : itemToString(selectedOption.value))) || label}
</span>
<ListBoxMenuIcon isOpen={state.isOpen} />
</button>
<ListBoxMenu {...listBoxAttrs} ref={listBoxElement} items={items} highlightedItem={highlightedOption.value} onKeyDown$={handleKeyDown}>
<ListBoxMenu
{...listBoxAttrs}
ref={listBoxElement}
items={items}
highlightedItem={highlightedOption.value}
onKeyDown$={handleKeyDown}
onMeasure$={$((dimensions: ListBoxDimensions) => {
listBoxDimensions.visibleRows = dimensions.visibleRows;
})}
preventdefault:keydown
>
{state.isOpen &&
items?.map((item: Item, index: number) => {
const title = itemToString(item);
const itemSelected = selectedOption.value === item;
const itemSelected = selectedOption.value ? itemsEqual(selectedOption.value, item) : undefined;
return (
<ListBoxMenuItem
key={itemAttrs?.[index].id}
isActive={selectedOption.value === item}
isHighlighted={highlightedOption.value === item}
isActive={!!itemSelected}
isHighlighted={highlightedOption.value ? itemsEqual(highlightedOption.value, item) : false}
title={title}
{...itemAttrs?.[index]}
onClick$={$(() => {
Expand Down
22 changes: 18 additions & 4 deletions src/components/list-box/list-box-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { QwikIntrinsicElements, Slot, component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
import { PropFunction, QwikIntrinsicElements, Slot, component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
import { usePrefix } from '../../internal/hooks/use-prefix';
import { Item } from '../dropdown/dropdown';

/**
* Measured dimensions
*/
export type ListBoxDimensions = {
height: number;
itemHeight: number;
visibleRows: number;
};

/**
* ListBoxMenu props
* @property {string} id - ID
Expand All @@ -12,29 +21,34 @@ export type ListBoxMenuProps = QwikIntrinsicElements['div'] & {
id?: string;
items?: Item[];
highlightedItem?: Item;
onMeasure$?: PropFunction<(dimensions: ListBoxDimensions) => void>;
};

/**
* ListBoxMenu
*/
export const ListBoxMenu = component$((props: ListBoxMenuProps) => {
const prefix = usePrefix();
const { id, items, highlightedItem } = props;
const { id, items, highlightedItem, onMeasure$ } = props;
const listBoxElement = useSignal<HTMLDivElement>();
useVisibleTask$(({ track }) => {
track(props);
if (items && highlightedItem && listBoxElement.value) {
const children = Array.from(listBoxElement.value.children);
const itemHeight = children[0]?.clientHeight;
const highlightedIndex = items.indexOf(highlightedItem);
if (highlightedIndex > -1) {
const children = Array.from(listBoxElement.value.children);
const itemHeight = children[0]?.clientHeight;
const itemTop = highlightedIndex * itemHeight;
if (itemTop < listBoxElement.value.scrollTop) {
listBoxElement.value.scrollTo(0, itemTop);
} else if (itemTop + itemHeight > listBoxElement.value.clientHeight + listBoxElement.value.scrollTop) {
listBoxElement.value.scrollTo(0, itemTop - itemHeight);
}
}
if (listBoxElement.value.clientHeight && itemHeight) {
onMeasure$ &&
onMeasure$({ height: listBoxElement.value.clientHeight, itemHeight, visibleRows: Math.floor(listBoxElement.value.clientHeight / itemHeight) });
}
}
listBoxElement.value?.focus();
});
Expand Down
2 changes: 2 additions & 0 deletions src/internal/key-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export enum KeyCodes {
Escape = 27,
Home = 36,
LeftArrow = 37,
PageDown = 34,
PageUp = 33,
RightArrow = 39,
Space = 32,
Tab = 9,
Expand Down
4 changes: 2 additions & 2 deletions src/test/test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const Test = component$(() => {
'Jackfruit',
].map((label) => ({ label, key: label }));
// const ItemComponent = component$(({ item }: ItemProps) => <span class="item-class">{defaultItemToString(item)}</span>);
const selectedItem = useSignal<Item>({ label: '' });
const selectedItem = useSignal<Item>(items.find((item) => (item as { label: string }).label === 'Banana')!);

return (
<CarbonRoot>
Expand Down Expand Up @@ -67,7 +67,7 @@ const Test = component$(() => {
<Dropdown
titleText="Select"
label="Select an item"
selectedItem={items.find((item) => (item as { label: string }).label === 'Banana')}
selectedItem={selectedItem.value}
renderSelectedItem={SelectedItemRenderComp}
items={items}
helperText="Optional"
Expand Down

0 comments on commit 422bd07

Please sign in to comment.