✨(react) add Select component
Finally our powerful Select component is available to make great forms!
This commit is contained in:
5
.changeset/good-coins-divide.md
Normal file
5
.changeset/good-coins-divide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@openfun/cunningham-react": minor
|
||||
---
|
||||
|
||||
add Select component
|
||||
5
.changeset/lazy-crews-press.md
Normal file
5
.changeset/lazy-crews-press.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@openfun/cunningham-react": patch
|
||||
---
|
||||
|
||||
add forwardRef to Button
|
||||
5
.changeset/moody-glasses-fly.md
Normal file
5
.changeset/moody-glasses-fly.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@openfun/cunningham-react": patch
|
||||
---
|
||||
|
||||
create a generic LabelledBox
|
||||
@@ -43,6 +43,7 @@
|
||||
"@openfun/cunningham-tokens": "*",
|
||||
"@tanstack/react-table": "8.8.4",
|
||||
"classnames": "2.3.2",
|
||||
"downshift": "7.6.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
@@ -51,9 +52,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.21.3",
|
||||
"@babel/preset-typescript": "7.21.4",
|
||||
"@babel/plugin-proposal-decorators": "7.21.0",
|
||||
"@babel/plugin-proposal-export-default-from": "7.18.10",
|
||||
"@babel/preset-typescript": "7.21.4",
|
||||
"@faker-js/faker": "7.6.0",
|
||||
"@openfun/cunningham-tokens": "*",
|
||||
"@openfun/typescript-configs": "*",
|
||||
|
||||
183
packages/react/src/components/Forms/Select/index.scss
Normal file
183
packages/react/src/components/Forms/Select/index.scss
Normal file
@@ -0,0 +1,183 @@
|
||||
.c__select {
|
||||
position: relative;
|
||||
|
||||
&__wrapper {
|
||||
border-radius: var(--c--components--forms-select--border-radius);
|
||||
border-width: var(--c--components--forms-select--border-width);
|
||||
border-color: var(--c--components--forms-select--border-color);
|
||||
border-style: var(--c--components--forms-select--border-style);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: border var(--c--theme--transitions--duration) var(--c--theme--transitions--ease-out);
|
||||
padding: 0 0.75rem;
|
||||
gap: 1rem;
|
||||
color: var(--c--components--forms-select--color);
|
||||
box-sizing: border-box;
|
||||
height: var(--c--components--forms-select--height);
|
||||
cursor: pointer;
|
||||
background-color: var(--c--components--forms-select--background-color);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-radius: var(--c--components--forms-select--border-radius--hover);
|
||||
border-color: var(--c--components--forms-select--border-color--hover);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-radius: var(--c--components--forms-select--border-radius--focus);
|
||||
border-color: var(--c--components--forms-select--border-color--focus);
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: none;
|
||||
min-width: 0;
|
||||
|
||||
&__value {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
font-size: var(--c--components--forms-select--font-size);
|
||||
|
||||
input {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: var(--c--components--forms-select--color);
|
||||
font-size: var(--c--components--forms-select--font-size);
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
position: relative;
|
||||
top: -14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
font-size: 1.25rem;
|
||||
transition: all var(--c--theme--transitions--duration) var(--c--theme--transitions--ease-out);
|
||||
&.opened {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__clear {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
}
|
||||
&__separator {
|
||||
background-color: var(--c--theme--colors--greyscale-400);
|
||||
height: 24px;
|
||||
width: 1px;
|
||||
}
|
||||
&__open {
|
||||
color: var(--c--theme--colors--greyscale-900);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
width: calc(100% - 4px);
|
||||
max-height: 10rem;
|
||||
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3);
|
||||
background-color: var(--c--components--forms-select--menu-background-color);
|
||||
transform: translate(2px, 0);
|
||||
display: none;
|
||||
z-index: 1;
|
||||
|
||||
&--opened {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
padding: 0.75rem;
|
||||
font-size: var(--c--components--forms-select--item-font-size);
|
||||
color: var(--c--components--forms-select--item-color);
|
||||
cursor: pointer;
|
||||
|
||||
&--highlight {
|
||||
background-color: var(--c--components--forms-select--item-background-color--hover);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: var(--c--components--forms-select--item-background-color--selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Modifiers */
|
||||
|
||||
&--disabled {
|
||||
|
||||
.c__select__wrapper {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
border-color: var(--c--theme--colors--greyscale-200);
|
||||
cursor: default;
|
||||
|
||||
label {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.c__input__inner {
|
||||
.c__input, label {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--c--theme--colors--greyscale-200);
|
||||
}
|
||||
}
|
||||
|
||||
&--error {
|
||||
|
||||
.c__select__wrapper {
|
||||
border-color: var(--c--theme--colors--danger-600);
|
||||
}
|
||||
|
||||
&:not(.c__select__wrapper--disabled) {
|
||||
.c__select__wrapper:hover {
|
||||
border-color: var(--c--theme--colors--danger-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--success {
|
||||
|
||||
.c__select__wrapper {
|
||||
border-color: var(--c--theme--colors--success-600);
|
||||
}
|
||||
|
||||
&:not(.c__select__wrapper--disabled) {
|
||||
.c__select__wrapper:hover {
|
||||
border-color: var(--c--theme--colors--success-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1032
packages/react/src/components/Forms/Select/index.spec.tsx
Normal file
1032
packages/react/src/components/Forms/Select/index.spec.tsx
Normal file
File diff suppressed because it is too large
Load Diff
134
packages/react/src/components/Forms/Select/index.stories.mdx
Normal file
134
packages/react/src/components/Forms/Select/index.stories.mdx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Canvas, Meta, Story, Source, ArgsTable } from '@storybook/addon-docs';
|
||||
import { Select } from "./index";
|
||||
|
||||
<Meta title="Components/Forms/Select/Doc" component={Select}/>
|
||||
|
||||
export const Template = (args) => <Input {...args} />;
|
||||
|
||||
# Select
|
||||
|
||||
Cunningham provides a versatile Select component that you can use in your forms. This component follows the [ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/)
|
||||
using [Downshift](https://www.downshift-js.com/), so that mean there is no `select` wrapped inside it.
|
||||
|
||||
> For now it is only available for mono selection, multiple selection will be available soon.
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-forms-select-mono--uncontrolled"/>
|
||||
</Canvas>
|
||||
|
||||
## Options
|
||||
|
||||
The available options must be given via the `options` props. It is an array of objects with the following shape:
|
||||
|
||||
<Source
|
||||
language='ts'
|
||||
dark
|
||||
format={false}
|
||||
code={`{
|
||||
label: string
|
||||
value?: string
|
||||
}`}
|
||||
/>
|
||||
|
||||
As you can see the `value` is optional, if not provided, the `label` will be used as the value.
|
||||
|
||||
## Searchable
|
||||
|
||||
You can enable the text live filtering simply by using the `searchable` props.
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--searchable-uncontrolled"/>
|
||||
</Canvas>
|
||||
|
||||
## States
|
||||
|
||||
You can use the following props to change the state of the Select component by using the `state` props.
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--success"/>
|
||||
</Canvas>
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--error"/>
|
||||
</Canvas>
|
||||
|
||||
## Disabled
|
||||
|
||||
As a regular select, you can disable it by using the `disabled` props.
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--disabled"/>
|
||||
</Canvas>
|
||||
|
||||
## Texts
|
||||
|
||||
As the component uses [Field](?path=/story/components-forms-field-doc--page), you can use the `text` props to provide a description of the checkbox.
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--with-text"/>
|
||||
</Canvas>
|
||||
|
||||
## Width
|
||||
|
||||
By default, the select has a default width, like all inputs. But you can force it to take the full width of its container by using the `fullWidth` props.
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--full-width"/>
|
||||
</Canvas>
|
||||
|
||||
## Controlled / Non Controlled
|
||||
|
||||
Like a native select, you can use the Select component in a controlled or non controlled way. You can see the example below
|
||||
using the component in a controlled way.
|
||||
|
||||
<Canvas withSource="open">
|
||||
<Story id="components-forms-select-mono--controlled"/>
|
||||
</Canvas>
|
||||
|
||||
## Props
|
||||
|
||||
The props of this component are as close as possible to the native select component. You can see the list of props below.
|
||||
|
||||
<ArgsTable of={Select} />
|
||||
|
||||
## Design tokens
|
||||
|
||||
Here are the custom design tokens defined by the select.
|
||||
|
||||
| Token | Description |
|
||||
|--------------- |----------------------------- |
|
||||
| background-color | Background color of the select |
|
||||
| border-color | Border color of the select |
|
||||
| border-color--hover | Border color of the select on mouse hover |
|
||||
| border-color--focus | Border color of the select when focus |
|
||||
| border-radius | Border radius of the select |
|
||||
| border-radius--hover | Border radius of the select on mouse hover |
|
||||
| border-radius--focus | Border radius of the select when focused |
|
||||
| color | Value color |
|
||||
| font-size | Value font size |
|
||||
| height | Height of the combo box |
|
||||
| item-background-color--hover | Background color of the item on mouse hover |
|
||||
| item-background-color--selected | Background color of the selected item |
|
||||
| item-color | Color of the item |
|
||||
| item-font-size | Font size of the item |
|
||||
| menu-background-color | Background color of the menu |
|
||||
|
||||
See also [Field](?path=/story/components-forms-field-doc--page)
|
||||
|
||||
## Form Example
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-forms-select-mono--form-example"/>
|
||||
</Canvas>
|
||||
|
||||
##
|
||||
|
||||
<img src="components/Forms/Select/resources/dd_1.svg"/>
|
||||
|
||||
##
|
||||
|
||||
<img src="components/Forms/Select/resources/dd_2.svg"/>
|
||||
|
||||
##
|
||||
|
||||
<img src="components/Forms/Select/resources/dd_3.svg"/>
|
||||
352
packages/react/src/components/Forms/Select/index.tsx
Normal file
352
packages/react/src/components/Forms/Select/index.tsx
Normal file
@@ -0,0 +1,352 @@
|
||||
import React, {
|
||||
HTMLAttributes,
|
||||
PropsWithChildren,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
useCombobox,
|
||||
useSelect,
|
||||
UseSelectReturnValue,
|
||||
UseSelectStateChange,
|
||||
} from "downshift";
|
||||
import classNames from "classnames";
|
||||
import { useCunningham } from ":/components/Provider";
|
||||
import { Field, FieldProps } from ":/components/Forms/Field";
|
||||
import { LabelledBox } from ":/components/Forms/LabelledBox";
|
||||
import { Button } from ":/components/Button";
|
||||
|
||||
interface Option {
|
||||
value?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
type Props = PropsWithChildren &
|
||||
FieldProps & {
|
||||
label: string;
|
||||
options: Option[];
|
||||
searchable?: boolean;
|
||||
name?: string;
|
||||
defaultValue?: string | number;
|
||||
value?: string | number;
|
||||
onChange?: (event: {
|
||||
target: { value: string | number | undefined };
|
||||
}) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function getOptionsFilter(inputValue?: string) {
|
||||
return (option: Option) => {
|
||||
return (
|
||||
!inputValue ||
|
||||
option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||
option.value?.toLowerCase().includes(inputValue.toLowerCase())
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const optionToString = (option: Option | null) => {
|
||||
return option ? option.label : "";
|
||||
};
|
||||
|
||||
const optionToValue = (option: Option) => {
|
||||
return option.value ?? option.label;
|
||||
};
|
||||
|
||||
interface SubProps extends Props {
|
||||
defaultSelectedItem?: Option;
|
||||
downshiftProps: {
|
||||
initialSelectedItem?: Option;
|
||||
onSelectedItemChange?: any;
|
||||
};
|
||||
}
|
||||
|
||||
interface SelectAuxProps extends SubProps {
|
||||
options: Option[];
|
||||
labelAsPlaceholder: boolean;
|
||||
downshiftReturn: {
|
||||
isOpen: boolean;
|
||||
wrapperProps?: HTMLAttributes<HTMLDivElement>;
|
||||
selectedItem?: Option | null;
|
||||
getLabelProps: any;
|
||||
toggleButtonProps: any;
|
||||
getMenuProps: any;
|
||||
getItemProps: any;
|
||||
highlightedIndex: number;
|
||||
selectItem: UseSelectReturnValue<Option>["selectItem"];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is used by searchable and non-searchable select components.
|
||||
* It contains the common logic between the two.
|
||||
*/
|
||||
const SelectAux = ({
|
||||
children,
|
||||
state = "default",
|
||||
text,
|
||||
rightText,
|
||||
fullWidth,
|
||||
options,
|
||||
name,
|
||||
label,
|
||||
labelAsPlaceholder,
|
||||
downshiftProps,
|
||||
downshiftReturn,
|
||||
value,
|
||||
disabled,
|
||||
}: SelectAuxProps) => {
|
||||
const { t } = useCunningham();
|
||||
const labelProps = downshiftReturn.getLabelProps();
|
||||
|
||||
// When component is controlled, this useEffect will update the local selected item.
|
||||
useEffect(() => {
|
||||
if (downshiftProps.initialSelectedItem !== undefined) {
|
||||
return;
|
||||
}
|
||||
const optionToSelect = options.find(
|
||||
(option) => optionToValue(option) === value
|
||||
);
|
||||
downshiftReturn.selectItem(optionToSelect ?? null);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Field
|
||||
state={state}
|
||||
text={text}
|
||||
rightText={rightText}
|
||||
fullWidth={fullWidth}
|
||||
>
|
||||
<div
|
||||
className={classNames("c__select", "c__select--" + state, {
|
||||
"c__select--disabled": disabled,
|
||||
})}
|
||||
>
|
||||
{/* We disabled linting for this specific line because we consider that the onClick props is only used for */}
|
||||
{/* mouse users, so this do not engender any issue for accessibility. */}
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
className={classNames("c__select__wrapper", {
|
||||
"c__select__wrapper--focus": downshiftReturn.isOpen && !disabled,
|
||||
})}
|
||||
{...downshiftReturn.wrapperProps}
|
||||
>
|
||||
{downshiftReturn.selectedItem && (
|
||||
<input
|
||||
type="hidden"
|
||||
name={name}
|
||||
value={optionToValue(downshiftReturn.selectedItem)}
|
||||
/>
|
||||
)}
|
||||
<LabelledBox
|
||||
label={label}
|
||||
labelAsPlaceholder={labelAsPlaceholder}
|
||||
htmlFor={labelProps.htmlFor}
|
||||
labelId={labelProps.id}
|
||||
>
|
||||
<div className="c__select__inner">
|
||||
<div className="c__select__inner__value">{children}</div>
|
||||
<div className="c__select__inner__actions">
|
||||
{!disabled && downshiftReturn.selectedItem && (
|
||||
<>
|
||||
<Button
|
||||
color="tertiary"
|
||||
size="small"
|
||||
aria-label={t(
|
||||
"components.forms.select.clear_button_aria_label"
|
||||
)}
|
||||
className="c__select__inner__actions__clear"
|
||||
onClick={(e) => {
|
||||
downshiftReturn.selectItem(null);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<span className="material-icons">close</span>}
|
||||
/>
|
||||
<div className="c__select__inner__actions__separator" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
color="tertiary"
|
||||
size="small"
|
||||
className="c__select__inner__actions__open"
|
||||
icon={
|
||||
<span
|
||||
className={classNames("material-icons", {
|
||||
opened: downshiftReturn.isOpen,
|
||||
})}
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
}
|
||||
disabled={disabled}
|
||||
{...downshiftReturn.toggleButtonProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</LabelledBox>
|
||||
</div>
|
||||
<div
|
||||
className={classNames("c__select__menu", {
|
||||
"c__select__menu--opened": downshiftReturn.isOpen,
|
||||
})}
|
||||
{...downshiftReturn.getMenuProps()}
|
||||
>
|
||||
<ul>
|
||||
{downshiftReturn.isOpen &&
|
||||
options.map((item, index) => (
|
||||
<li
|
||||
className={classNames("c__select__menu__item", {
|
||||
"c__select__menu__item--highlight":
|
||||
downshiftReturn.highlightedIndex === index,
|
||||
"c__select__menu__item--selected":
|
||||
downshiftReturn.selectedItem === item,
|
||||
})}
|
||||
key={`${item.value}${index}`}
|
||||
{...downshiftReturn.getItemProps({ item, index })}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectSimple = (props: SubProps) => {
|
||||
const downshiftReturn = useSelect({
|
||||
...props.downshiftProps,
|
||||
items: props.options,
|
||||
itemToString: optionToString,
|
||||
});
|
||||
const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(
|
||||
!downshiftReturn.selectedItem
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setLabelAsPlaceholder(!downshiftReturn.selectedItem);
|
||||
}, [downshiftReturn.selectedItem]);
|
||||
|
||||
return (
|
||||
<SelectAux
|
||||
{...props}
|
||||
downshiftReturn={{
|
||||
...downshiftReturn,
|
||||
wrapperProps: downshiftReturn.getToggleButtonProps({
|
||||
disabled: props.disabled,
|
||||
}),
|
||||
toggleButtonProps: {},
|
||||
}}
|
||||
labelAsPlaceholder={labelAsPlaceholder}
|
||||
>
|
||||
{downshiftReturn.selectedItem && (
|
||||
<span>{optionToString(downshiftReturn.selectedItem)}</span>
|
||||
)}
|
||||
</SelectAux>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectSearchable = (props: SubProps) => {
|
||||
const { t } = useCunningham();
|
||||
const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
|
||||
const [hasInputFocused, setHasInputFocused] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const downshiftReturn = useCombobox({
|
||||
...props.downshiftProps,
|
||||
items: optionsToDisplay,
|
||||
itemToString: optionToString,
|
||||
onInputValueChange: (e) => {
|
||||
setOptionsToDisplay(props.options.filter(getOptionsFilter(e.inputValue)));
|
||||
if (!e.inputValue) {
|
||||
downshiftReturn.selectItem(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(
|
||||
!downshiftReturn.selectedItem
|
||||
);
|
||||
useEffect(() => {
|
||||
if (hasInputFocused || downshiftReturn.inputValue) {
|
||||
setLabelAsPlaceholder(false);
|
||||
return;
|
||||
}
|
||||
setLabelAsPlaceholder(!downshiftReturn.selectedItem);
|
||||
}, [
|
||||
downshiftReturn.selectedItem,
|
||||
hasInputFocused,
|
||||
downshiftReturn.inputValue,
|
||||
]);
|
||||
|
||||
const inputProps = downshiftReturn.getInputProps({
|
||||
ref: inputRef,
|
||||
disabled: props.disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectAux
|
||||
{...props}
|
||||
downshiftReturn={{
|
||||
...downshiftReturn,
|
||||
wrapperProps: {
|
||||
onClick: () => {
|
||||
inputRef.current?.focus();
|
||||
},
|
||||
},
|
||||
toggleButtonProps: downshiftReturn.getToggleButtonProps({
|
||||
disabled: props.disabled,
|
||||
"aria-label": t("components.forms.select.toggle_button_aria_label"),
|
||||
}),
|
||||
}}
|
||||
labelAsPlaceholder={labelAsPlaceholder}
|
||||
options={optionsToDisplay}
|
||||
>
|
||||
<input
|
||||
className="w-full p-1.5"
|
||||
{...inputProps}
|
||||
onFocus={(e) => {
|
||||
inputProps.onFocus(e);
|
||||
setHasInputFocused(true);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
inputProps.onBlur(e);
|
||||
setHasInputFocused(false);
|
||||
}}
|
||||
/>
|
||||
</SelectAux>
|
||||
);
|
||||
};
|
||||
|
||||
export const Select = (props: Props) => {
|
||||
if (props.defaultValue && props.value) {
|
||||
throw new Error(
|
||||
"You cannot use both defaultValue and value props on Select component"
|
||||
);
|
||||
}
|
||||
|
||||
const defaultSelectedItem = props.defaultValue
|
||||
? props.options.find(
|
||||
(option) => optionToValue(option) === props.defaultValue
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const commonDownshiftProps: SubProps["downshiftProps"] = {
|
||||
initialSelectedItem: defaultSelectedItem,
|
||||
onSelectedItemChange: (e: UseSelectStateChange<Option>) => {
|
||||
props.onChange?.({
|
||||
target: {
|
||||
value: e.selectedItem ? optionToValue(e.selectedItem) : undefined,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return props.searchable ? (
|
||||
<SelectSearchable {...props} downshiftProps={commonDownshiftProps} />
|
||||
) : (
|
||||
<SelectSimple {...props} downshiftProps={commonDownshiftProps} />
|
||||
);
|
||||
};
|
||||
258
packages/react/src/components/Forms/Select/mono.stories.tsx
Normal file
258
packages/react/src/components/Forms/Select/mono.stories.tsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import React, { useState } from "react";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { Select } from ":/components/Forms/Select";
|
||||
import { Button } from ":/components/Button";
|
||||
import { CunninghamProvider } from ":/components/Provider";
|
||||
|
||||
export default {
|
||||
title: "Components/Forms/Select/Mono",
|
||||
component: Select,
|
||||
} as ComponentMeta<typeof Select>;
|
||||
|
||||
const Template: ComponentStory<typeof Select> = (args) => (
|
||||
<div style={{ paddingBottom: "200px" }}>
|
||||
<CunninghamProvider>
|
||||
<Select {...args} />
|
||||
</CunninghamProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
const CITIES = Array.from({ length: 10 }).map(() => faker.address.city());
|
||||
const OPTIONS = CITIES.map((city) => ({
|
||||
label: city,
|
||||
value: city.toLowerCase(),
|
||||
}));
|
||||
|
||||
export const Uncontrolled = Template.bind({});
|
||||
Uncontrolled.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
defaultValue: OPTIONS[4].value,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
defaultValue: OPTIONS[4].value,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
export const WithText = Template.bind({});
|
||||
WithText.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
defaultValue: OPTIONS[4].value,
|
||||
text: "This is a text, you can display anything you want here like warnings, information or errors.",
|
||||
};
|
||||
export const Controlled = () => {
|
||||
const [value, setValue] = useState(OPTIONS[8].value);
|
||||
return (
|
||||
<CunninghamProvider>
|
||||
<div>
|
||||
<div>
|
||||
Value: <span>{value}</span>
|
||||
</div>
|
||||
<Select
|
||||
label="Select a city"
|
||||
options={OPTIONS}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value as string)}
|
||||
/>
|
||||
<Button onClick={() => setValue("")}>Reset</Button>
|
||||
</div>
|
||||
</CunninghamProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const Overflow = Template.bind({});
|
||||
Overflow.args = {
|
||||
label: "Select a city",
|
||||
options: [
|
||||
{
|
||||
value: "1",
|
||||
label: "Very long long long long long long long city name",
|
||||
},
|
||||
],
|
||||
defaultValue: "1",
|
||||
};
|
||||
|
||||
export const SearchableEmpty = Template.bind({});
|
||||
SearchableEmpty.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
searchable: true,
|
||||
};
|
||||
|
||||
export const SearchableUncontrolled = Template.bind({});
|
||||
SearchableUncontrolled.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
defaultValue: OPTIONS[4].value,
|
||||
searchable: true,
|
||||
};
|
||||
|
||||
export const SearchableDisabled = Template.bind({});
|
||||
SearchableDisabled.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
defaultValue: OPTIONS[4].value,
|
||||
searchable: true,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
export const SearchableControlled = () => {
|
||||
const [value, setValue] = useState(OPTIONS[8].value);
|
||||
return (
|
||||
<CunninghamProvider>
|
||||
<div>
|
||||
<div>
|
||||
Value: <span>{value}</span>
|
||||
</div>
|
||||
<Select
|
||||
label="Select a city"
|
||||
options={OPTIONS}
|
||||
searchable={true}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value as string)}
|
||||
/>
|
||||
<Button onClick={() => setValue("")}>Reset</Button>
|
||||
</div>
|
||||
</CunninghamProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const FullWidth = Template.bind({});
|
||||
FullWidth.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
fullWidth: true,
|
||||
};
|
||||
|
||||
export const Success = Template.bind({});
|
||||
Success.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
state: "success",
|
||||
text: "Well done",
|
||||
};
|
||||
|
||||
export const Error = Template.bind({});
|
||||
Error.args = {
|
||||
label: "Select a city",
|
||||
options: OPTIONS,
|
||||
state: "error",
|
||||
text: "Something went wrong",
|
||||
};
|
||||
|
||||
export const FormExample = () => {
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target as any);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data.getAll("city"));
|
||||
};
|
||||
|
||||
return (
|
||||
<CunninghamProvider>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-s">
|
||||
<Select
|
||||
label="Your city"
|
||||
name="city"
|
||||
options={OPTIONS}
|
||||
defaultValue={OPTIONS[2].value}
|
||||
text="The city you were born in"
|
||||
state="success"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-s">
|
||||
<Select
|
||||
label="Your gender"
|
||||
name="gender"
|
||||
options={[
|
||||
{
|
||||
label: "Male",
|
||||
},
|
||||
{
|
||||
label: "Female",
|
||||
},
|
||||
{
|
||||
label: "Other",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-s">
|
||||
<Select
|
||||
label="Your department"
|
||||
name="department"
|
||||
searchable={true}
|
||||
options={[
|
||||
{
|
||||
label: "Legal",
|
||||
},
|
||||
{
|
||||
label: "Tech",
|
||||
},
|
||||
{
|
||||
label: "AI",
|
||||
},
|
||||
{
|
||||
label: "Accounting",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-s">
|
||||
<Select
|
||||
label="Your grade"
|
||||
text="Any error you want"
|
||||
name="grade"
|
||||
searchable={true}
|
||||
options={[
|
||||
{
|
||||
label: "Level 1",
|
||||
},
|
||||
{
|
||||
label: "Level 2",
|
||||
},
|
||||
{
|
||||
label: "Level 3",
|
||||
},
|
||||
{
|
||||
label: "Emperor",
|
||||
},
|
||||
]}
|
||||
state="error"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-s">
|
||||
<Select
|
||||
label="Your plan"
|
||||
text="This field is disabled"
|
||||
name="grade"
|
||||
disabled={true}
|
||||
options={[
|
||||
{
|
||||
label: "Bronze",
|
||||
},
|
||||
{
|
||||
label: "Silver",
|
||||
},
|
||||
{
|
||||
label: "Gold",
|
||||
},
|
||||
{
|
||||
label: "Platinum",
|
||||
},
|
||||
]}
|
||||
defaultValue="Platinum"
|
||||
/>
|
||||
</div>
|
||||
<Button>Submit</Button>
|
||||
</form>
|
||||
</CunninghamProvider>
|
||||
);
|
||||
};
|
||||
127
packages/react/src/components/Forms/Select/resources/dd_1.svg
Normal file
127
packages/react/src/components/Forms/Select/resources/dd_1.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 266 KiB |
128
packages/react/src/components/Forms/Select/resources/dd_2.svg
Normal file
128
packages/react/src/components/Forms/Select/resources/dd_2.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 364 KiB |
135
packages/react/src/components/Forms/Select/resources/dd_3.svg
Normal file
135
packages/react/src/components/Forms/Select/resources/dd_3.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 268 KiB |
21
packages/react/src/components/Forms/Select/tokens.ts
Normal file
21
packages/react/src/components/Forms/Select/tokens.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DefaultTokens } from "@openfun/cunningham-tokens";
|
||||
|
||||
export const tokens = (defaults: DefaultTokens) => ({
|
||||
"border-color": defaults.theme.colors["greyscale-300"],
|
||||
"border-color--focus": defaults.theme.colors["primary-600"],
|
||||
"border-color--hover": defaults.theme.colors["greyscale-500"],
|
||||
"border-radius": "8px",
|
||||
"border-radius--focus": "2px",
|
||||
"border-radius--hover": "2px",
|
||||
"border-style": "solid",
|
||||
"border-width": "2px",
|
||||
color: defaults.theme.colors["greyscale-800"],
|
||||
"font-size": defaults.theme.font.sizes.l,
|
||||
height: "3.5rem",
|
||||
"item-background-color--hover": defaults.theme.colors["greyscale-200"],
|
||||
"item-background-color--selected": defaults.theme.colors["primary-100"],
|
||||
"item-color": defaults.theme.colors["greyscale-800"],
|
||||
"item-font-size": defaults.theme.font.sizes.l,
|
||||
"background-color": "white",
|
||||
"menu-background-color": "white",
|
||||
});
|
||||
@@ -96,6 +96,23 @@
|
||||
--c--theme--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1);
|
||||
--c--theme--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
--c--theme--transitions--duration: 250ms;
|
||||
--c--components--forms-select--border-color: #E7E8EA;
|
||||
--c--components--forms-select--border-color--focus: #0556BF;
|
||||
--c--components--forms-select--border-color--hover: #9EA3AA;
|
||||
--c--components--forms-select--border-radius: 8px;
|
||||
--c--components--forms-select--border-radius--focus: 2px;
|
||||
--c--components--forms-select--border-radius--hover: 2px;
|
||||
--c--components--forms-select--border-style: solid;
|
||||
--c--components--forms-select--border-width: 2px;
|
||||
--c--components--forms-select--color: #303C4B;
|
||||
--c--components--forms-select--font-size: 1rem;
|
||||
--c--components--forms-select--height: 3.5rem;
|
||||
--c--components--forms-select--item-background-color--hover: #F3F4F4;
|
||||
--c--components--forms-select--item-background-color--selected: #EBF2FC;
|
||||
--c--components--forms-select--item-color: #303C4B;
|
||||
--c--components--forms-select--item-font-size: 1rem;
|
||||
--c--components--forms-select--background-color: white;
|
||||
--c--components--forms-select--menu-background-color: white;
|
||||
--c--components--forms-radio--border-color: #E7E8EA;
|
||||
--c--components--forms-radio--accent-color: #419A14;
|
||||
--c--components--forms-input--font-weight: 400;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const tokens = {"theme":{"colors":{"primary-text":"#FFFFFF","primary-100":"#EBF2FC","primary-200":"#8CB5EA","primary-300":"#5894E1","primary-400":"#377FDB","primary-500":"#055FD2","primary-600":"#0556BF","primary-700":"#044395","primary-800":"#033474","primary-900":"#022858","secondary-text":"#555F6B","secondary-100":"#F2F7FC","secondary-200":"#EBF3FA","secondary-300":"#E2EEF8","secondary-400":"#DDEAF7","secondary-500":"#D4E5F5","secondary-600":"#C1D0DF","secondary-700":"#97A3AE","secondary-800":"#757E87","secondary-900":"#596067","greyscale-000":"#FFFFFF","greyscale-100":"#FAFAFB","greyscale-200":"#F3F4F4","greyscale-300":"#E7E8EA","greyscale-400":"#C2C6CA","greyscale-500":"#9EA3AA","greyscale-600":"#79818A","greyscale-700":"#555F6B","greyscale-800":"#303C4B","greyscale-900":"#0C1A2B","success-text":"#FFFFFF","success-100":"#EFFCD3","success-200":"#DBFAA9","success-300":"#BEF27C","success-400":"#A0E659","success-500":"#76D628","success-600":"#5AB81D","success-700":"#419A14","success-800":"#2C7C0C","success-900":"#1D6607","info-text":"#FFFFFF","info-100":"#EBF2FC","info-200":"#8CB5EA","info-300":"#5894E1","info-400":"#377FDB","info-500":"#055FD2","info-600":"#0556BF","info-700":"#044395","info-800":"#033474","info-900":"#022858","warning-text":"#FFFFFF","warning-100":"#FFF8CD","warning-200":"#FFEF9B","warning-300":"#FFE469","warning-400":"#FFDA43","warning-500":"#FFC805","warning-600":"#DBA603","warning-700":"#B78702","warning-800":"#936901","warning-900":"#7A5400","danger-text":"#FFFFFF","danger-100":"#F4B0B0","danger-200":"#EE8A8A","danger-300":"#E65454","danger-400":"#E13333","danger-500":"#DA0000","danger-600":"#C60000","danger-700":"#9B0000","danger-800":"#780000","danger-900":"#5C0000"},"font":{"sizes":{"h1":"1.75rem","h2":"1.375rem","h3":"1.125rem","h4":"0.8125rem","h5":"0.625rem","h6":"0.5rem","l":"1rem","m":"0.8125rem","s":"0.6875rem"},"weights":{"thin":100,"regular":300,"medium":400,"bold":500,"extrabold":700,"black":900},"families":{"base":"Roboto","accent":"Roboto"}},"spacings":{"xl":"4rem","l":"3rem","b":"1.625rem","s":"1rem","t":"0.5rem","st":"0.25rem"},"transitions":{"ease-in":"cubic-bezier(0.32, 0, 0.67, 0)","ease-out":"cubic-bezier(0.33, 1, 0.68, 1)","ease-in-out":"cubic-bezier(0.65, 0, 0.35, 1)","duration":"250ms"}},"components":{"forms-radio":{"border-color":"#E7E8EA","accent-color":"#419A14"},"forms-input":{"font-weight":400,"font-size":"1rem","border-radius":"8px","border-radius--hover":"2px","border-radius--focus":"2px","border-width":"2px","border-color":"#E7E8EA","border-color--hover":"#9EA3AA","border-color--focus":"#0556BF","border-style":"solid","color":"#303C4B"},"forms-field":{"width":"292px","font-size":"0.6875rem","color":"#79818A"},"forms-checkbox":{"font-size":"0.8125rem","font-weight":400,"color":"#0C1A2B","border-color":"#E7E8EA","border-radius":"2px","accent-color":"#419A14","size":"1.5rem"},"button":{"border-radius":"8px","border-radius--active":"2px","medium-height":"48px","small-height":"32px","medium-font-size":"1rem","small-font-size":"0.8125rem","font-weight":400}}};
|
||||
export const tokens = {"theme":{"colors":{"primary-text":"#FFFFFF","primary-100":"#EBF2FC","primary-200":"#8CB5EA","primary-300":"#5894E1","primary-400":"#377FDB","primary-500":"#055FD2","primary-600":"#0556BF","primary-700":"#044395","primary-800":"#033474","primary-900":"#022858","secondary-text":"#555F6B","secondary-100":"#F2F7FC","secondary-200":"#EBF3FA","secondary-300":"#E2EEF8","secondary-400":"#DDEAF7","secondary-500":"#D4E5F5","secondary-600":"#C1D0DF","secondary-700":"#97A3AE","secondary-800":"#757E87","secondary-900":"#596067","greyscale-000":"#FFFFFF","greyscale-100":"#FAFAFB","greyscale-200":"#F3F4F4","greyscale-300":"#E7E8EA","greyscale-400":"#C2C6CA","greyscale-500":"#9EA3AA","greyscale-600":"#79818A","greyscale-700":"#555F6B","greyscale-800":"#303C4B","greyscale-900":"#0C1A2B","success-text":"#FFFFFF","success-100":"#EFFCD3","success-200":"#DBFAA9","success-300":"#BEF27C","success-400":"#A0E659","success-500":"#76D628","success-600":"#5AB81D","success-700":"#419A14","success-800":"#2C7C0C","success-900":"#1D6607","info-text":"#FFFFFF","info-100":"#EBF2FC","info-200":"#8CB5EA","info-300":"#5894E1","info-400":"#377FDB","info-500":"#055FD2","info-600":"#0556BF","info-700":"#044395","info-800":"#033474","info-900":"#022858","warning-text":"#FFFFFF","warning-100":"#FFF8CD","warning-200":"#FFEF9B","warning-300":"#FFE469","warning-400":"#FFDA43","warning-500":"#FFC805","warning-600":"#DBA603","warning-700":"#B78702","warning-800":"#936901","warning-900":"#7A5400","danger-text":"#FFFFFF","danger-100":"#F4B0B0","danger-200":"#EE8A8A","danger-300":"#E65454","danger-400":"#E13333","danger-500":"#DA0000","danger-600":"#C60000","danger-700":"#9B0000","danger-800":"#780000","danger-900":"#5C0000"},"font":{"sizes":{"h1":"1.75rem","h2":"1.375rem","h3":"1.125rem","h4":"0.8125rem","h5":"0.625rem","h6":"0.5rem","l":"1rem","m":"0.8125rem","s":"0.6875rem"},"weights":{"thin":100,"regular":300,"medium":400,"bold":500,"extrabold":700,"black":900},"families":{"base":"Roboto","accent":"Roboto"}},"spacings":{"xl":"4rem","l":"3rem","b":"1.625rem","s":"1rem","t":"0.5rem","st":"0.25rem"},"transitions":{"ease-in":"cubic-bezier(0.32, 0, 0.67, 0)","ease-out":"cubic-bezier(0.33, 1, 0.68, 1)","ease-in-out":"cubic-bezier(0.65, 0, 0.35, 1)","duration":"250ms"}},"components":{"forms-select":{"border-color":"#E7E8EA","border-color--focus":"#0556BF","border-color--hover":"#9EA3AA","border-radius":"8px","border-radius--focus":"2px","border-radius--hover":"2px","border-style":"solid","border-width":"2px","color":"#303C4B","font-size":"1rem","height":"3.5rem","item-background-color--hover":"#F3F4F4","item-background-color--selected":"#EBF2FC","item-color":"#303C4B","item-font-size":"1rem","background-color":"white","menu-background-color":"white"},"forms-radio":{"border-color":"#E7E8EA","accent-color":"#419A14"},"forms-input":{"font-weight":400,"font-size":"1rem","border-radius":"8px","border-radius--hover":"2px","border-radius--focus":"2px","border-width":"2px","border-color":"#E7E8EA","border-color--hover":"#9EA3AA","border-color--focus":"#0556BF","border-style":"solid","color":"#303C4B"},"forms-field":{"width":"292px","font-size":"0.6875rem","color":"#79818A"},"forms-checkbox":{"font-size":"0.8125rem","font-weight":400,"color":"#0C1A2B","border-color":"#E7E8EA","border-radius":"2px","accent-color":"#419A14","size":"1.5rem"},"button":{"border-radius":"8px","border-radius--active":"2px","medium-height":"48px","small-height":"32px","medium-font-size":"1rem","small-font-size":"0.8125rem","font-weight":400}}};
|
||||
|
||||
@@ -1,151 +1 @@
|
||||
export const tokens = {
|
||||
theme: {
|
||||
colors: {
|
||||
"primary-text": "#FFFFFF",
|
||||
"primary-100": "#EBF2FC",
|
||||
"primary-200": "#8CB5EA",
|
||||
"primary-300": "#5894E1",
|
||||
"primary-400": "#377FDB",
|
||||
"primary-500": "#055FD2",
|
||||
"primary-600": "#0556BF",
|
||||
"primary-700": "#044395",
|
||||
"primary-800": "#033474",
|
||||
"primary-900": "#022858",
|
||||
"secondary-text": "#555F6B",
|
||||
"secondary-100": "#F2F7FC",
|
||||
"secondary-200": "#EBF3FA",
|
||||
"secondary-300": "#E2EEF8",
|
||||
"secondary-400": "#DDEAF7",
|
||||
"secondary-500": "#D4E5F5",
|
||||
"secondary-600": "#C1D0DF",
|
||||
"secondary-700": "#97A3AE",
|
||||
"secondary-800": "#757E87",
|
||||
"secondary-900": "#596067",
|
||||
"greyscale-000": "#FFFFFF",
|
||||
"greyscale-100": "#FAFAFB",
|
||||
"greyscale-200": "#F3F4F4",
|
||||
"greyscale-300": "#E7E8EA",
|
||||
"greyscale-400": "#C2C6CA",
|
||||
"greyscale-500": "#9EA3AA",
|
||||
"greyscale-600": "#79818A",
|
||||
"greyscale-700": "#555F6B",
|
||||
"greyscale-800": "#303C4B",
|
||||
"greyscale-900": "#0C1A2B",
|
||||
"success-text": "#FFFFFF",
|
||||
"success-100": "#EFFCD3",
|
||||
"success-200": "#DBFAA9",
|
||||
"success-300": "#BEF27C",
|
||||
"success-400": "#A0E659",
|
||||
"success-500": "#76D628",
|
||||
"success-600": "#5AB81D",
|
||||
"success-700": "#419A14",
|
||||
"success-800": "#2C7C0C",
|
||||
"success-900": "#1D6607",
|
||||
"info-text": "#FFFFFF",
|
||||
"info-100": "#EBF2FC",
|
||||
"info-200": "#8CB5EA",
|
||||
"info-300": "#5894E1",
|
||||
"info-400": "#377FDB",
|
||||
"info-500": "#055FD2",
|
||||
"info-600": "#0556BF",
|
||||
"info-700": "#044395",
|
||||
"info-800": "#033474",
|
||||
"info-900": "#022858",
|
||||
"warning-text": "#FFFFFF",
|
||||
"warning-100": "#FFF8CD",
|
||||
"warning-200": "#FFEF9B",
|
||||
"warning-300": "#FFE469",
|
||||
"warning-400": "#FFDA43",
|
||||
"warning-500": "#FFC805",
|
||||
"warning-600": "#DBA603",
|
||||
"warning-700": "#B78702",
|
||||
"warning-800": "#936901",
|
||||
"warning-900": "#7A5400",
|
||||
"danger-text": "#FFFFFF",
|
||||
"danger-100": "#F4B0B0",
|
||||
"danger-200": "#EE8A8A",
|
||||
"danger-300": "#E65454",
|
||||
"danger-400": "#E13333",
|
||||
"danger-500": "#DA0000",
|
||||
"danger-600": "#C60000",
|
||||
"danger-700": "#9B0000",
|
||||
"danger-800": "#780000",
|
||||
"danger-900": "#5C0000",
|
||||
},
|
||||
font: {
|
||||
sizes: {
|
||||
h1: "1.75rem",
|
||||
h2: "1.375rem",
|
||||
h3: "1.125rem",
|
||||
h4: "0.8125rem",
|
||||
h5: "0.625rem",
|
||||
h6: "0.5rem",
|
||||
l: "1rem",
|
||||
m: "0.8125rem",
|
||||
s: "0.6875rem",
|
||||
},
|
||||
weights: {
|
||||
thin: 100,
|
||||
regular: 300,
|
||||
medium: 400,
|
||||
bold: 500,
|
||||
extrabold: 700,
|
||||
black: 900,
|
||||
},
|
||||
families: { base: "Roboto", accent: "Roboto" },
|
||||
},
|
||||
spacings: {
|
||||
xl: "4rem",
|
||||
l: "3rem",
|
||||
b: "1.625rem",
|
||||
s: "1rem",
|
||||
t: "0.5rem",
|
||||
st: "0.25rem",
|
||||
},
|
||||
transitions: {
|
||||
"ease-in": "cubic-bezier(0.32, 0, 0.67, 0)",
|
||||
"ease-out": "cubic-bezier(0.33, 1, 0.68, 1)",
|
||||
"ease-in-out": "cubic-bezier(0.65, 0, 0.35, 1)",
|
||||
duration: "250ms",
|
||||
},
|
||||
},
|
||||
components: {
|
||||
"forms-radio": { "border-color": "#E7E8EA", "accent-color": "#419A14" },
|
||||
"forms-input": {
|
||||
"font-weight": 400,
|
||||
"font-size": "1rem",
|
||||
"border-radius": "8px",
|
||||
"border-radius--hover": "2px",
|
||||
"border-radius--focus": "2px",
|
||||
"border-width": "2px",
|
||||
"border-color": "#E7E8EA",
|
||||
"border-color--hover": "#9EA3AA",
|
||||
"border-color--focus": "#0556BF",
|
||||
"border-style": "solid",
|
||||
color: "#303C4B",
|
||||
},
|
||||
"forms-field": {
|
||||
width: "292px",
|
||||
"font-size": "0.6875rem",
|
||||
color: "#79818A",
|
||||
},
|
||||
"forms-checkbox": {
|
||||
"font-size": "0.8125rem",
|
||||
"font-weight": 400,
|
||||
color: "#0C1A2B",
|
||||
"border-color": "#E7E8EA",
|
||||
"border-radius": "2px",
|
||||
"accent-color": "#419A14",
|
||||
size: "1.5rem",
|
||||
},
|
||||
button: {
|
||||
"border-radius": "8px",
|
||||
"border-radius--active": "2px",
|
||||
"medium-height": "48px",
|
||||
"small-height": "32px",
|
||||
"medium-font-size": "1rem",
|
||||
"small-font-size": "0.8125rem",
|
||||
"font-weight": 400,
|
||||
},
|
||||
},
|
||||
};
|
||||
export const tokens = {"theme":{"colors":{"primary-text":"#FFFFFF","primary-100":"#EBF2FC","primary-200":"#8CB5EA","primary-300":"#5894E1","primary-400":"#377FDB","primary-500":"#055FD2","primary-600":"#0556BF","primary-700":"#044395","primary-800":"#033474","primary-900":"#022858","secondary-text":"#555F6B","secondary-100":"#F2F7FC","secondary-200":"#EBF3FA","secondary-300":"#E2EEF8","secondary-400":"#DDEAF7","secondary-500":"#D4E5F5","secondary-600":"#C1D0DF","secondary-700":"#97A3AE","secondary-800":"#757E87","secondary-900":"#596067","greyscale-000":"#FFFFFF","greyscale-100":"#FAFAFB","greyscale-200":"#F3F4F4","greyscale-300":"#E7E8EA","greyscale-400":"#C2C6CA","greyscale-500":"#9EA3AA","greyscale-600":"#79818A","greyscale-700":"#555F6B","greyscale-800":"#303C4B","greyscale-900":"#0C1A2B","success-text":"#FFFFFF","success-100":"#EFFCD3","success-200":"#DBFAA9","success-300":"#BEF27C","success-400":"#A0E659","success-500":"#76D628","success-600":"#5AB81D","success-700":"#419A14","success-800":"#2C7C0C","success-900":"#1D6607","info-text":"#FFFFFF","info-100":"#EBF2FC","info-200":"#8CB5EA","info-300":"#5894E1","info-400":"#377FDB","info-500":"#055FD2","info-600":"#0556BF","info-700":"#044395","info-800":"#033474","info-900":"#022858","warning-text":"#FFFFFF","warning-100":"#FFF8CD","warning-200":"#FFEF9B","warning-300":"#FFE469","warning-400":"#FFDA43","warning-500":"#FFC805","warning-600":"#DBA603","warning-700":"#B78702","warning-800":"#936901","warning-900":"#7A5400","danger-text":"#FFFFFF","danger-100":"#F4B0B0","danger-200":"#EE8A8A","danger-300":"#E65454","danger-400":"#E13333","danger-500":"#DA0000","danger-600":"#C60000","danger-700":"#9B0000","danger-800":"#780000","danger-900":"#5C0000"},"font":{"sizes":{"h1":"1.75rem","h2":"1.375rem","h3":"1.125rem","h4":"0.8125rem","h5":"0.625rem","h6":"0.5rem","l":"1rem","m":"0.8125rem","s":"0.6875rem"},"weights":{"thin":100,"regular":300,"medium":400,"bold":500,"extrabold":700,"black":900},"families":{"base":"Roboto","accent":"Roboto"}},"spacings":{"xl":"4rem","l":"3rem","b":"1.625rem","s":"1rem","t":"0.5rem","st":"0.25rem"},"transitions":{"ease-in":"cubic-bezier(0.32, 0, 0.67, 0)","ease-out":"cubic-bezier(0.33, 1, 0.68, 1)","ease-in-out":"cubic-bezier(0.65, 0, 0.35, 1)","duration":"250ms"}},"components":{"forms-select":{"border-color":"#E7E8EA","border-color--focus":"#0556BF","border-color--hover":"#9EA3AA","border-radius":"8px","border-radius--focus":"2px","border-radius--hover":"2px","border-style":"solid","border-width":"2px","color":"#303C4B","font-size":"1rem","height":"3.5rem","item-background-color--hover":"#F3F4F4","item-background-color--selected":"#EBF2FC","item-color":"#303C4B","item-font-size":"1rem","background-color":"white","menu-background-color":"white"},"forms-radio":{"border-color":"#E7E8EA","accent-color":"#419A14"},"forms-input":{"font-weight":400,"font-size":"1rem","border-radius":"8px","border-radius--hover":"2px","border-radius--focus":"2px","border-width":"2px","border-color":"#E7E8EA","border-color--hover":"#9EA3AA","border-color--focus":"#0556BF","border-style":"solid","color":"#303C4B"},"forms-field":{"width":"292px","font-size":"0.6875rem","color":"#79818A"},"forms-checkbox":{"font-size":"0.8125rem","font-weight":400,"color":"#0C1A2B","border-color":"#E7E8EA","border-radius":"2px","accent-color":"#419A14","size":"1.5rem"},"button":{"border-radius":"8px","border-radius--active":"2px","medium-height":"48px","small-height":"32px","medium-font-size":"1rem","small-font-size":"0.8125rem","font-weight":400}}};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@import './components/Forms/Radio';
|
||||
@import './components/Forms/Input';
|
||||
@import './components/Forms/LabelledBox';
|
||||
@import './components/Forms/Select';
|
||||
@import './components/Loader';
|
||||
@import './components/Pagination';
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export * from "./components/DataGrid/SimpleDataGrid";
|
||||
export * from "./components/DataGrid/DataList";
|
||||
export * from "./components/Forms/Field";
|
||||
export * from "./components/Forms/Input";
|
||||
export * from "./components/Forms/Select";
|
||||
export * from "./components/Loader";
|
||||
export * from "./components/Pagination";
|
||||
export * from "./components/Provider";
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"test": "This is a test: {name}"
|
||||
},
|
||||
"forms": {
|
||||
"select": {
|
||||
"toggle_button_aria_label": "Toggle dropdown",
|
||||
"clear_button_aria_label": "Clear selection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"test": "Ceci est un test : {name}"
|
||||
},
|
||||
"forms": {
|
||||
"select": {
|
||||
"toggle_button_aria_label": "Ouvrir le menu",
|
||||
"clear_button_aria_label": "Effacer la sélection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
yarn.lock
22
yarn.lock
@@ -1330,7 +1330,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.14.8", "@babel/runtime@^7.9.2":
|
||||
version "7.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
||||
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
||||
@@ -6165,6 +6165,11 @@ compression@^1.7.4:
|
||||
safe-buffer "5.1.2"
|
||||
vary "~1.1.2"
|
||||
|
||||
compute-scroll-into-view@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz#2b444b2b9e4724819d2531efacb7ac094155fdf6"
|
||||
integrity sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
@@ -6875,6 +6880,17 @@ dotenv@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
|
||||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||
|
||||
downshift@^7.6.0:
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/downshift/-/downshift-7.6.0.tgz#de04fb2962bd6c4ea94589c797c91f34aa9816f3"
|
||||
integrity sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.8"
|
||||
compute-scroll-into-view "^2.0.4"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
tslib "^2.3.0"
|
||||
|
||||
duplexify@^3.4.2, duplexify@^3.6.0:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
||||
@@ -12264,7 +12280,7 @@ react-inspector@^5.1.0:
|
||||
is-dom "^1.0.0"
|
||||
prop-types "^15.0.0"
|
||||
|
||||
react-is@17.0.2, react-is@^17.0.1:
|
||||
react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
@@ -13982,7 +13998,7 @@ tslib@^1.8.1, tslib@^1.9.3:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.4.0, tslib@^2.5.0:
|
||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
|
||||
Reference in New Issue
Block a user