@agentero/design-system 0.35.0 → 0.36.0

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.
@@ -6673,6 +6673,284 @@
6673
6673
  ],
6674
6674
  "import": "import {\n Button,\n Divider,\n DropdownMenu,\n Field,\n FieldText,\n IconKeyboardArrowDown,\n IconSearch,\n Input,\n InputGroup,\n Label,\n Tag,\n} from \"@agentero/design-system\";"
6675
6675
  },
6676
+ "components-inputnumber": {
6677
+ "id": "components-inputnumber",
6678
+ "name": "InputNumber",
6679
+ "path": "./src/input-number/input-number.stories.tsx",
6680
+ "jsDocTags": {
6681
+ "summary": [
6682
+ "Numeric input formatting as you type, built on react-number-format"
6683
+ ],
6684
+ "dataAttribute": [
6685
+ "{string} data-slot - Always set to \"input-number\""
6686
+ ],
6687
+ "example": [
6688
+ "<FieldText>\n <Label>Annual premium</Label>\n <InputNumber name=\"premium\" thousandSeparator allowLeadingZeros={false} />\n</FieldText>"
6689
+ ]
6690
+ },
6691
+ "description": "InputNumber is a text field that only takes digits and a decimal point and\nformats them as the user types: thousands separators on request, leading\nzeros kept unless turned off, no sign. `onChange` fires with the formatted text in\n`event.target.value`. It is an `Input` driven by `react-number-format`, so\nit takes its label, messages and states from a surrounding `FieldText`.",
6692
+ "summary": "Numeric input formatting as you type, built on react-number-format",
6693
+ "reactComponentMeta": {
6694
+ "displayName": "InputNumber",
6695
+ "exportName": "InputNumber",
6696
+ "filePath": "/home/runner/work/design-system/design-system/src/input-number/input-number.tsx",
6697
+ "description": "InputNumber is a text field that only takes digits and a decimal point and\nformats them as the user types: a thousands separator when\n`thousandSeparator` is set, leading zeros kept unless `allowLeadingZeros` is\noff, no sign, no decimal rounding.\nAnything that is not a digit or the decimal point is ignored, and\n`maxNumber` drops any edit that would exceed it. `onChange` fires with the\nformatted text in `event.target.value` (`\"1,234\"`, not `1234`), which is\nwhat a form library stores; parse it where the number is consumed.\n\nIt is an [Input](?path=/docs/components-input--docs) driven by\n`react-number-format`, so it keeps the caret in place while it reformats\nand takes its wiring from a surrounding `FieldText` as a plain `Input`\ndoes. Every standard `<input>` attribute is accepted and forwarded, such as\n`placeholder`, `required`, `maxLength` or `aria-invalid`; `type` is limited\nto `text`, `tel` and `password`, and `value` and `defaultValue` also take a\nnumber or `null`. The invalid state comes from `aria-invalid`, which paints\nthe destructive border. Place it inside an\n[InputGroup](?path=/docs/components-inputgroup--docs) to show a currency or\nunit affix.\n\nDo not use it for a phone number, which is formatted by `InputTel`, nor for\nfree text with some digits in it, which is a plain `Input`. It is not a\n`type=\"number\"` input on purpose: that one drops leading zeros and cannot\nshow separators, which identifiers and amounts both need.\n\nIt renders from `value`, not from the DOM, so bind it to a form through a\ncontrolled value (react-hook-form's `Controller` or `useController`), not\nthrough `register`: a value `register` writes into the `<input>`, such as a\ndefault or a `reset`, is wiped on the next render.",
6698
+ "jsDocTags": {
6699
+ "summary": [
6700
+ "Numeric input formatting as you type, built on react-number-format"
6701
+ ],
6702
+ "dataAttribute": [
6703
+ "{string} data-slot - Always set to \"input-number\""
6704
+ ],
6705
+ "example": [
6706
+ "<FieldText>\n <Label>Annual premium</Label>\n <InputNumber name=\"premium\" thousandSeparator allowLeadingZeros={false} />\n</FieldText>"
6707
+ ]
6708
+ },
6709
+ "props": {
6710
+ "size": {
6711
+ "name": "size",
6712
+ "required": false,
6713
+ "type": {
6714
+ "name": "enum",
6715
+ "raw": "\"sm\" | \"md\" | \"lg\"",
6716
+ "value": [
6717
+ {
6718
+ "value": "\"sm\""
6719
+ },
6720
+ {
6721
+ "value": "\"md\""
6722
+ },
6723
+ {
6724
+ "value": "\"lg\""
6725
+ }
6726
+ ]
6727
+ },
6728
+ "description": "Control height. Defaults to `'md'`.\n- `sm` (32px) — dense layouts where vertical space is tight.\n- `md` (40px) — standard form density.\n- `lg` (48px) — low-density forms and larger touch targets. Also raises\n the text to `base` and the corner radius to `lg`.",
6729
+ "defaultValue": null,
6730
+ "parent": {
6731
+ "name": "InputProps",
6732
+ "fileName": "src/input/input.tsx"
6733
+ },
6734
+ "declarations": [
6735
+ {
6736
+ "name": "TypeLiteral",
6737
+ "fileName": "src/input/input.tsx"
6738
+ }
6739
+ ]
6740
+ },
6741
+ "maxNumber": {
6742
+ "name": "maxNumber",
6743
+ "required": false,
6744
+ "type": {
6745
+ "name": "number"
6746
+ },
6747
+ "description": "Highest value the field accepts. A keystroke or paste that would take\nthe number above it is dropped before it reaches the field, so `onChange`\nis not called for it. No limit by default.",
6748
+ "defaultValue": null,
6749
+ "parent": {
6750
+ "name": "InputNumberProps",
6751
+ "fileName": "src/input-number/input-number.tsx"
6752
+ },
6753
+ "declarations": [
6754
+ {
6755
+ "name": "TypeLiteral",
6756
+ "fileName": "src/input-number/input-number.tsx"
6757
+ }
6758
+ ]
6759
+ },
6760
+ "thousandSeparator": {
6761
+ "name": "thousandSeparator",
6762
+ "required": false,
6763
+ "type": {
6764
+ "name": "boolean"
6765
+ },
6766
+ "description": "Groups the integer part in thousands with a comma as the user types\n(`1,234,567`). The separators are part of `event.target.value`. Defaults\nto `false`.",
6767
+ "defaultValue": {
6768
+ "value": "false"
6769
+ },
6770
+ "parent": {
6771
+ "name": "InputNumberProps",
6772
+ "fileName": "src/input-number/input-number.tsx"
6773
+ },
6774
+ "declarations": [
6775
+ {
6776
+ "name": "TypeLiteral",
6777
+ "fileName": "src/input-number/input-number.tsx"
6778
+ }
6779
+ ]
6780
+ },
6781
+ "allowLeadingZeros": {
6782
+ "name": "allowLeadingZeros",
6783
+ "required": false,
6784
+ "type": {
6785
+ "name": "boolean"
6786
+ },
6787
+ "description": "Keeps zeros typed in front of the number (`007`), which an identifier\nsuch as an NPN or a ZIP code needs. Set it to `false` for an amount, so\n`007` becomes `7` once the field loses focus, and `onChange` fires with\nthe trimmed value. Defaults to `true`.",
6788
+ "defaultValue": {
6789
+ "value": "true"
6790
+ },
6791
+ "parent": {
6792
+ "name": "InputNumberProps",
6793
+ "fileName": "src/input-number/input-number.tsx"
6794
+ },
6795
+ "declarations": [
6796
+ {
6797
+ "name": "TypeLiteral",
6798
+ "fileName": "src/input-number/input-number.tsx"
6799
+ }
6800
+ ]
6801
+ },
6802
+ "type": {
6803
+ "name": "type",
6804
+ "required": false,
6805
+ "type": {
6806
+ "name": "enum",
6807
+ "raw": "\"text\" | \"tel\" | \"password\"",
6808
+ "value": [
6809
+ {
6810
+ "value": "\"text\""
6811
+ },
6812
+ {
6813
+ "value": "\"tel\""
6814
+ },
6815
+ {
6816
+ "value": "\"password\""
6817
+ }
6818
+ ]
6819
+ },
6820
+ "description": "The underlying `<input>` type. Defaults to `'text'`.\n- `text` — the general case.\n- `tel` — brings up the numeric keypad on phones, for codes and\n identifiers that are digits only.\n- `password` — masks the digits, for a PIN.",
6821
+ "defaultValue": null,
6822
+ "parent": {
6823
+ "name": "InputNumberProps",
6824
+ "fileName": "src/input-number/input-number.tsx"
6825
+ },
6826
+ "declarations": [
6827
+ {
6828
+ "name": "TypeLiteral",
6829
+ "fileName": "src/input-number/input-number.tsx"
6830
+ }
6831
+ ]
6832
+ },
6833
+ "value": {
6834
+ "name": "value",
6835
+ "required": false,
6836
+ "type": {
6837
+ "name": "string | number | null | undefined"
6838
+ },
6839
+ "description": "The number to show, as a number or as a string of digits. It is displayed\nformatted according to `thousandSeparator`. `null` and `''` both clear the\nfield.",
6840
+ "defaultValue": null,
6841
+ "parent": {
6842
+ "name": "InputNumberProps",
6843
+ "fileName": "src/input-number/input-number.tsx"
6844
+ },
6845
+ "declarations": [
6846
+ {
6847
+ "name": "TypeLiteral",
6848
+ "fileName": "src/input-number/input-number.tsx"
6849
+ }
6850
+ ]
6851
+ },
6852
+ "defaultValue": {
6853
+ "name": "defaultValue",
6854
+ "required": false,
6855
+ "type": {
6856
+ "name": "string | number | null | undefined"
6857
+ },
6858
+ "description": "Initial value of an uncontrolled field, in the same forms as `value`.",
6859
+ "defaultValue": null,
6860
+ "parent": {
6861
+ "name": "InputNumberProps",
6862
+ "fileName": "src/input-number/input-number.tsx"
6863
+ },
6864
+ "declarations": [
6865
+ {
6866
+ "name": "TypeLiteral",
6867
+ "fileName": "src/input-number/input-number.tsx"
6868
+ }
6869
+ ]
6870
+ }
6871
+ }
6872
+ },
6873
+ "stories": [
6874
+ {
6875
+ "id": "components-inputnumber--default",
6876
+ "name": "Default",
6877
+ "snippet": "const Default = () => <InputNumber\n aria-label=\"Premium\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n ref={inputRef} />;",
6878
+ "description": "Only digits and a decimal point make it into the field: letters and a minus\nsign are ignored as they are typed. `onChange` reports the text as it is shown, and the `ref`\npoints at the `<input>` element.",
6879
+ "summary": "Numeric field that ignores letters and signs"
6880
+ },
6881
+ {
6882
+ "id": "components-inputnumber--type-tel",
6883
+ "name": "Type Tel",
6884
+ "snippet": "const TypeTel = () => <InputNumber\n aria-label=\"NPN\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n type=\"tel\" />;",
6885
+ "description": "`type=\"tel\"` brings up the numeric keypad on phones. The field still\nformats and filters the same way; only the underlying input type changes.",
6886
+ "summary": "Numeric keypad on phones through the tel input type"
6887
+ },
6888
+ {
6889
+ "id": "components-inputnumber--thousand-separator",
6890
+ "name": "Thousand Separator",
6891
+ "snippet": "const ThousandSeparator = () => <InputNumber\n aria-label=\"Premium\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n thousandSeparator />;",
6892
+ "description": "With `thousandSeparator` the integer part is grouped with commas as the\nuser types, and the commas are part of what `onChange` reports.",
6893
+ "summary": "Thousands grouped with commas as the user types"
6894
+ },
6895
+ {
6896
+ "id": "components-inputnumber--max-number",
6897
+ "name": "Max Number",
6898
+ "snippet": "const MaxNumber = () => <InputNumber\n aria-label=\"Commission rate\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n maxNumber={100} />;",
6899
+ "description": "`maxNumber` caps the value: a keystroke that would push the number above\nit is dropped, so the field keeps its previous value and `onChange` is not\ncalled for it.",
6900
+ "summary": "Keystrokes that would exceed a maximum are dropped"
6901
+ },
6902
+ {
6903
+ "id": "components-inputnumber--leading-zeros",
6904
+ "name": "Leading Zeros",
6905
+ "snippet": "const LeadingZeros = () => <div style={{ display: 'grid', gap: '1rem' }}>\n <InputNumber\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n aria-label=\"ZIP code\" />\n <InputNumber\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n aria-label=\"Amount\"\n allowLeadingZeros={false} />\n</div>;",
6906
+ "description": "Leading zeros are kept by default, as an NPN or a ZIP code needs. With\n`allowLeadingZeros` off they are dropped once the field loses focus, which\nis what an amount wants, and `onChange` reports the trimmed value.",
6907
+ "summary": "Leading zeros kept by default, dropped on blur when turned off"
6908
+ },
6909
+ {
6910
+ "id": "components-inputnumber--controlled",
6911
+ "name": "Controlled",
6912
+ "snippet": "const Controlled = () => {\n const [premium, setPremium] = useState<number | string>(1234);\n\n return (\n <InputNumber\n aria-label=\"Premium\"\n thousandSeparator\n value={premium}\n onChange={event => {\n setPremium(event.target.value);\n args.onChange?.(event);\n }} />\n );\n};",
6913
+ "description": "A controlled field takes `value` as a number and shows it formatted. The\nstate keeps what `onChange` reports, so it holds the formatted text.",
6914
+ "summary": "Controlled field showing a numeric value formatted"
6915
+ },
6916
+ {
6917
+ "id": "components-inputnumber--inside-input-group",
6918
+ "name": "Inside Input Group",
6919
+ "snippet": "const InsideInputGroup = () => <InputGroup.Root>\n <InputGroup.Text>$</InputGroup.Text>\n <InputNumber\n aria-label=\"Premium\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n thousandSeparator\n allowLeadingZeros={false}\n placeholder=\"0\" />\n</InputGroup.Root>;",
6920
+ "description": "Inside an `InputGroup` the field sits behind a currency affix and the\nframe takes its focus, disabled and invalid states from the input.",
6921
+ "summary": "Amount with a currency prefix inside an input group"
6922
+ },
6923
+ {
6924
+ "id": "components-inputnumber--inside-field-text",
6925
+ "name": "Inside Field Text",
6926
+ "snippet": "const InsideFieldText = () => (\n <div style={{ display: 'grid', gap: '1.5rem' }}>\n <FieldText required>\n <Label>NPN</Label>\n <InputNumber type=\"tel\" />\n <Field.Description>The 7 to 10 digit National Producer Number.</Field.Description>\n </FieldText>\n <FieldText invalid>\n <Label>Years in business</Label>\n <InputNumber defaultValue={150} />\n <Field.Error errors={[{ message: 'Enter at most 100 years.' }]} />\n </FieldText>\n <FieldText disabled>\n <Label>Agency code</Label>\n <InputNumber defaultValue=\"00412\" />\n </FieldText>\n </div>\n);",
6927
+ "description": "Inside a `FieldText` the input takes its label, description and states from\nthe field. `invalid` on the field sets `aria-invalid`, which paints the\ndestructive border, and `disabled` reaches the input the same way.",
6928
+ "summary": "Label, messages and states reaching the input from a FieldText"
6929
+ },
6930
+ {
6931
+ "id": "components-inputnumber--disabled",
6932
+ "name": "Disabled",
6933
+ "snippet": "const Disabled = () => <InputNumber\n aria-label=\"Premium\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n disabled\n thousandSeparator\n defaultValue={12500} />;",
6934
+ "description": "A disabled field keeps its formatted value and takes no input.",
6935
+ "summary": "Disabled field with a formatted value"
6936
+ },
6937
+ {
6938
+ "id": "components-inputnumber--invalid",
6939
+ "name": "Invalid",
6940
+ "snippet": "const Invalid = () => <InputNumber\n aria-label=\"Premium\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n aria-invalid\n defaultValue={0} />;",
6941
+ "description": "`aria-invalid` paints the destructive border, the same way it does on a\nplain `Input`.",
6942
+ "summary": "Invalid state driven by aria-invalid"
6943
+ },
6944
+ {
6945
+ "id": "components-inputnumber--all-sizes",
6946
+ "name": "All Sizes",
6947
+ "snippet": "const AllSizes = () => <div style={{ display: 'grid', gap: '1rem' }}>\n <InputNumber\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n thousandSeparator\n defaultValue={1234}\n size=\"sm\"\n aria-label=\"Small\" />\n <InputNumber\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n thousandSeparator\n defaultValue={1234}\n size=\"md\"\n aria-label=\"Medium\" />\n <InputNumber\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n thousandSeparator\n defaultValue={1234}\n size=\"lg\"\n aria-label=\"Large\" />\n</div>;",
6948
+ "description": "The three sizes match Input's: 32, 40 or 48px tall.",
6949
+ "summary": "The three sizes side by side"
6950
+ }
6951
+ ],
6952
+ "import": "import { Controller, Field, FieldText, InputContext, InputGroup, InputNumber, Label } from \"@agentero/design-system\";"
6953
+ },
6676
6954
  "components-inputpassword": {
6677
6955
  "id": "components-inputpassword",
6678
6956
  "name": "InputPassword",
@@ -6779,6 +7057,174 @@
6779
7057
  ],
6780
7058
  "import": "import { Field, FieldText, InputPassword, Label } from \"@agentero/design-system\";"
6781
7059
  },
7060
+ "components-debouncedinputsearch": {
7061
+ "id": "components-debouncedinputsearch",
7062
+ "name": "DebouncedInputSearch",
7063
+ "path": "./src/input-search/debounced-input-search.stories.tsx",
7064
+ "jsDocTags": {
7065
+ "summary": [
7066
+ "Search input that reports the query once the user pauses typing"
7067
+ ],
7068
+ "dataAttribute": [
7069
+ "{string} data-slot - Always set to \"input-search\" on the frame"
7070
+ ],
7071
+ "example": [
7072
+ "<DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={filters.query}\n onChange={event => setFilters({ ...filters, query: event.target.value })}\n isLoading={isFetching}\n/>"
7073
+ ]
7074
+ },
7075
+ "description": "DebouncedInputSearch is an `InputSearch` for filters that are expensive to\nrun. Keystrokes show up at once, but `onChange` fires only once the user\npauses for 300ms, with the last change event. It is always controlled:\n`value` and `onChange` are required, and the field follows a `value`\nchanged from outside.",
7076
+ "summary": "Search input that reports the query once the user pauses typing",
7077
+ "reactComponentMeta": {
7078
+ "displayName": "DebouncedInputSearch",
7079
+ "exportName": "DebouncedInputSearch",
7080
+ "filePath": "/home/runner/work/design-system/design-system/src/input-search/debounced-input-search.tsx",
7081
+ "description": "DebouncedInputSearch is an [InputSearch](?path=/docs/components-inputsearch--docs)\nfor filters that are expensive to run: a table query, a request to the\nserver. Keystrokes show up in the field at once, but `onChange` fires only\nonce the user pauses for `DEBOUNCE_TIME` (300ms), with the last change\nevent, so the page runs one search per pause instead of one per key. It is\nalways controlled: `value` and `onChange` are required, the page owns the\nquery, and the field follows a `value` changed from outside while the user\nis not typing.\n\nEvery other prop goes to the InputSearch and from there to the `<input>`:\nthe `ref`, `size`, `isLoading`, `placeholder`, `aria-*` and every other\nstandard attribute. Only `type` is not accepted. `className` styles the\nframe.\n\nDo not use it where every keystroke matters, such as filtering a short list\nalready in memory; that is a plain\n[InputSearch](?path=/docs/components-inputsearch--docs). To pick a value from\nthe results, use [Combobox](?path=/docs/components-combobox--docs).",
7082
+ "jsDocTags": {
7083
+ "summary": [
7084
+ "Search input that reports the query once the user pauses typing"
7085
+ ],
7086
+ "dataAttribute": [
7087
+ "{string} data-slot - Always set to \"input-search\" on the frame"
7088
+ ],
7089
+ "example": [
7090
+ "<DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={filters.query}\n onChange={event => setFilters({ ...filters, query: event.target.value })}\n isLoading={isFetching}\n/>"
7091
+ ]
7092
+ },
7093
+ "props": {
7094
+ "size": {
7095
+ "name": "size",
7096
+ "required": false,
7097
+ "type": {
7098
+ "name": "enum",
7099
+ "raw": "\"sm\" | \"md\" | \"lg\"",
7100
+ "value": [
7101
+ {
7102
+ "value": "\"sm\""
7103
+ },
7104
+ {
7105
+ "value": "\"md\""
7106
+ },
7107
+ {
7108
+ "value": "\"lg\""
7109
+ }
7110
+ ]
7111
+ },
7112
+ "description": "Control height. Defaults to `'md'`.\n- `sm` (32px) — dense layouts where vertical space is tight.\n- `md` (40px) — standard form density.\n- `lg` (48px) — low-density forms and larger touch targets. Also raises\n the text to `base` and the corner radius to `lg`.",
7113
+ "defaultValue": null,
7114
+ "parent": {
7115
+ "name": "InputProps",
7116
+ "fileName": "src/input/input.tsx"
7117
+ },
7118
+ "declarations": [
7119
+ {
7120
+ "name": "TypeLiteral",
7121
+ "fileName": "src/input/input.tsx"
7122
+ }
7123
+ ]
7124
+ },
7125
+ "clearLabel": {
7126
+ "name": "clearLabel",
7127
+ "required": false,
7128
+ "type": {
7129
+ "name": "string"
7130
+ },
7131
+ "description": "Accessible name of the clear button. Defaults to `'Clear search'`; name it\nafter what is searched (`'Clear agency search'`) when a page carries\nseveral search fields.",
7132
+ "defaultValue": null,
7133
+ "parent": {
7134
+ "name": "InputSearchProps",
7135
+ "fileName": "src/input-search/input-search.tsx"
7136
+ },
7137
+ "declarations": [
7138
+ {
7139
+ "name": "TypeLiteral",
7140
+ "fileName": "src/input-search/input-search.tsx"
7141
+ }
7142
+ ]
7143
+ },
7144
+ "isLoading": {
7145
+ "name": "isLoading",
7146
+ "required": false,
7147
+ "type": {
7148
+ "name": "boolean"
7149
+ },
7150
+ "description": "Shows a spinner at the end of the frame, in place of the clear button,\nwhile the results for the current text are on their way. Defaults to\n`false`.",
7151
+ "defaultValue": null,
7152
+ "parent": {
7153
+ "name": "InputSearchProps",
7154
+ "fileName": "src/input-search/input-search.tsx"
7155
+ },
7156
+ "declarations": [
7157
+ {
7158
+ "name": "TypeLiteral",
7159
+ "fileName": "src/input-search/input-search.tsx"
7160
+ }
7161
+ ]
7162
+ },
7163
+ "value": {
7164
+ "name": "value",
7165
+ "required": true,
7166
+ "type": {
7167
+ "name": "string"
7168
+ },
7169
+ "description": "The query the page is filtering by. The field shows it, and follows it\nwhen it changes from outside (a reset button, a saved filter, navigation),\nunless the user is in the middle of typing: then what they typed stays,\nand `onChange` reports it once they pause.",
7170
+ "defaultValue": null,
7171
+ "parent": {
7172
+ "name": "DebouncedInputSearchProps",
7173
+ "fileName": "src/input-search/debounced-input-search.tsx"
7174
+ },
7175
+ "declarations": [
7176
+ {
7177
+ "name": "TypeLiteral",
7178
+ "fileName": "src/input-search/debounced-input-search.tsx"
7179
+ }
7180
+ ]
7181
+ },
7182
+ "onChange": {
7183
+ "name": "onChange",
7184
+ "required": true,
7185
+ "type": {
7186
+ "name": "ChangeEventHandler<HTMLInputElement>"
7187
+ },
7188
+ "description": "Fires once the user has stopped typing for `DEBOUNCE_TIME` milliseconds,\nwith the change event of the last keystroke; clearing the field goes\nthrough the same delay. A pending call fires at once when the user\npresses Enter, and is dropped when the field unmounts, so a page that is\nnavigating away is not sent back. Read the text from `event.target.value`, which is\nwhat the field shows at that moment. `event.currentTarget` is no longer\nset by then, since the event is delivered after the browser's dispatch.",
7189
+ "defaultValue": null,
7190
+ "parent": {
7191
+ "name": "DebouncedInputSearchProps",
7192
+ "fileName": "src/input-search/debounced-input-search.tsx"
7193
+ },
7194
+ "declarations": [
7195
+ {
7196
+ "name": "TypeLiteral",
7197
+ "fileName": "src/input-search/debounced-input-search.tsx"
7198
+ }
7199
+ ]
7200
+ }
7201
+ }
7202
+ },
7203
+ "stories": [
7204
+ {
7205
+ "id": "components-debouncedinputsearch--default",
7206
+ "name": "Default",
7207
+ "snippet": "const Default = () => {\n const [query, setQuery] = useState(args.value);\n\n return (\n <div style={{ display: 'grid', gap: '1rem' }}>\n <DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={query}\n onChange={event => {\n setQuery(event.target.value);\n args.onChange(event);\n }} />\n <output>Filtering by: {query}</output>\n </div>\n );\n};",
7208
+ "description": "A page filtering by a query it owns, shown below the field. Typing shows up\nin the field at once; `onChange` arrives once the user pauses, with the\nwhole text in `event.target.value`, and only then does the query below\nchange. Clearing the field goes through the same delay.",
7209
+ "summary": "Controlled query reported once the user stops typing"
7210
+ },
7211
+ {
7212
+ "id": "components-debouncedinputsearch--external-value",
7213
+ "name": "External Value",
7214
+ "snippet": "const ExternalValue = () => {\n const [query, setQuery] = useState('Acme');\n\n return (\n <div style={{ display: 'grid', gap: '1rem' }}>\n <DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={query}\n onChange={event => {\n setQuery(event.target.value);\n args.onChange(event);\n }} />\n <Button variant=\"secondary\" size=\"sm\" onClick={() => setQuery('Travelers')}>Apply saved search\n </Button>\n <output>Filtering by: {query}</output>\n </div>\n );\n};",
7215
+ "description": "The field follows `value` when the page changes it from outside, here a\nsaved search applied with a button: nothing is being typed, so the new\nquery replaces the text.",
7216
+ "summary": "Field following a value changed from outside while idle"
7217
+ },
7218
+ {
7219
+ "id": "components-debouncedinputsearch--submit-on-enter",
7220
+ "name": "Submit On Enter",
7221
+ "snippet": "const SubmitOnEnter = () => {\n const [query, setQuery] = useState(args.value);\n\n return (\n <div style={{ display: 'grid', gap: '1rem' }}>\n <DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={query}\n onChange={event => {\n setQuery(event.target.value);\n args.onChange(event);\n }} />\n <output>Filtering by: {query}</output>\n </div>\n );\n};",
7222
+ "description": "Pressing Enter reports the text at once instead of waiting for the pause.",
7223
+ "summary": "Enter reports the pending query without waiting"
7224
+ }
7225
+ ],
7226
+ "import": "import { Button, DebouncedInputSearch } from \"@agentero/design-system\";"
7227
+ },
6782
7228
  "components-inputsearch": {
6783
7229
  "id": "components-inputsearch",
6784
7230
  "name": "InputSearch",
@@ -10055,6 +10501,6 @@
10055
10501
  },
10056
10502
  "meta": {
10057
10503
  "docgen": "react-component-meta",
10058
- "durationMs": 4990
10504
+ "durationMs": 5136
10059
10505
  }
10060
10506
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentero/design-system",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "A React component library built with Tailwind CSS v4 and Radix UI primitives",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -97,6 +97,10 @@
97
97
  "types": "./src/input-group/index.d.ts",
98
98
  "import": "./src/input-group/index.js"
99
99
  },
100
+ "./input-number": {
101
+ "types": "./src/input-number/index.d.ts",
102
+ "import": "./src/input-number/index.js"
103
+ },
100
104
  "./input-password": {
101
105
  "types": "./src/input-password/index.d.ts",
102
106
  "import": "./src/input-password/index.js"
@@ -214,8 +218,10 @@
214
218
  "@radix-ui/react-tooltip": "^1.2.11",
215
219
  "clsx": "^2.1.1",
216
220
  "cmdk": "^1.1.1",
221
+ "react-number-format": "5.4.5",
217
222
  "sonner": "^2.0.7",
218
223
  "tailwind-merge": "^3.5.0",
219
- "tailwind-variants": "^3.2.2"
224
+ "tailwind-variants": "^3.2.2",
225
+ "use-debounce": "^10.1.1"
220
226
  }
221
227
  }
@@ -6,12 +6,21 @@ import { InputProps } from './input';
6
6
  * and `ref`. An `Input` with no provider above it reads `null` and uses only
7
7
  * its own props.
8
8
  */
9
- export declare const InputContext: import('react').Context<Partial<InputProps> | null>;
9
+ export type InputContextValue = Pick<InputProps, 'id' | 'aria-describedby' | 'aria-invalid' | 'required' | 'disabled' | 'readOnly' | 'name' | 'onChange' | 'onBlur' | 'ref'> & {
10
+ value?: string | number;
11
+ };
12
+ export declare const InputContext: import('react').Context<InputContextValue | null>;
10
13
  /**
11
14
  * Merges the surrounding `InputContext`, if any, under the input's own props.
12
15
  * Own props win; handlers present on both sides are chained and
13
16
  * `aria-describedby` ids are concatenated. In development it warns when the
14
17
  * input carries an `id` that differs from the one the container provides,
15
18
  * since the label would then point elsewhere.
19
+ *
20
+ * Generic over the props so a wrapper whose props narrow or widen the
21
+ * input's (a `type` limited to a few values, a `value` that takes `null`)
22
+ * gets its own type back, with the context's wiring merged in. The props must
23
+ * accept everything the context can carry; when they do not, `props` resolves
24
+ * to `never` and the call does not compile.
16
25
  */
17
- export declare const useInputContext: (props: InputProps) => InputProps;
26
+ export declare const useInputContext: <P extends Pick<InputProps, "id">>(props: P & (InputContextValue extends Partial<P> ? unknown : never)) => P;
@@ -1,3 +1,4 @@
1
1
  export { InputContext, useInputContext } from './context';
2
+ export type { InputContextValue } from './context';
2
3
  export { Input, inputRecipe } from './input';
3
4
  export type { InputProps, InputSize, InputVariants } from './input';
@@ -0,0 +1,2 @@
1
+ export { InputNumber } from './input-number';
2
+ export type { InputNumberProps } from './input-number';
@@ -0,0 +1,2 @@
1
+ import { InputNumber as e } from "./input-number.js";
2
+ export { e as InputNumber };
@@ -0,0 +1,82 @@
1
+ import { InputProps } from '../input';
2
+ export type InputNumberProps = Omit<InputProps, 'type' | 'value' | 'defaultValue'> & {
3
+ /**
4
+ * Highest value the field accepts. A keystroke or paste that would take
5
+ * the number above it is dropped before it reaches the field, so `onChange`
6
+ * is not called for it. No limit by default.
7
+ */
8
+ maxNumber?: number;
9
+ /**
10
+ * Groups the integer part in thousands with a comma as the user types
11
+ * (`1,234,567`). The separators are part of `event.target.value`. Defaults
12
+ * to `false`.
13
+ */
14
+ thousandSeparator?: boolean;
15
+ /**
16
+ * Keeps zeros typed in front of the number (`007`), which an identifier
17
+ * such as an NPN or a ZIP code needs. Set it to `false` for an amount, so
18
+ * `007` becomes `7` once the field loses focus, and `onChange` fires with
19
+ * the trimmed value. Defaults to `true`.
20
+ */
21
+ allowLeadingZeros?: boolean;
22
+ /**
23
+ * The underlying `<input>` type. Defaults to `'text'`.
24
+ * - `text` — the general case.
25
+ * - `tel` — brings up the numeric keypad on phones, for codes and
26
+ * identifiers that are digits only.
27
+ * - `password` — masks the digits, for a PIN.
28
+ */
29
+ type?: 'password' | 'tel' | 'text';
30
+ /**
31
+ * The number to show, as a number or as a string of digits. It is displayed
32
+ * formatted according to `thousandSeparator`. `null` and `''` both clear the
33
+ * field.
34
+ */
35
+ value?: number | string | null;
36
+ /** Initial value of an uncontrolled field, in the same forms as `value`. */
37
+ defaultValue?: number | string | null;
38
+ };
39
+ /**
40
+ * InputNumber is a text field that only takes digits and a decimal point and
41
+ * formats them as the user types: a thousands separator when
42
+ * `thousandSeparator` is set, leading zeros kept unless `allowLeadingZeros` is
43
+ * off, no sign, no decimal rounding.
44
+ * Anything that is not a digit or the decimal point is ignored, and
45
+ * `maxNumber` drops any edit that would exceed it. `onChange` fires with the
46
+ * formatted text in `event.target.value` (`"1,234"`, not `1234`), which is
47
+ * what a form library stores; parse it where the number is consumed.
48
+ *
49
+ * It is an [Input](?path=/docs/components-input--docs) driven by
50
+ * `react-number-format`, so it keeps the caret in place while it reformats
51
+ * and takes its wiring from a surrounding `FieldText` as a plain `Input`
52
+ * does. Every standard `<input>` attribute is accepted and forwarded, such as
53
+ * `placeholder`, `required`, `maxLength` or `aria-invalid`; `type` is limited
54
+ * to `text`, `tel` and `password`, and `value` and `defaultValue` also take a
55
+ * number or `null`. The invalid state comes from `aria-invalid`, which paints
56
+ * the destructive border. Place it inside an
57
+ * [InputGroup](?path=/docs/components-inputgroup--docs) to show a currency or
58
+ * unit affix.
59
+ *
60
+ * Do not use it for a phone number, which is formatted by `InputTel`, nor for
61
+ * free text with some digits in it, which is a plain `Input`. It is not a
62
+ * `type="number"` input on purpose: that one drops leading zeros and cannot
63
+ * show separators, which identifiers and amounts both need.
64
+ *
65
+ * It renders from `value`, not from the DOM, so bind it to a form through a
66
+ * controlled value (react-hook-form's `Controller` or `useController`), not
67
+ * through `register`: a value `register` writes into the `<input>`, such as a
68
+ * default or a `reset`, is wiped on the next render.
69
+ *
70
+ * @summary Numeric input formatting as you type, built on react-number-format
71
+ * @dataAttribute {string} data-slot - Always set to "input-number"
72
+ *
73
+ * @example
74
+ * <FieldText>
75
+ * <Label>Annual premium</Label>
76
+ * <InputNumber name="premium" thousandSeparator allowLeadingZeros={false} />
77
+ * </FieldText>
78
+ */
79
+ export declare const InputNumber: {
80
+ (props: InputNumberProps): import("react/jsx-runtime").JSX.Element;
81
+ displayName: string;
82
+ };