Skip to content

Commit

Permalink
noCloseOnChange sur Dropdown/Autocomplete et `keepSelectedValuesInS…
Browse files Browse the repository at this point in the history
…elect` pour `SelectChips`
  • Loading branch information
JabX committed Oct 9, 2024
1 parent cf66e8c commit 1037b3e
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 17 deletions.
20 changes: 20 additions & 0 deletions packages/forms/src/components/__style__/select-chips.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
:root {
--select-chips-line-selected-color: rgb(var(--color-accent));
}

.select {
display: flex;
flex-direction: column;
}

.line {
display: flex;
align-items: center;
justify-content: space-between;
transition: color var(--animation-duration) var(--animation-curve-default);

& > span {
margin-right: calc(-2 * var(--text-field-icon-size) - 2 * var(--text-field-icon-padding));
font-size: var(--text-field-icon-size);
}
}

.line--selected {
color: var(--select-chips-line-selected-color);
}

.chips {
padding-top: var(--button-spacing);
display: inline-flex;
Expand Down
54 changes: 41 additions & 13 deletions packages/forms/src/components/select-chips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useCallback, useMemo} from "react";

import {DomainFieldTypeMultiple, DomainType, ReferenceList, SingleDomainFieldType} from "@focus4/stores";
import {CSSProp, useTheme} from "@focus4/styling";
import {Chip, ChipCss, DropdownCss, Icon, TextFieldCss} from "@focus4/toolbox";
import {Chip, ChipCss, DropdownCss, FontIcon, Icon, TextFieldCss} from "@focus4/toolbox";

import {toSimpleType} from "../utils";

Expand All @@ -25,6 +25,8 @@ export interface SelectChipsProps<T extends DomainFieldTypeMultiple> {
error?: string;
/** Permet la sélection de tous les éléments à la fois. */
hasSelectAll?: boolean;
/** Laisse les valeurs sélectionnées dans le Select au lieu de les retirer. */
keepSelectedValuesInSelect?: boolean;
/** Préfixe i18n. Par défaut : "focus". */
i18nPrefix?: string;
/** Icône à poser devant le texte. */
Expand Down Expand Up @@ -72,6 +74,7 @@ export function SelectChips<const T extends DomainFieldTypeMultiple>({
disabled = false,
error,
hasSelectAll = false,
keepSelectedValuesInSelect = false,
i18nPrefix = "focus",
icon,
id,
Expand All @@ -89,22 +92,26 @@ export function SelectChips<const T extends DomainFieldTypeMultiple>({
}: SelectChipsProps<T>) {
const theme = useTheme<DropdownCss & SelectChipsCss & TextFieldCss>("selectChips", selectChipsCss, pTheme);

const handleAddValue = useCallback(
function handleAddValue(v?: boolean | number | string) {
if (v && (!maxSelectable || value.length < maxSelectable)) {
onChange([...value, v] as DomainType<T>);
}
},
[onChange, maxSelectable, value]
);

const handleRemoveValue = useCallback(
function handleRemoveValue(v: boolean | number | string) {
function handleRemoveValue(v: DomainType<SingleDomainFieldType<T>>) {
onChange(value.filter(i => i !== v) as DomainType<T>);
},
[onChange, value]
);

const handleAddValue = useCallback(
function handleAddValue(v?: DomainType<SingleDomainFieldType<T>>) {
const hasValue = v !== undefined && (value as DomainType<SingleDomainFieldType<T>>[]).includes(v);

if (v !== undefined && !hasValue && (!maxSelectable || value.length < maxSelectable)) {
onChange([...value, v] as DomainType<T>);
} else if (hasValue) {
handleRemoveValue(v);
}
},
[handleRemoveValue, maxSelectable]
);

const handleAddAll = useCallback(
function handleRemoveAll() {
onChange(values.map(i => i[values.$valueKey]) as DomainType<T>);
Expand Down Expand Up @@ -142,6 +149,22 @@ export function SelectChips<const T extends DomainFieldTypeMultiple>({
}
}, [handleAddAll, handleRemoveAll, hasSelectAll, i18nPrefix]);

const LineComponent = useMemo(() => {
if (!keepSelectedValuesInSelect) {
return undefined;
}

return function SelectChipsLineComponent({item}: any) {
const selected = (value as any).includes(item[values.$valueKey]);
return (
<span className={theme.line({selected})}>
{item[values.$labelKey]}
{selected ? <FontIcon iconI18nKey={`${i18nPrefix}.icons.select.selected`} /> : null}
</span>
);
};
}, [i18nPrefix, keepSelectedValuesInSelect, value, values]);

return useObserver(() => (
<div className={theme.select({error: !!error})}>
<Select
Expand All @@ -151,15 +174,20 @@ export function SelectChips<const T extends DomainFieldTypeMultiple>({
error={error}
icon={icon}
id={id}
LineComponent={LineComponent}
name={name}
noCloseOnChange={keepSelectedValuesInSelect}
onChange={handleAddValue}
showSupportingText="never"
sizing={sizing}
theme={theme}
trailing={trailing}
type={toSimpleType(type)}
values={values.filter(
v => !(value as (boolean | number | string)[]).includes(v[values.$valueKey]) && !unselectable?.(v)
v =>
(keepSelectedValuesInSelect ||
!(value as (boolean | number | string)[]).includes(v[values.$valueKey])) &&
!unselectable?.(v)
)}
/>
{value.length > 0 ? (
Expand All @@ -173,7 +201,7 @@ export function SelectChips<const T extends DomainFieldTypeMultiple>({
label={values.getLabel(item)}
onDeleteClick={
!undeletable?.(item as DomainType<SingleDomainFieldType<T>>)
? () => handleRemoveValue(item)
? () => handleRemoveValue(item as DomainType<SingleDomainFieldType<T>>)
: undefined
}
theme={chipTheme}
Expand Down
3 changes: 3 additions & 0 deletions packages/forms/src/translation/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export const icons = {
},
unselectAll: {
name: "clear"
},
selected: {
name: "check"
}
}
};
4 changes: 4 additions & 0 deletions packages/toolbox/src/components/autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export interface AutocompleteProps<TSource = {key: string; label: string}>
* Par défaut : 50.
*/
maxDisplayed?: number;
/** Ne ferme pas le menu de l'Autocomplete lors de la sélection d'un item. */
noCloseOnChange?: boolean;
/**
* Appelé avec la clé correspondante lors de la sélection d'une valeur.
*
Expand Down Expand Up @@ -108,6 +110,7 @@ export const Autocomplete = forwardRef(function Autocomplete<TSource = {key: str
maxDisplayed = 50,
multiline,
name,
noCloseOnChange,
onBlur,
onChange,
onContextMenu,
Expand Down Expand Up @@ -349,6 +352,7 @@ export const Autocomplete = forwardRef(function Autocomplete<TSource = {key: str
{...menu}
keepSelectionOnPointerLeave
noBlurOnArrowPress
noCloseOnClick={noCloseOnChange}
noRing
onItemClick={key => onValueChange(key)}
onSelectedChange={setSelected}
Expand Down
4 changes: 4 additions & 0 deletions packages/toolbox/src/components/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export interface DropdownProps<TSource = {key: string; label: string}>
hasUndefined?: boolean;
/** Composant personnalisé pour afficher les valeurs. */
LineComponent?: (props: {item: TSource}) => ReactElement;
/** Ne ferme pas le menu de la Dropdown lors de la sélection d'un item. */
noCloseOnChange?: boolean;
/** Appelé avec la clé correspondante lors de la sélection d'une valeur. */
onChange?: (value?: string) => void;
/**
Expand Down Expand Up @@ -92,6 +94,7 @@ export const Dropdown = forwardRef(function Dropdown<TSource = {key: string; lab
loading = false,
multiline,
name,
noCloseOnChange,
onBlur,
onChange,
onContextMenu,
Expand Down Expand Up @@ -312,6 +315,7 @@ export const Dropdown = forwardRef(function Dropdown<TSource = {key: string; lab
keepSelectionOnPointerLeave
keepSelectionOnToggle
noBlurOnArrowPress
noCloseOnClick={noCloseOnChange}
onItemClick={handleChange}
onSelectedChange={onSelectedChange}
position={
Expand Down
11 changes: 8 additions & 3 deletions packages/toolbox/src/components/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export interface MenuProps extends MenuControls {
id?: string;
/** Si renseigné, la navigation clavier dans le Menu n'appelera pas le `blur` de l'élément courant actif (pour un input par exemple). */
noBlurOnArrowPress?: boolean;
/** Ne ferme pas le menu au clic sur un item. */
noCloseOnClick?: boolean;
/** Si renseigné, utilise des <div> à la place d'un <ul> et des <li> pour les éléments du Menu. */
noList?: boolean;
/** N'affiche pas le focus ring lors de la navigation clavier dans le Menu. */
Expand Down Expand Up @@ -199,6 +201,7 @@ export function Menu({
keepSelectionOnToggle = false,
id,
noBlurOnArrowPress = false,
noCloseOnClick = false,
noList = false,
noRing = false,
noSelection = false,
Expand Down Expand Up @@ -370,10 +373,12 @@ export function Menu({
onItemClick?.(item.key, e instanceof KeyboardEvent ? "keyboard" : "click");
}

setShowRing(false);
close?.();
if (!noCloseOnClick) {
setShowRing(false);
close?.();
}
},
[noSelection, onItemClick, close]
[noCloseOnClick, noSelection, onItemClick, close]
);

const resetPointerEvents = useCallback(function resetPointerEvents() {
Expand Down
2 changes: 1 addition & 1 deletion packages/tooling/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export default {
format: "cjs",
dir: "lib"
},
external: [...Object.keys(pkg.dependencies || {}), "dns", "fs", "path"]
external: [...Object.keys(pkg.dependencies || {}), "crypto", "dns", "fs", "path"]
};

0 comments on commit 1037b3e

Please sign in to comment.