Skip to content

Commit

Permalink
fix: #191
Browse files Browse the repository at this point in the history
  • Loading branch information
fabien-ml committed Apr 25, 2023
1 parent abe09f1 commit 89d0117
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 20 deletions.
37 changes: 24 additions & 13 deletions packages/core/dev/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { createSignal } from "solid-js";
import { I18nProvider, Combobox } from "../src";
import { I18nProvider, Select } from "../src";

export default function App() {
const [options, setOptions] = createSignal([
"Apple",
"Banana",
"Blueberry",
"Grapes",
"Pineapple",
]);
const [value, setValue] = createSignal("Pineapple");

return (
<I18nProvider locale="en-US">
<p>Your favorite fruit is: {value()}.</p>
<button onClick={() => setOptions(list => list.slice(0, -1))}>remove item</button>
<Select.Root
multiple
options={["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]}
placeholder="Select a fruit…"
itemComponent={props => (
<Select.Item item={props.item} class="select__item">
<Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
<Select.ItemIndicator class="select__item-indicator">X</Select.ItemIndicator>
</Select.Item>
)}
>
<Select.Trigger class="select__trigger" aria-label="Fruit">
<Select.Value class="select__value">
{state => state.selectedOptions().join(", ")}
</Select.Value>
<Select.Icon class="select__icon">V</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content class="select__content">
<Select.Listbox class="select__listbox" />
</Select.Content>
</Select.Portal>
</Select.Root>
</I18nProvider>
);
}
125 changes: 125 additions & 0 deletions packages/core/dev/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
.select__trigger {
display: inline-flex;
align-items: center;
justify-content: space-between;
width: 200px;
border-radius: 6px;
padding: 0 10px 0 16px;
font-size: 16px;
line-height: 1;
height: 40px;
outline: none;
background-color: white;
border: 1px solid hsl(240 6% 90%);
color: hsl(240 4% 16%);
transition: border-color 250ms, color 250ms;
}
.select__trigger:hover {
border-color: hsl(240 5% 65%);
}
.select__trigger:focus-visible {
outline: 2px solid hsl(200 98% 39%);
outline-offset: 2px;
}
.select__trigger[data-invalid] {
border-color: hsl(0 72% 51%);
color: hsl(0 72% 51%);
}
.select__value {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.select__value[data-placeholder-shown] {
color: hsl(240 4% 46%);
}
.select__icon {
height: 20px;
width: 20px;
flex: 0 0 20px;
}
.select__description {
margin-top: 8px;
color: hsl(240 5% 26%);
font-size: 12px;
user-select: none;
}
.select__error-message {
margin-top: 8px;
color: hsl(0 72% 51%);
font-size: 12px;
user-select: none;
}
.select__content {
background-color: white;
border-radius: 6px;
border: 1px solid hsl(240 6% 90%);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
transform-origin: var(--kb-select-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.select__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
.select__listbox {
overflow-y: auto;
max-height: 360px;
padding: 8px;
}
.select__item {
font-size: 16px;
line-height: 1;
color: hsl(240 4% 16%);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
padding: 0 8px;
position: relative;
user-select: none;
outline: none;
}
.select__item[data-disabled] {
color: hsl(240 5% 65%);
opacity: 0.5;
pointer-events: none;
}
.select__item[data-highlighted] {
outline: none;
background-color: hsl(200 98% 39%);
color: white;
}
.select__section {
padding: 8px 0 0 8px;
font-size: 14px;
line-height: 32px;
color: hsl(240 4% 46%);
}
.select__item-indicator {
height: 20px;
width: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
}
@keyframes contentShow {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-8px);
}
}
29 changes: 28 additions & 1 deletion packages/core/src/combobox/combobox-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ export function ComboboxBase<Option, OptGroup = never>(props: ComboboxBaseProps<
const [isInputFocused, setIsInputFocusedState] = createSignal(false);

const [lastDisplayedOptions, setLastDisplayedOptions] = createSignal(local.options);
//const [selectedOptions, setSelectedOptions] = createSignal<Array<Option>>([]);

const messageFormatter = createMessageFormatter(() => COMBOBOX_INTL_MESSAGES);

Expand Down Expand Up @@ -351,7 +352,9 @@ export function ComboboxBase<Option, OptGroup = never>(props: ComboboxBaseProps<
});

const getOptionsFromValues = (values: Set<string>): Option[] => {
return flattenOptions().filter(option => values.has(getOptionValue(option as Option)));
return [...values]
.map(value => flattenOptions().find(option => getOptionValue(option) === value))
.filter(option => option != null) as Option[];
};

const disclosureState = createDisclosureState({
Expand Down Expand Up @@ -556,6 +559,30 @@ export function ComboboxBase<Option, OptGroup = never>(props: ComboboxBaseProps<
}
});

// Create a copy of the selected options when selection changes
// Prevent displayed selection from disappearing when filtering the options.
/*
createEffect(
on(
() => listState.selectionManager().selectedKeys(),
selectedKeys => {
setSelectedOptions(prev => {
const optionsToKeep = prev.filter(option => selectedKeys.has(getOptionValue(option)));
const keysToKeep = optionsToKeep.map(option => getOptionValue(option));
const newKeysToAdd = [...selectedKeys].filter(key => !keysToKeep.includes(key));
return [
...optionsToKeep,
...getOptionsFromValues(new Set(newKeysToAdd)).filter(option => option != null),
];
});
}
)
);
*/

// VoiceOver has issues with announcing aria-activedescendant properly on change.
// We use a live region announcer to announce focus changes manually.
let lastAnnouncedFocusedKey = "";
Expand Down
20 changes: 14 additions & 6 deletions packages/core/src/select/select-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,9 @@ export function SelectBase<Option, OptGroup = never>(props: SelectBaseProps<Opti
});

const getOptionsFromValues = (values: Set<string>): Option[] => {
return flattenOptions().filter(option => values.has(getOptionValue(option as Option)));
return [...values]
.map(value => flattenOptions().find(option => getOptionValue(option) === value))
.filter(option => option != null) as Option[];
};

const disclosureState = createDisclosureState({
Expand Down Expand Up @@ -420,13 +422,19 @@ export function SelectBase<Option, OptGroup = never>(props: SelectBaseProps<Opti

// Delete selected keys that do not match any option in the listbox.
createEffect(
on([flattenOptionKeys], ([flattenOptionKeys]) => {
const currentSelectedKeys = [...listState.selectionManager().selectedKeys()];
on(
[flattenOptionKeys],
([flattenOptionKeys]) => {
const currentSelectedKeys = [...listState.selectionManager().selectedKeys()];

const keysToKeep = currentSelectedKeys.filter(key => flattenOptionKeys.includes(key));
const keysToKeep = currentSelectedKeys.filter(key => flattenOptionKeys.includes(key));

listState.selectionManager().setSelectedKeys(keysToKeep);
})
listState.selectionManager().setSelectedKeys(keysToKeep);
},
{
defer: true,
}
)
);

const dataset: Accessor<SelectDataSet> = createMemo(() => ({
Expand Down

0 comments on commit 89d0117

Please sign in to comment.