@ceed/cds 1.39.1 → 1.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Autocomplete/Autocomplete.d.ts +11 -3
- package/dist/components/CurrencyInput/CurrencyInput.d.ts +10 -2
- package/dist/components/DatePicker/DatePicker.d.ts +4 -1
- package/dist/components/DateRangePicker/DateRangePicker.d.ts +4 -1
- package/dist/components/FilterableCheckboxGroup/FilterableCheckboxGroup.d.ts +5 -1
- package/dist/components/Input/Input.d.ts +6 -1
- package/dist/components/MonthPicker/MonthPicker.d.ts +5 -1
- package/dist/components/MonthRangePicker/MonthRangePicker.d.ts +5 -1
- package/dist/components/PercentageInput/PercentageInput.d.ts +10 -2
- package/dist/components/data-display/DataTable.md +89 -0
- package/dist/components/inputs/Autocomplete.md +37 -33
- package/dist/components/inputs/CurrencyInput.md +46 -17
- package/dist/components/inputs/DatePicker.md +4 -2
- package/dist/components/inputs/DateRangePicker.md +4 -2
- package/dist/components/inputs/FilterableCheckboxGroup.md +3 -1
- package/dist/components/inputs/Input.md +32 -3
- package/dist/components/inputs/MonthPicker.md +4 -2
- package/dist/components/inputs/MonthRangePicker.md +4 -2
- package/dist/components/inputs/PercentageInput.md +20 -18
- package/dist/hooks/use-controlled-state/index.d.ts +13 -0
- package/dist/index.browser.js +4 -4
- package/dist/index.browser.js.map +3 -3
- package/dist/index.cjs +81 -53
- package/dist/index.js +116 -88
- package/framer/index.js +1 -1
- package/package.json +1 -1
|
@@ -9,23 +9,31 @@ export interface AutocompleteOption {
|
|
|
9
9
|
}
|
|
10
10
|
export type AutocompleteValue<Multiple extends boolean | undefined = false> = Multiple extends true ? string[] : string;
|
|
11
11
|
export type AutocompleteProps<T extends AutocompleteOption | string = string, Multiple extends boolean | undefined = false> = {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
14
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
15
|
+
*/
|
|
16
|
+
value?: AutocompleteValue<Multiple> | null;
|
|
13
17
|
defaultValue?: AutocompleteValue<Multiple>;
|
|
14
18
|
multiple?: Multiple;
|
|
15
19
|
options: T[];
|
|
16
20
|
label?: React.ReactNode;
|
|
17
21
|
error?: boolean;
|
|
18
22
|
helperText?: React.ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* NOTE: 빈 값은 `undefined`가 아니라 `null`로 전달된다. 그래야 `value={selected}`로 그대로
|
|
25
|
+
* 되먹여도 controlled가 유지된다.
|
|
26
|
+
*/
|
|
19
27
|
onChange?: (event: {
|
|
20
28
|
target: {
|
|
21
29
|
name?: string;
|
|
22
|
-
value?: AutocompleteValue<Multiple
|
|
30
|
+
value?: AutocompleteValue<Multiple> | null;
|
|
23
31
|
};
|
|
24
32
|
}) => void;
|
|
25
33
|
onChangeComplete?: (event: {
|
|
26
34
|
target: {
|
|
27
35
|
name?: string;
|
|
28
|
-
value?: AutocompleteValue<Multiple
|
|
36
|
+
value?: AutocompleteValue<Multiple> | null;
|
|
29
37
|
};
|
|
30
38
|
}) => void;
|
|
31
39
|
} & Omit<JoyAutocompleteProps<AutocompleteOption, Multiple, boolean, boolean>, 'onChange' | 'value' | 'options' | 'defaultValue'>;
|
|
@@ -5,12 +5,20 @@ import type { Currency } from './types';
|
|
|
5
5
|
export interface CurrencyInputProps {
|
|
6
6
|
currency?: Currency;
|
|
7
7
|
max?: number;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
10
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
11
|
+
*/
|
|
12
|
+
value?: number | null;
|
|
9
13
|
defaultValue?: number;
|
|
14
|
+
/**
|
|
15
|
+
* NOTE: 빈 값은 `undefined`가 아니라 `null`로 전달된다. 그래야 `value={amount}`로 그대로
|
|
16
|
+
* 되먹여도 controlled가 유지된다. 상태는 `useState<number | null>(null)`로 잡으면 된다.
|
|
17
|
+
*/
|
|
10
18
|
onChange?: (event: {
|
|
11
19
|
target: {
|
|
12
20
|
name?: string;
|
|
13
|
-
value?: number;
|
|
21
|
+
value?: number | null;
|
|
14
22
|
};
|
|
15
23
|
}) => void;
|
|
16
24
|
name?: string;
|
|
@@ -9,8 +9,11 @@ export interface DatePickerPreset {
|
|
|
9
9
|
interface BaseDatePickerProps {
|
|
10
10
|
/**
|
|
11
11
|
* props.format 의 형식을 따라야 한다.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
14
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
12
15
|
*/
|
|
13
|
-
value?: string;
|
|
16
|
+
value?: string | null;
|
|
14
17
|
defaultValue?: string;
|
|
15
18
|
onChange?: (event: {
|
|
16
19
|
target: {
|
|
@@ -9,8 +9,11 @@ export interface DateRangePickerPreset {
|
|
|
9
9
|
interface BaseDateRangePickerProps {
|
|
10
10
|
/**
|
|
11
11
|
* props.format 의 형식을 따라야 한다.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
14
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
12
15
|
*/
|
|
13
|
-
value?: string;
|
|
16
|
+
value?: string | null;
|
|
14
17
|
defaultValue?: string;
|
|
15
18
|
onChange?: (event: {
|
|
16
19
|
target: {
|
|
@@ -5,7 +5,11 @@ interface FilterableCheckboxGroupOption {
|
|
|
5
5
|
disabled?: boolean;
|
|
6
6
|
}
|
|
7
7
|
export type FilterableCheckboxGroupProps = {
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
10
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
11
|
+
*/
|
|
12
|
+
value?: string[] | null;
|
|
9
13
|
options: FilterableCheckboxGroupOption[];
|
|
10
14
|
label?: React.ReactNode;
|
|
11
15
|
placeholder?: string;
|
|
@@ -12,6 +12,11 @@ export type InputProps = {
|
|
|
12
12
|
* This has no effect when type is not "password".
|
|
13
13
|
*/
|
|
14
14
|
disableTogglePasswordButton?: boolean;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
17
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
18
|
+
*/
|
|
19
|
+
value?: JoyInputProps['value'] | null;
|
|
20
|
+
} & Omit<JoyInputProps, 'value'> & MotionProps;
|
|
16
21
|
declare const Input: React.ForwardRefExoticComponent<Omit<InputProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
17
22
|
export { Input };
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Input from '../Input';
|
|
3
3
|
interface BaseMonthPickerProps {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
6
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
7
|
+
*/
|
|
8
|
+
value?: string | null;
|
|
5
9
|
defaultValue?: string;
|
|
6
10
|
onChange?: (event: {
|
|
7
11
|
target: {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Input from '../Input';
|
|
3
3
|
interface BaseMonthRangePickerProps {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
6
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
7
|
+
*/
|
|
8
|
+
value?: string | null;
|
|
5
9
|
defaultValue?: string;
|
|
6
10
|
onChange?: (event: {
|
|
7
11
|
target: {
|
|
@@ -2,12 +2,20 @@ import React from 'react';
|
|
|
2
2
|
import { InputProps } from '@mui/joy';
|
|
3
3
|
import { MotionProps } from 'framer-motion';
|
|
4
4
|
interface BasePercentageInputProps {
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* NOTE: 빈 값으로 시작하는 controlled 필드는 `undefined`가 아니라 `null`을 넘겨야 한다.
|
|
7
|
+
* `undefined`를 넘기면 마운트 시점에 uncontrolled로 판정되어 이후 외부에서의 값 변경이 반영되지 않는다.
|
|
8
|
+
*/
|
|
9
|
+
value?: number | null;
|
|
6
10
|
defaultValue?: number;
|
|
11
|
+
/**
|
|
12
|
+
* NOTE: 빈 값은 `undefined`가 아니라 `null`로 전달된다. 그래야 `value={amount}`로 그대로
|
|
13
|
+
* 되먹여도 controlled가 유지된다. 상태는 `useState<number | null>(null)`로 잡으면 된다.
|
|
14
|
+
*/
|
|
7
15
|
onChange?: (event: {
|
|
8
16
|
target: {
|
|
9
17
|
name?: string;
|
|
10
|
-
value?: number;
|
|
18
|
+
value?: number | null;
|
|
11
19
|
};
|
|
12
20
|
}) => void;
|
|
13
21
|
name?: string;
|
|
@@ -1640,6 +1640,95 @@ const columns = [
|
|
|
1640
1640
|
- Supported values: `1 | 2`.
|
|
1641
1641
|
- Works with all existing features: sorting, pinning, resizing, and column grouping.
|
|
1642
1642
|
|
|
1643
|
+
## Column Width
|
|
1644
|
+
|
|
1645
|
+
Columns keep the width you specify. When every column has an explicit `width`, the table appends an
|
|
1646
|
+
invisible filler column that absorbs the space left over, so a `200px` column renders at exactly 200px
|
|
1647
|
+
instead of stretching to fill the container.
|
|
1648
|
+
|
|
1649
|
+
```tsx
|
|
1650
|
+
<Stack gap={1}>
|
|
1651
|
+
<Typography level="body-sm" textColor="text.secondary">
|
|
1652
|
+
Three 200px columns in a 900px container. Each column keeps exactly 200px instead of
|
|
1653
|
+
stretching to fill the table, and dragging a resize handle moves the edge 1:1.
|
|
1654
|
+
</Typography>
|
|
1655
|
+
<Box
|
|
1656
|
+
sx={{
|
|
1657
|
+
width: "900px"
|
|
1658
|
+
}}
|
|
1659
|
+
>
|
|
1660
|
+
<DataTable rows={widthRows} columns={columns} noWrap />
|
|
1661
|
+
</Box>
|
|
1662
|
+
</Stack>
|
|
1663
|
+
```
|
|
1664
|
+
|
|
1665
|
+
```tsx
|
|
1666
|
+
const columns = [
|
|
1667
|
+
{ field: 'name', headerName: 'Name', width: '200px' },
|
|
1668
|
+
{ field: 'category', headerName: 'Category', width: '200px' },
|
|
1669
|
+
{ field: 'calories', headerName: 'Calories', type: 'number', width: '200px' },
|
|
1670
|
+
];
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
Dragging a resize handle moves the column edge 1:1 with the pointer — the other columns keep their
|
|
1674
|
+
widths and the filler absorbs the difference.
|
|
1675
|
+
|
|
1676
|
+
```tsx
|
|
1677
|
+
<Stack gap={1}>
|
|
1678
|
+
<Typography level="body-sm" textColor="text.secondary">
|
|
1679
|
+
Drag the handle on the Name column — the edge follows the pointer 1:1 and the other columns
|
|
1680
|
+
stay put.
|
|
1681
|
+
</Typography>
|
|
1682
|
+
<Box
|
|
1683
|
+
sx={{
|
|
1684
|
+
width: "900px"
|
|
1685
|
+
}}
|
|
1686
|
+
>
|
|
1687
|
+
<DataTable rows={widthRows} columns={columns} noWrap />
|
|
1688
|
+
</Box>
|
|
1689
|
+
</Stack>
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
### Letting a Column Fill the Remaining Space
|
|
1693
|
+
|
|
1694
|
+
Omit `width` on a column and it absorbs all the leftover space — no filler column is added.
|
|
1695
|
+
|
|
1696
|
+
```tsx
|
|
1697
|
+
<Stack gap={1}>
|
|
1698
|
+
<Typography level="body-sm" textColor="text.secondary">
|
|
1699
|
+
Leave a column without a width and it absorbs all the leftover space, exactly as before.
|
|
1700
|
+
</Typography>
|
|
1701
|
+
<Box
|
|
1702
|
+
sx={{
|
|
1703
|
+
width: "900px"
|
|
1704
|
+
}}
|
|
1705
|
+
>
|
|
1706
|
+
<DataTable rows={widthRows} columns={columns} noWrap />
|
|
1707
|
+
</Box>
|
|
1708
|
+
</Stack>
|
|
1709
|
+
```
|
|
1710
|
+
|
|
1711
|
+
```tsx
|
|
1712
|
+
const columns = [
|
|
1713
|
+
{ field: 'name', headerName: 'Name' }, // fills the remaining space
|
|
1714
|
+
{ field: 'category', headerName: 'Category', width: '200px' },
|
|
1715
|
+
{ field: 'calories', headerName: 'Calories', type: 'number', width: '200px' },
|
|
1716
|
+
];
|
|
1717
|
+
```
|
|
1718
|
+
|
|
1719
|
+
#### Notes
|
|
1720
|
+
|
|
1721
|
+
- Percentage widths (`'40%'`) resolve against the table width, so `40%` really is 40% of the table.
|
|
1722
|
+
- When the columns are wider than the container the filler collapses to 0 and horizontal scrolling
|
|
1723
|
+
works as usual — pinned columns stay pinned.
|
|
1724
|
+
- `minWidth` is enforced by the layout; `maxWidth` is only applied while dragging the resize handle.
|
|
1725
|
+
- Dragging a resize handle now moves the column edge 1:1 with the pointer.
|
|
1726
|
+
- Column groups and the no-rows overlay account for the filler automatically — group `colspan`
|
|
1727
|
+
values and the overlay's `colSpan` are unaffected.
|
|
1728
|
+
- A custom `slots.footer` writes its own `colSpan` values. Those keep aligning with the data
|
|
1729
|
+
columns, but the footer will not extend under the filler column. Add one trailing empty `<td />`
|
|
1730
|
+
if you want the footer to span the full table width.
|
|
1731
|
+
|
|
1643
1732
|
## Editing Features
|
|
1644
1733
|
|
|
1645
1734
|
### Inline Editing
|
|
@@ -66,7 +66,8 @@ function CountrySelector() {
|
|
|
66
66
|
label="Country"
|
|
67
67
|
placeholder="Search countries..."
|
|
68
68
|
options={['United States', 'United Kingdom', 'Canada', 'Australia']}
|
|
69
|
-
|
|
69
|
+
// NOTE: pass `null`, not `undefined`, while the field is empty - see Props and Customization.
|
|
70
|
+
value={country ?? null}
|
|
70
71
|
onChange={(e) => setCountry(e.target.value)}
|
|
71
72
|
/>
|
|
72
73
|
);
|
|
@@ -782,36 +783,38 @@ function LazyAutocomplete({ fetchOptions }) {
|
|
|
782
783
|
|
|
783
784
|
### Key Props
|
|
784
785
|
|
|
785
|
-
| Prop | Type
|
|
786
|
-
| ----------------------- |
|
|
787
|
-
| `options` | `string[] \| OptionObject[]`
|
|
788
|
-
| `value` | `string \| string[]`
|
|
789
|
-
| `defaultValue` | `string \| string[]`
|
|
790
|
-
| `onChange` | `(event: { target: { value } }) => void`
|
|
791
|
-
| `onInputChange` | `(event: { target: { value } }) => void`
|
|
792
|
-
| `label` | `ReactNode`
|
|
793
|
-
| `placeholder` | `string`
|
|
794
|
-
| `loading` | `boolean`
|
|
795
|
-
| `multiple` | `boolean`
|
|
796
|
-
| `groupBy` | `(option) => string`
|
|
797
|
-
| `size` | `'sm' \| 'md' \| 'lg'`
|
|
798
|
-
| `variant` | `'outlined' \| 'soft' \| 'solid' \| 'plain'`
|
|
799
|
-
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'`
|
|
800
|
-
| `disabled` | `boolean`
|
|
801
|
-
| `readOnly` | `boolean`
|
|
802
|
-
| `required` | `boolean`
|
|
803
|
-
| `error` | `boolean`
|
|
804
|
-
| `helperText` | `ReactNode`
|
|
805
|
-
| `freeSolo` | `boolean`
|
|
806
|
-
| `disableClearable` | `boolean`
|
|
807
|
-
| `noOptionsText` | `ReactNode`
|
|
808
|
-
| `loadingText` | `ReactNode`
|
|
809
|
-
| `limitTags` | `number`
|
|
810
|
-
| `autoHighlight` | `boolean`
|
|
811
|
-
| `clearOnEscape` | `boolean`
|
|
812
|
-
| `disableCloseOnSelect` | `boolean`
|
|
813
|
-
| `openOnFocus` | `boolean`
|
|
814
|
-
| `filterSelectedOptions` | `boolean`
|
|
786
|
+
| Prop | Type | Default | Description |
|
|
787
|
+
| ----------------------- | ---------------------------------------------------------------------------- | -------------- | ----------------------------------------------------- |
|
|
788
|
+
| `options` | `string[] \| OptionObject[]` | - | Array of options (strings or objects) |
|
|
789
|
+
| `value` | `string \| string[] \| null` | - | Selected value(s) for controlled mode |
|
|
790
|
+
| `defaultValue` | `string \| string[]` | - | Initial value for uncontrolled mode |
|
|
791
|
+
| `onChange` | `(event: { target: { name?, value?: string \| string[] \| null } }) => void` | - | Callback when selection changes |
|
|
792
|
+
| `onInputChange` | `(event: { target: { value } }) => void` | - | Callback when input text changes |
|
|
793
|
+
| `label` | `ReactNode` | - | Label text above the input |
|
|
794
|
+
| `placeholder` | `string` | - | Placeholder text when empty |
|
|
795
|
+
| `loading` | `boolean` | `false` | Show loading indicator |
|
|
796
|
+
| `multiple` | `boolean` | `false` | Allow multiple selections |
|
|
797
|
+
| `groupBy` | `(option) => string` | - | Function to group options |
|
|
798
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
|
|
799
|
+
| `variant` | `'outlined' \| 'soft' \| 'solid' \| 'plain'` | `'outlined'` | Visual style variant |
|
|
800
|
+
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color scheme |
|
|
801
|
+
| `disabled` | `boolean` | `false` | Disable the input |
|
|
802
|
+
| `readOnly` | `boolean` | `false` | Make the input read-only (focusable but not editable) |
|
|
803
|
+
| `required` | `boolean` | `false` | Mark the field as required |
|
|
804
|
+
| `error` | `boolean` | `false` | Indicate an error state |
|
|
805
|
+
| `helperText` | `ReactNode` | - | Helper text below the input |
|
|
806
|
+
| `freeSolo` | `boolean` | `false` | Allow arbitrary values not in the options list |
|
|
807
|
+
| `disableClearable` | `boolean` | `false` | Hide the clear (X) button |
|
|
808
|
+
| `noOptionsText` | `ReactNode` | `'No options'` | Text when no options match |
|
|
809
|
+
| `loadingText` | `ReactNode` | `'Loading…'` | Text while loading |
|
|
810
|
+
| `limitTags` | `number` | `-1` | Max visible tags in multiple mode (-1 for unlimited) |
|
|
811
|
+
| `autoHighlight` | `boolean` | `false` | Auto-highlight first option |
|
|
812
|
+
| `clearOnEscape` | `boolean` | `false` | Clear value on Escape key |
|
|
813
|
+
| `disableCloseOnSelect` | `boolean` | `false` | Keep popup open after selection |
|
|
814
|
+
| `openOnFocus` | `boolean` | `false` | Open popup on focus |
|
|
815
|
+
| `filterSelectedOptions` | `boolean` | `false` | Hide selected options from dropdown (multiple mode) |
|
|
816
|
+
|
|
817
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
815
818
|
|
|
816
819
|
### Option Object Structure
|
|
817
820
|
|
|
@@ -852,11 +855,12 @@ const options = [
|
|
|
852
855
|
### Controlled vs Uncontrolled
|
|
853
856
|
|
|
854
857
|
```tsx
|
|
855
|
-
// Controlled - manage value externally
|
|
858
|
+
// Controlled - manage value externally.
|
|
859
|
+
// NOTE: pass `null`, not `undefined`, while the field is empty - see the note under Props.
|
|
856
860
|
function ControlledExample() {
|
|
857
861
|
const [value, setValue] = useState<string | undefined>();
|
|
858
862
|
|
|
859
|
-
return <Autocomplete value={value} onChange={(e) => setValue(e.target.value)} options={options} />;
|
|
863
|
+
return <Autocomplete value={value ?? null} onChange={(e) => setValue(e.target.value)} options={options} />;
|
|
860
864
|
}
|
|
861
865
|
|
|
862
866
|
// Uncontrolled - internal state management
|
|
@@ -229,6 +229,33 @@ function BudgetSetting() {
|
|
|
229
229
|
}
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
+
## Controlled Empty Value
|
|
233
|
+
|
|
234
|
+
Whether a field is controlled is decided **once, on mount** — the same convention MUI's `useControlled` follows. A field that mounts with `value={undefined}` is treated as uncontrolled for its whole lifetime, so later updates from the parent never reach the screen.
|
|
235
|
+
|
|
236
|
+
Pass `null` to express "controlled, but currently empty":
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { useState } from 'react';
|
|
240
|
+
import { Button, CurrencyInput, Stack } from '@ceed/cds';
|
|
241
|
+
|
|
242
|
+
function AmountField() {
|
|
243
|
+
const [amount, setAmount] = useState<number | null>(null);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<Stack gap={2}>
|
|
247
|
+
<CurrencyInput label="Amount" value={amount} onChange={(e) => setAmount(e.target.value)} />
|
|
248
|
+
{/* Clears the input on screen, not just in state. */}
|
|
249
|
+
<Button onClick={() => setAmount(null)}>Reset</Button>
|
|
250
|
+
</Stack>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Form libraries are where this bites. When something outside the input empties the state — `react-hook-form`'s `setValue('amount', undefined)`, for instance — `value={field.value ?? null}` is what carries that back to the screen.
|
|
256
|
+
|
|
257
|
+
`onChange` reports an empty field as `null` too, so the value it hands you can go straight back into `value` without a conversion step. Typing the state as `number | null` and seeding it with `null` is the shape that needs no `??` anywhere.
|
|
258
|
+
|
|
232
259
|
## Best Practices
|
|
233
260
|
|
|
234
261
|
1. **Choose the right currency**: Always set the `currency` prop explicitly. USD uses 2 decimal places while KRW uses none.
|
|
@@ -254,23 +281,25 @@ function BudgetSetting() {
|
|
|
254
281
|
|
|
255
282
|
### Key Props
|
|
256
283
|
|
|
257
|
-
| Prop | Type
|
|
258
|
-
| -------------- |
|
|
259
|
-
| `value` | `number` | - | Currency value (controlled mode) |
|
|
260
|
-
| `defaultValue` | `number`
|
|
261
|
-
| `onChange` | `(event: { target: { name?, value?: number } }) => void` | - | Callback when the value changes |
|
|
262
|
-
| `currency` | `'USD' \| 'KRW' \| 'CAD' \| 'usd' \| 'krw' \| 'cad'`
|
|
263
|
-
| `useMinorUnit` | `boolean`
|
|
264
|
-
| `max` | `number`
|
|
265
|
-
| `label` | `ReactNode`
|
|
266
|
-
| `helperText` | `ReactNode`
|
|
267
|
-
| `error` | `boolean`
|
|
268
|
-
| `required` | `boolean`
|
|
269
|
-
| `disabled` | `boolean`
|
|
270
|
-
| `name` | `string`
|
|
271
|
-
| `placeholder` | `string`
|
|
272
|
-
| `size` | `'sm' \| 'md' \| 'lg'`
|
|
273
|
-
| `sx` | `SxProps`
|
|
284
|
+
| Prop | Type | Default | Description |
|
|
285
|
+
| -------------- | ---------------------------------------------------------------- | ------- | ------------------------------------------------------------------------ |
|
|
286
|
+
| `value` | `number \| null` | - | Currency value (controlled mode) |
|
|
287
|
+
| `defaultValue` | `number` | - | Initial currency value (uncontrolled mode) |
|
|
288
|
+
| `onChange` | `(event: { target: { name?, value?: number \| null } }) => void` | - | Callback when the value changes |
|
|
289
|
+
| `currency` | `'USD' \| 'KRW' \| 'CAD' \| 'usd' \| 'krw' \| 'cad'` | `'USD'` | Currency code (uppercase or lowercase; determines symbol and formatting) |
|
|
290
|
+
| `useMinorUnit` | `boolean` | `false` | When true, value is in minor units (e.g., cents for USD) |
|
|
291
|
+
| `max` | `number` | - | Maximum allowed value |
|
|
292
|
+
| `label` | `ReactNode` | - | Form label displayed above the input |
|
|
293
|
+
| `helperText` | `ReactNode` | - | Helper text displayed below the input |
|
|
294
|
+
| `error` | `boolean` | `false` | Applies danger color to indicate validation error |
|
|
295
|
+
| `required` | `boolean` | `false` | Marks the field as required |
|
|
296
|
+
| `disabled` | `boolean` | `false` | Disables the input |
|
|
297
|
+
| `name` | `string` | - | HTML name attribute for form submission |
|
|
298
|
+
| `placeholder` | `string` | - | Placeholder text when the input is empty |
|
|
299
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
|
|
300
|
+
| `sx` | `SxProps` | - | Custom styles using the MUI system |
|
|
301
|
+
|
|
302
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
274
303
|
|
|
275
304
|
## Accessibility
|
|
276
305
|
|
|
@@ -603,8 +603,8 @@ function RegionalDateField({ locale }: { locale: string }) {
|
|
|
603
603
|
|
|
604
604
|
| Prop | Type | Default | Description |
|
|
605
605
|
| ------------------- | ------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
606
|
-
| `value` | `string`
|
|
607
|
-
| `defaultValue` | `string` |
|
|
606
|
+
| `value` | `string \| null` | - | Selected date string in `format` (controlled mode) |
|
|
607
|
+
| `defaultValue` | `string` | - | Initial date string (uncontrolled mode) |
|
|
608
608
|
| `onChange` | `(event: { target: { name?, value: string } }) => void` | - | Callback when the date changes. Value is in `format` |
|
|
609
609
|
| `format` | `string` | `'YYYY/MM/DD'` | Format of the `value` and `onChange` value. Determines the internal data format |
|
|
610
610
|
| `displayFormat` | `string` | `'YYYY/MM/DD'` | Format displayed in the input field. Can differ from `format` for locale display. Supports `MMM` (short month name) and `MMMM` (full month name) tokens |
|
|
@@ -623,6 +623,8 @@ function RegionalDateField({ locale }: { locale: string }) {
|
|
|
623
623
|
| `inputReadOnly` | `boolean` | `false` | Prevents keyboard typing in the input; users select via the calendar, which opens on clicking either the input or the calendar button |
|
|
624
624
|
| `hideClearButton` | `boolean` | `false` | Hides the clear button in the calendar popup |
|
|
625
625
|
|
|
626
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
627
|
+
|
|
626
628
|
### format vs displayFormat
|
|
627
629
|
|
|
628
630
|
| Prop | Controls | Example |
|
|
@@ -725,8 +725,8 @@ const getDuration = (value) => {
|
|
|
725
725
|
|
|
726
726
|
| Prop | Type | Default | Description |
|
|
727
727
|
| ----------------- | ------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
728
|
-
| `value` | `string`
|
|
729
|
-
| `defaultValue` | `string` |
|
|
728
|
+
| `value` | `string \| null` | - | Selected date range string in `format` (controlled mode) |
|
|
729
|
+
| `defaultValue` | `string` | - | Initial date range string (uncontrolled mode) |
|
|
730
730
|
| `onChange` | `(event: { target: { name?, value: string } }) => void` | - | Callback when the date range changes |
|
|
731
731
|
| `format` | `string` | `'YYYY/MM/DD'` | Format of the `value` and `onChange` value |
|
|
732
732
|
| `displayFormat` | `string` | `'YYYY/MM/DD'` | Format displayed in the input field. Supports `MMM` (short month name) and `MMMM` (full month name) tokens |
|
|
@@ -745,6 +745,8 @@ const getDuration = (value) => {
|
|
|
745
745
|
| `hideClearButton` | `boolean` | `false` | Hides the clear button in the calendar popup |
|
|
746
746
|
| `numberOfMonths` | `number` | `1` | Number of calendar months to show side by side in the popup |
|
|
747
747
|
|
|
748
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
749
|
+
|
|
748
750
|
### Alphabetic Month Tokens (MMM / MMMM)
|
|
749
751
|
|
|
750
752
|
The `displayFormat` prop supports alphabetic month tokens for more human-readable date display:
|
|
@@ -377,7 +377,7 @@ When dealing with a large set of options like countries, the search filter and v
|
|
|
377
377
|
| Prop | Type | Default | Description |
|
|
378
378
|
| ------------- | -------------------------------------------------------- | ---------- | --------------------------------------------------- |
|
|
379
379
|
| `options` | `{ value: string; label: string; disabled?: boolean }[]` | (required) | Array of checkbox options |
|
|
380
|
-
| `value` | `string[]`
|
|
380
|
+
| `value` | `string[] \| null` | - | Selected values (controlled mode) |
|
|
381
381
|
| `onChange` | `(value: string[]) => void` | - | Callback when selection changes |
|
|
382
382
|
| `label` | `ReactNode` | - | Group label displayed above the component |
|
|
383
383
|
| `helperText` | `ReactNode` | - | Helper text displayed below the component |
|
|
@@ -387,6 +387,8 @@ When dealing with a large set of options like countries, the search filter and v
|
|
|
387
387
|
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
|
|
388
388
|
| `maxHeight` | `string \| number` | `300` | Maximum height of the options list before scrolling |
|
|
389
389
|
|
|
390
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
391
|
+
|
|
390
392
|
## Accessibility
|
|
391
393
|
|
|
392
394
|
- The search input is labeled by the component's `label` prop, ensuring screen readers announce its purpose.
|
|
@@ -416,6 +416,33 @@ function ValidatedForm() {
|
|
|
416
416
|
}
|
|
417
417
|
```
|
|
418
418
|
|
|
419
|
+
## Controlled Empty Value
|
|
420
|
+
|
|
421
|
+
Whether a field is controlled is decided **once, on mount** — the same convention MUI's `useControlled` follows. A field that mounts with `value={undefined}` is treated as uncontrolled for its whole lifetime, so later updates from the parent never reach the screen.
|
|
422
|
+
|
|
423
|
+
Pass `null` to express "controlled, but currently empty":
|
|
424
|
+
|
|
425
|
+
```tsx
|
|
426
|
+
import { useState } from 'react';
|
|
427
|
+
import { Button, Input } from '@ceed/cds';
|
|
428
|
+
|
|
429
|
+
function NameField() {
|
|
430
|
+
const [name, setName] = useState<string | undefined>(undefined);
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<>
|
|
434
|
+
<Input label="Name" value={name ?? null} onChange={(e) => setName(e.target.value)} />
|
|
435
|
+
{/* Clears the input on screen, not just in state. */}
|
|
436
|
+
<Button onClick={() => setName(undefined)}>Reset</Button>
|
|
437
|
+
</>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Form libraries are where this bites. When something outside the input empties the state — `react-hook-form`'s `setValue('name', undefined)`, for instance — `value={field.value ?? null}` is what carries that back to the screen.
|
|
443
|
+
|
|
444
|
+
`onChange` never emits `null` or `undefined`: `Input` reports an empty field as `''`. Only the `value` prop accepts `null`.
|
|
445
|
+
|
|
419
446
|
## Props and Customization
|
|
420
447
|
|
|
421
448
|
### Key Props
|
|
@@ -427,9 +454,9 @@ function ValidatedForm() {
|
|
|
427
454
|
| `error` | `boolean` | `false` | Applies danger color to indicate validation error |
|
|
428
455
|
| `enableClearable` | `boolean` | `false` | Shows a clear button when the input has a value |
|
|
429
456
|
| `disableTogglePasswordButton` | `boolean` | `false` | Hides the password visibility toggle (only applies when `type="password"`) |
|
|
430
|
-
| `value` | `string`
|
|
431
|
-
| `defaultValue` | `string`
|
|
432
|
-
| `onChange` | `(event:
|
|
457
|
+
| `value` | `string \| number \| readonly string[] \| null` | - | Input value for controlled mode |
|
|
458
|
+
| `defaultValue` | `string \| number \| readonly string[]` | - | Initial value for uncontrolled mode |
|
|
459
|
+
| `onChange` | `(event: { target: { name: string; value: string } }) => void` | - | Callback when the input value changes |
|
|
433
460
|
| `placeholder` | `string` | - | Placeholder text when the input is empty |
|
|
434
461
|
| `type` | `string` | `'text'` | HTML input type (`text`, `password`, `email`, `number`, etc.) |
|
|
435
462
|
| `disabled` | `boolean` | `false` | Disables the input |
|
|
@@ -443,6 +470,8 @@ function ValidatedForm() {
|
|
|
443
470
|
| `endDecorator` | `ReactNode` | - | Content rendered at the end of the input (not supported with `type="password"`) |
|
|
444
471
|
| `sx` | `SxProps` | - | Custom styles using the MUI system |
|
|
445
472
|
|
|
473
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
474
|
+
|
|
446
475
|
> **Note**: Input also accepts all Joy UI Input props and Framer Motion props.
|
|
447
476
|
|
|
448
477
|
## Best Practices
|
|
@@ -477,8 +477,8 @@ const handleChange = (e) => {
|
|
|
477
477
|
|
|
478
478
|
| Prop | Type | Default | Description |
|
|
479
479
|
| --------------- | ------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
480
|
-
| `value` | `string`
|
|
481
|
-
| `defaultValue` | `string` |
|
|
480
|
+
| `value` | `string \| null` | - | Selected month string in `format` (controlled mode) |
|
|
481
|
+
| `defaultValue` | `string` | - | Initial month string (uncontrolled mode) |
|
|
482
482
|
| `onChange` | `(event: { target: { name?, value: string } }) => void` | - | Callback when the month changes |
|
|
483
483
|
| `format` | `string` | `'YYYY/MM/DD'` | Format of the `value` and `onChange` value |
|
|
484
484
|
| `displayFormat` | `string` | `'YYYY/MM'` | Format displayed in the input field. Supports `MMM` (short month name) and `MMMM` (full month name) tokens |
|
|
@@ -494,6 +494,8 @@ const handleChange = (e) => {
|
|
|
494
494
|
| `disablePast` | `boolean` | `false` | Disables all past months |
|
|
495
495
|
| `locale` | `string` | `'default'` | Locale for month names (BCP 47 format) |
|
|
496
496
|
|
|
497
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
498
|
+
|
|
497
499
|
### Alphabetic Month Tokens (MMM / MMMM)
|
|
498
500
|
|
|
499
501
|
The `displayFormat` prop supports alphabetic month tokens for more human-readable month display:
|
|
@@ -391,8 +391,8 @@ const handleSubmit = () => {
|
|
|
391
391
|
|
|
392
392
|
| Prop | Type | Default | Description |
|
|
393
393
|
| --------------- | ------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
394
|
-
| `value` | `string`
|
|
395
|
-
| `defaultValue` | `string` |
|
|
394
|
+
| `value` | `string \| null` | - | Selected month range string in `format` (controlled mode) |
|
|
395
|
+
| `defaultValue` | `string` | - | Initial month range string (uncontrolled mode) |
|
|
396
396
|
| `onChange` | `(event: { target: { name?, value: string } }) => void` | - | Callback when the month range changes |
|
|
397
397
|
| `format` | `string` | `'YYYY/MM'` | Format of the `value` and `onChange` value |
|
|
398
398
|
| `displayFormat` | `string` | same as `format` | Format displayed in the input field. Supports `MMM` (short month name) and `MMMM` (full month name) tokens |
|
|
@@ -408,6 +408,8 @@ const handleSubmit = () => {
|
|
|
408
408
|
| `disableFuture` | `boolean` | `false` | Disables all future months |
|
|
409
409
|
| `disablePast` | `boolean` | `false` | Disables all past months |
|
|
410
410
|
|
|
411
|
+
> **Note**: For a **controlled** field that starts empty, pass `null` as `value`. Passing `undefined` makes the field uncontrolled from the first render, so later external updates (including resetting it to an empty value) are not reflected on screen.
|
|
412
|
+
|
|
411
413
|
### Alphabetic Month Tokens (MMM / MMMM)
|
|
412
414
|
|
|
413
415
|
The `displayFormat` prop supports alphabetic month tokens for more human-readable month display:
|