@robr0/design-system 0.8.0 → 0.9.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.
- package/README.md +3 -3
- package/components/Accordion/Accordion.js +1 -0
- package/components/AgentPlan/AgentPlan.css +2 -2
- package/components/AgentPlan/AgentPlan.js +1 -0
- package/components/AgentStatus/AgentStatus.js +1 -0
- package/components/AiButton/AiButton.js +1 -0
- package/components/AlertDialog/AlertDialog.js +1 -0
- package/components/AnchorNav/AnchorNav.css +63 -0
- package/components/AnchorNav/AnchorNav.d.ts +33 -0
- package/components/AnchorNav/AnchorNav.js +82 -0
- package/components/AppLayout/AppLayout.js +1 -0
- package/components/AppSidebar/AppSidebar.js +1 -0
- package/components/Avatar/Avatar.js +1 -0
- package/components/Button/Button.js +1 -0
- package/components/Card/Card.js +1 -0
- package/components/Carousel/Carousel.js +1 -0
- package/components/ChatThread/ChatThread.js +1 -0
- package/components/Checkbox/Checkbox.js +1 -0
- package/components/CodeBlock/CodeBlock.js +1 -0
- package/components/CodeDiff/CodeDiff.css +175 -0
- package/components/CodeDiff/CodeDiff.d.ts +58 -0
- package/components/CodeDiff/CodeDiff.js +114 -0
- package/components/ColorPicker/ColorPicker.js +1 -0
- package/components/Combobox/Combobox.js +1 -0
- package/components/CommandPalette/CommandPalette.js +1 -0
- package/components/Composer/Composer.js +1 -0
- package/components/ContactCard/ContactCard.js +1 -0
- package/components/ContextMenu/ContextMenu.js +1 -0
- package/components/DataTable/DataTable.js +1 -0
- package/components/DateInput/DateInput.js +1 -0
- package/components/DatePicker/DatePicker.js +1 -0
- package/components/Dialog/Dialog.js +1 -0
- package/components/Drawer/Drawer.js +1 -0
- package/components/Dropdown/Dropdown.js +1 -0
- package/components/DropdownMenu/DropdownMenu.css +1 -1
- package/components/DropdownMenu/DropdownMenu.js +1 -0
- package/components/EventCalendar/EventCalendar.js +1 -0
- package/components/Field/Field.js +1 -0
- package/components/Field/FieldContext.js +1 -0
- package/components/FileInput/FileInput.js +1 -0
- package/components/Input/Input.js +1 -0
- package/components/InterruptCard/InterruptCard.css +11 -21
- package/components/InterruptCard/InterruptCard.d.ts +5 -6
- package/components/MessageCard/MessageCard.css +1 -2
- package/components/ModelPicker/ModelPicker.css +3 -1
- package/components/ModelPicker/ModelPicker.js +1 -0
- package/components/NavList/NavList.js +1 -0
- package/components/NotificationCenter/NotificationCenter.js +1 -0
- package/components/NumberInput/NumberInput.css +154 -0
- package/components/NumberInput/NumberInput.d.ts +44 -0
- package/components/NumberInput/NumberInput.js +147 -0
- package/components/Pagination/Pagination.js +1 -0
- package/components/PinInput/PinInput.css +92 -0
- package/components/PinInput/PinInput.d.ts +46 -0
- package/components/PinInput/PinInput.js +161 -0
- package/components/Popover/Popover.css +5 -6
- package/components/Popover/Popover.js +1 -0
- package/components/RadioButton/RadioButton.js +1 -0
- package/components/Reasoning/Reasoning.js +1 -0
- package/components/SegmentedControl/SegmentedControl.js +1 -0
- package/components/SelectionCard/SelectionCard.js +1 -0
- package/components/ShaderField/ShaderField.js +1 -0
- package/components/ShaderField/useShaderField.js +1 -0
- package/components/Slider/Slider.js +1 -0
- package/components/Sparkline/Sparkline.css +52 -0
- package/components/Sparkline/Sparkline.d.ts +49 -0
- package/components/Sparkline/Sparkline.js +86 -0
- package/components/Stepper/Stepper.css +207 -0
- package/components/Stepper/Stepper.d.ts +41 -0
- package/components/Stepper/Stepper.js +61 -0
- package/components/Tabs/Tabs.js +1 -0
- package/components/TagInput/TagInput.css +160 -0
- package/components/TagInput/TagInput.d.ts +43 -0
- package/components/TagInput/TagInput.js +137 -0
- package/components/Textarea/Textarea.js +1 -0
- package/components/TimePicker/TimePicker.css +230 -0
- package/components/TimePicker/TimePicker.d.ts +46 -0
- package/components/TimePicker/TimePicker.js +246 -0
- package/components/Toast/Toast.js +1 -0
- package/components/ToggleGroup/ToggleGroup.js +1 -0
- package/components/ToggleSwitch/ToggleSwitch.js +1 -0
- package/components/ToolCall/ToolCall.css +7 -11
- package/components/ToolCall/ToolCall.js +1 -0
- package/components/Tooltip/Tooltip.js +1 -0
- package/components/TreeView/TreeView.css +119 -0
- package/components/TreeView/TreeView.d.ts +45 -0
- package/components/TreeView/TreeView.js +232 -0
- package/components/registry.d.ts +22 -6
- package/components/registry.js +3 -1
- package/components/registry.json +206 -20
- package/components/registry.json.d.ts +206 -20
- package/components/registry.json.js +2 -2
- package/index.d.ts +11 -2
- package/index.js +21 -1
- package/package.json +1 -1
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
left: 0;
|
|
38
38
|
z-index: 50;
|
|
39
39
|
min-width: 200px;
|
|
40
|
-
margin-top: var(--gap-xxs); /*
|
|
40
|
+
margin-top: var(--gap-xxs); /* 2px offset from trigger */
|
|
41
41
|
|
|
42
42
|
/* Appearance — Figma: page/primary bg, input/border/primary, radius/md */
|
|
43
43
|
background: var(--color-bg-page-primary);
|
|
@@ -4,33 +4,25 @@
|
|
|
4
4
|
from the agent, a detail slot, and option
|
|
5
5
|
buttons in a footer.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
card drops to monochrome.
|
|
7
|
+
The card is deliberately quiet — one tone,
|
|
8
|
+
the standard container border, no status
|
|
9
|
+
colour. The question and its buttons are
|
|
10
|
+
the signal; the visually hidden status
|
|
11
|
+
announcement carries the waiting state.
|
|
13
12
|
============================================ */
|
|
14
13
|
|
|
15
14
|
.ds-interrupt-card {
|
|
16
|
-
--ds-interrupt-card-color: var(--color-
|
|
17
|
-
--ds-interrupt-card-border: var(--color-status-warning-border);
|
|
15
|
+
--ds-interrupt-card-color: var(--color-icon-secondary);
|
|
18
16
|
|
|
19
17
|
display: flex;
|
|
20
18
|
flex-direction: column;
|
|
21
19
|
width: 100%;
|
|
22
20
|
background-color: var(--color-bg-container-primary);
|
|
23
|
-
border: var(--border-xs) solid var(--
|
|
21
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
24
22
|
border-radius: var(--radius-md);
|
|
25
23
|
overflow: hidden;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
/* Decision made — no signal needed */
|
|
29
|
-
.ds-interrupt-card--answered {
|
|
30
|
-
--ds-interrupt-card-color: var(--color-text-secondary);
|
|
31
|
-
--ds-interrupt-card-border: var(--color-bg-container-border);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
/* ============================================
|
|
35
27
|
HEADER
|
|
36
28
|
============================================ */
|
|
@@ -39,7 +31,7 @@
|
|
|
39
31
|
display: flex;
|
|
40
32
|
align-items: flex-start;
|
|
41
33
|
gap: var(--gap-sm);
|
|
42
|
-
padding: var(--padding-sm) var(--padding-
|
|
34
|
+
padding: var(--padding-sm-md) var(--padding-md);
|
|
43
35
|
}
|
|
44
36
|
|
|
45
37
|
.ds-interrupt-card__icon {
|
|
@@ -88,7 +80,7 @@
|
|
|
88
80
|
============================================ */
|
|
89
81
|
|
|
90
82
|
.ds-interrupt-card__detail {
|
|
91
|
-
padding: 0 var(--padding-
|
|
83
|
+
padding: 0 var(--padding-md) var(--padding-sm-md);
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
.ds-interrupt-card__detail > :first-child {
|
|
@@ -111,9 +103,8 @@
|
|
|
111
103
|
align-items: center;
|
|
112
104
|
justify-content: flex-end;
|
|
113
105
|
gap: var(--gap-sm);
|
|
114
|
-
padding: var(--padding-sm) var(--padding-
|
|
106
|
+
padding: var(--padding-sm-md) var(--padding-md);
|
|
115
107
|
border-top: var(--border-xs) solid var(--color-divider);
|
|
116
|
-
background-color: var(--color-bg-container-secondary);
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
/* ============================================
|
|
@@ -127,9 +118,8 @@
|
|
|
127
118
|
align-items: center;
|
|
128
119
|
justify-content: flex-end;
|
|
129
120
|
gap: var(--gap-xs);
|
|
130
|
-
padding: var(--padding-sm) var(--padding-
|
|
121
|
+
padding: var(--padding-sm-md) var(--padding-md);
|
|
131
122
|
border-top: var(--border-xs) solid var(--color-divider);
|
|
132
|
-
background-color: var(--color-bg-container-secondary);
|
|
133
123
|
font-family: var(--font-paragraph-sm-family);
|
|
134
124
|
font-size: var(--font-paragraph-sm-size);
|
|
135
125
|
font-weight: var(--font-paragraph-sm-weight);
|
|
@@ -22,8 +22,7 @@ type InterruptCardOwnProps = {
|
|
|
22
22
|
onValueChange?: (value: string) => void;
|
|
23
23
|
/**
|
|
24
24
|
* The already-chosen value. When set, the card renders its answered state:
|
|
25
|
-
* the
|
|
26
|
-
* echo of the chosen label.
|
|
25
|
+
* the options are replaced by a quiet echo of the chosen label.
|
|
27
26
|
*/
|
|
28
27
|
value?: string;
|
|
29
28
|
/** Override the echoed text in the answered state (default: the chosen option's label). */
|
|
@@ -43,10 +42,10 @@ export interface InterruptCardProps extends InterruptCardOwnProps, Omit<React.Co
|
|
|
43
42
|
* with option buttons for the person to decide. "Allow this file edit?" —
|
|
44
43
|
* allow once, always allow, deny.
|
|
45
44
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
45
|
+
* The card stays quiet in both states: one tone, the standard container
|
|
46
|
+
* border, options in a footer outside the content. Once `value` is set the
|
|
47
|
+
* decision is made and the footer echoes the chosen label; a visually hidden
|
|
48
|
+
* status announcement carries the waiting state for screen readers.
|
|
50
49
|
*
|
|
51
50
|
* Fully controlled: the card never stores the answer itself — `onValueChange`
|
|
52
51
|
* reports the choice and `value` renders it.
|
|
@@ -106,7 +106,6 @@
|
|
|
106
106
|
align-items: center;
|
|
107
107
|
justify-content: flex-end;
|
|
108
108
|
gap: var(--gap-sm);
|
|
109
|
-
padding: var(--padding-sm) var(--padding-
|
|
109
|
+
padding: var(--padding-sm-md) var(--padding-md);
|
|
110
110
|
border-top: var(--border-xs) solid var(--color-divider);
|
|
111
|
-
background-color: var(--color-bg-container-secondary);
|
|
112
111
|
}
|
|
@@ -76,7 +76,9 @@
|
|
|
76
76
|
min-width: 260px;
|
|
77
77
|
display: flex;
|
|
78
78
|
flex-direction: column;
|
|
79
|
-
background
|
|
79
|
+
/* Page background, not container: the container fill is semi-transparent
|
|
80
|
+
in dark mode, and a floating panel must not show text through itself */
|
|
81
|
+
background-color: var(--color-bg-page-primary);
|
|
80
82
|
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
81
83
|
border-radius: var(--radius-md);
|
|
82
84
|
box-shadow: var(--shadow-floating);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
NUMBER INPUT COMPONENT
|
|
3
|
+
Numeric field with stepper buttons and clamping
|
|
4
|
+
============================================ */
|
|
5
|
+
|
|
6
|
+
/* Base */
|
|
7
|
+
|
|
8
|
+
.ds-numberinput {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--gap-sm); /* 8px */
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* ============================================
|
|
16
|
+
SHELL — border, fill and focus ring live here
|
|
17
|
+
============================================ */
|
|
18
|
+
|
|
19
|
+
.ds-numberinput__shell {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: stretch;
|
|
22
|
+
width: 100%;
|
|
23
|
+
border: var(--border-xs) solid var(--color-input-border-primary);
|
|
24
|
+
border-radius: var(--radius-md); /* 12px — all inputs */
|
|
25
|
+
background-color: var(--color-input-bg-primary);
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
transition: border-color var(--motion-duration-base) var(--motion-ease-standard), background-color var(--motion-duration-base) var(--motion-ease-standard);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ds-numberinput__shell:hover:not(:focus-within) {
|
|
31
|
+
border-color: var(--color-input-border-hover);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ds-numberinput__shell:focus-within {
|
|
35
|
+
border-color: var(--color-input-border-selected);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ============================================
|
|
39
|
+
FIELD
|
|
40
|
+
============================================ */
|
|
41
|
+
|
|
42
|
+
.ds-numberinput__input {
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
padding: var(--padding-sm) var(--padding-xs); /* 8px 6px */
|
|
46
|
+
border: none;
|
|
47
|
+
outline: none;
|
|
48
|
+
background-color: transparent;
|
|
49
|
+
text-align: center;
|
|
50
|
+
|
|
51
|
+
/* Typography */
|
|
52
|
+
font-family: var(--font-paragraph-family);
|
|
53
|
+
font-size: var(--font-paragraph-size);
|
|
54
|
+
font-weight: var(--font-paragraph-weight);
|
|
55
|
+
line-height: var(--font-paragraph-line-height);
|
|
56
|
+
letter-spacing: var(--font-paragraph-letter-spacing);
|
|
57
|
+
color: var(--color-input-text-primary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ds-numberinput__input::placeholder {
|
|
61
|
+
color: var(--color-input-text-placeholder);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Hide the native spinners — the steppers replace them */
|
|
65
|
+
.ds-numberinput__input {
|
|
66
|
+
appearance: textfield;
|
|
67
|
+
-moz-appearance: textfield;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ds-numberinput__input::-webkit-outer-spin-button,
|
|
71
|
+
.ds-numberinput__input::-webkit-inner-spin-button {
|
|
72
|
+
-webkit-appearance: none;
|
|
73
|
+
margin: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ============================================
|
|
77
|
+
STEPPERS
|
|
78
|
+
============================================ */
|
|
79
|
+
|
|
80
|
+
.ds-numberinput__stepper {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
padding: 0 var(--padding-sm-md); /* 12px */
|
|
85
|
+
border: none;
|
|
86
|
+
background-color: transparent;
|
|
87
|
+
color: var(--color-icon-primary);
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard), color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ds-numberinput__stepper .material-symbols-rounded {
|
|
93
|
+
--icon-size: var(--icon-size-sm);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.ds-numberinput__stepper:hover:not(:disabled) {
|
|
97
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.ds-numberinput__stepper:active:not(:disabled) {
|
|
101
|
+
background-color: var(--color-action-passive-bg-active);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ds-numberinput__stepper:focus-visible {
|
|
105
|
+
outline: var(--border-md) solid var(--color-input-border-selected);
|
|
106
|
+
outline-offset: calc(-2 * var(--border-md));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ds-numberinput__stepper:disabled {
|
|
110
|
+
opacity: 0.4;
|
|
111
|
+
cursor: not-allowed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ============================================
|
|
115
|
+
ERROR STATE
|
|
116
|
+
============================================ */
|
|
117
|
+
|
|
118
|
+
.ds-numberinput--error .ds-numberinput__shell,
|
|
119
|
+
.ds-numberinput--error .ds-numberinput__shell:focus-within {
|
|
120
|
+
border-color: var(--color-status-error-border);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ============================================
|
|
124
|
+
DISABLED STATE
|
|
125
|
+
============================================ */
|
|
126
|
+
|
|
127
|
+
.ds-numberinput--disabled .ds-numberinput__shell {
|
|
128
|
+
background-color: var(--color-input-bg-disabled);
|
|
129
|
+
border-color: var(--color-input-border-disabled);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ds-numberinput--disabled .ds-numberinput__input {
|
|
133
|
+
color: var(--color-input-text-disabled);
|
|
134
|
+
cursor: not-allowed;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ds-numberinput--disabled .ds-numberinput__input::placeholder {
|
|
138
|
+
color: var(--color-input-text-disabled);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ============================================
|
|
142
|
+
SIZE — COMPACT
|
|
143
|
+
============================================ */
|
|
144
|
+
|
|
145
|
+
.ds-numberinput--compact .ds-numberinput__input {
|
|
146
|
+
padding: var(--padding-xs) var(--padding-xs); /* 6px */
|
|
147
|
+
font-size: var(--font-paragraph-sm-size);
|
|
148
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
149
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ds-numberinput--compact .ds-numberinput__stepper {
|
|
153
|
+
padding: 0 var(--padding-sm); /* 8px */
|
|
154
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by NumberInput itself — everything else falls through to the <input>. */
|
|
3
|
+
type NumberInputOwnProps = {
|
|
4
|
+
/** Input label text */
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Current value (controlled). Pass an empty string for an empty field */
|
|
7
|
+
value?: number | '';
|
|
8
|
+
/** Initial value (uncontrolled) */
|
|
9
|
+
defaultValue?: number;
|
|
10
|
+
/** Minimum allowed value — steppers stop here, and typed values clamp to it on blur */
|
|
11
|
+
min?: number;
|
|
12
|
+
/** Maximum allowed value — steppers stop here, and typed values clamp to it on blur */
|
|
13
|
+
max?: number;
|
|
14
|
+
/** Amount each stepper click (or arrow key) changes the value by */
|
|
15
|
+
step?: number;
|
|
16
|
+
/** Component size (not the native character-width `size` attribute) */
|
|
17
|
+
size?: 'default' | 'compact';
|
|
18
|
+
/** Error state — shows error styling and message */
|
|
19
|
+
error?: boolean;
|
|
20
|
+
/** Helper or error message displayed below the input */
|
|
21
|
+
helperText?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Convenience callback receiving the numeric value directly — `null` when
|
|
24
|
+
* the field is empty or unparseable. Fires on typing alongside `onChange`
|
|
25
|
+
* (which keeps the standard React event signature so form libraries work
|
|
26
|
+
* unmodified) and also on stepper clicks, where no native change event exists.
|
|
27
|
+
*/
|
|
28
|
+
onValueChange?: (value: number | null) => void;
|
|
29
|
+
/** Additional CSS classes — applied to the wrapper, not the <input> */
|
|
30
|
+
className?: string;
|
|
31
|
+
};
|
|
32
|
+
export interface NumberInputProps extends NumberInputOwnProps, Omit<React.ComponentPropsWithoutRef<'input'>, keyof NumberInputOwnProps | 'type'> {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Numeric field with increment and decrement steppers and min/max clamping.
|
|
36
|
+
*
|
|
37
|
+
* The native spinners are hidden and replaced by stepper buttons that clamp to
|
|
38
|
+
* `min`/`max` and disable at the bounds; a typed out-of-range value clamps into
|
|
39
|
+
* range on blur. Forwards a ref to the underlying `<input>` and spreads
|
|
40
|
+
* unrecognised props onto it, so `inputMode`, `data-*` and form-library
|
|
41
|
+
* registration all work.
|
|
42
|
+
*/
|
|
43
|
+
export declare const NumberInput: React.ForwardRefExoticComponent<NumberInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { Field } from "../Field/Field.js";
|
|
5
|
+
import "./NumberInput.css";
|
|
6
|
+
import "../../fonts/material-symbols.css";
|
|
7
|
+
const parseValue = (raw) => {
|
|
8
|
+
if (raw === "" || raw === void 0) return null;
|
|
9
|
+
const parsed = typeof raw === "number" ? raw : parseFloat(raw);
|
|
10
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
11
|
+
};
|
|
12
|
+
const decimalsOf = (value) => {
|
|
13
|
+
const text = String(value);
|
|
14
|
+
const dot = text.indexOf(".");
|
|
15
|
+
return dot === -1 ? 0 : text.length - dot - 1;
|
|
16
|
+
};
|
|
17
|
+
const NumberInput = React.forwardRef(
|
|
18
|
+
({
|
|
19
|
+
label,
|
|
20
|
+
value,
|
|
21
|
+
defaultValue,
|
|
22
|
+
min,
|
|
23
|
+
max,
|
|
24
|
+
step = 1,
|
|
25
|
+
size = "default",
|
|
26
|
+
disabled = false,
|
|
27
|
+
required = false,
|
|
28
|
+
error = false,
|
|
29
|
+
helperText,
|
|
30
|
+
onChange,
|
|
31
|
+
onValueChange,
|
|
32
|
+
onBlur,
|
|
33
|
+
className = "",
|
|
34
|
+
name,
|
|
35
|
+
id,
|
|
36
|
+
...rest
|
|
37
|
+
}, ref) => {
|
|
38
|
+
const baseClass = "ds-numberinput";
|
|
39
|
+
const isControlled = value !== void 0;
|
|
40
|
+
const [internalValue, setInternalValue] = useState(
|
|
41
|
+
defaultValue !== void 0 ? String(defaultValue) : ""
|
|
42
|
+
);
|
|
43
|
+
const currentValue = isControlled ? value : internalValue;
|
|
44
|
+
const currentNumber = parseValue(currentValue);
|
|
45
|
+
const classes = [
|
|
46
|
+
baseClass,
|
|
47
|
+
size === "compact" ? `${baseClass}--compact` : "",
|
|
48
|
+
error ? `${baseClass}--error` : "",
|
|
49
|
+
disabled ? `${baseClass}--disabled` : "",
|
|
50
|
+
className
|
|
51
|
+
].filter(Boolean).join(" ");
|
|
52
|
+
const clamp = (candidate) => {
|
|
53
|
+
if (min !== void 0 && candidate < min) return min;
|
|
54
|
+
if (max !== void 0 && candidate > max) return max;
|
|
55
|
+
return candidate;
|
|
56
|
+
};
|
|
57
|
+
const commit = (next) => {
|
|
58
|
+
if (!isControlled) setInternalValue(String(next));
|
|
59
|
+
onValueChange?.(next);
|
|
60
|
+
};
|
|
61
|
+
const stepBy = (direction) => {
|
|
62
|
+
const base = currentNumber ?? 0;
|
|
63
|
+
const precision = Math.max(decimalsOf(base), decimalsOf(step));
|
|
64
|
+
commit(clamp(Number((base + direction * step).toFixed(precision))));
|
|
65
|
+
};
|
|
66
|
+
const handleChange = (e) => {
|
|
67
|
+
if (!isControlled) setInternalValue(e.target.value);
|
|
68
|
+
onChange?.(e);
|
|
69
|
+
onValueChange?.(parseValue(e.target.value));
|
|
70
|
+
};
|
|
71
|
+
const handleBlur = (e) => {
|
|
72
|
+
const typed = parseValue(e.target.value);
|
|
73
|
+
if (typed !== null) {
|
|
74
|
+
const clamped = clamp(typed);
|
|
75
|
+
if (clamped !== typed) commit(clamped);
|
|
76
|
+
}
|
|
77
|
+
onBlur?.(e);
|
|
78
|
+
};
|
|
79
|
+
const atMin = min !== void 0 && currentNumber !== null && currentNumber <= min;
|
|
80
|
+
const atMax = max !== void 0 && currentNumber !== null && currentNumber >= max;
|
|
81
|
+
const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
|
|
82
|
+
const describedBy = helperText ? `${inputId}-helper` : rest["aria-describedby"];
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
Field,
|
|
85
|
+
{
|
|
86
|
+
className: classes,
|
|
87
|
+
label,
|
|
88
|
+
helperText,
|
|
89
|
+
error,
|
|
90
|
+
required,
|
|
91
|
+
disabled,
|
|
92
|
+
size,
|
|
93
|
+
id: inputId,
|
|
94
|
+
children: /* @__PURE__ */ jsxs("div", { className: `${baseClass}__shell`, children: [
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
type: "button",
|
|
99
|
+
className: `${baseClass}__stepper`,
|
|
100
|
+
"aria-label": "Decrease value",
|
|
101
|
+
disabled: disabled || atMin,
|
|
102
|
+
onClick: () => stepBy(-1),
|
|
103
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "remove" })
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
"input",
|
|
108
|
+
{
|
|
109
|
+
...rest,
|
|
110
|
+
ref,
|
|
111
|
+
className: `${baseClass}__input`,
|
|
112
|
+
type: "number",
|
|
113
|
+
id: inputId,
|
|
114
|
+
name,
|
|
115
|
+
value: currentValue,
|
|
116
|
+
min,
|
|
117
|
+
max,
|
|
118
|
+
step,
|
|
119
|
+
disabled,
|
|
120
|
+
required,
|
|
121
|
+
"aria-label": rest["aria-label"] || label,
|
|
122
|
+
"aria-invalid": error,
|
|
123
|
+
"aria-describedby": describedBy,
|
|
124
|
+
onChange: handleChange,
|
|
125
|
+
onBlur: handleBlur
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
"button",
|
|
130
|
+
{
|
|
131
|
+
type: "button",
|
|
132
|
+
className: `${baseClass}__stepper`,
|
|
133
|
+
"aria-label": "Increase value",
|
|
134
|
+
disabled: disabled || atMax,
|
|
135
|
+
onClick: () => stepBy(1),
|
|
136
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "add" })
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
] })
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
NumberInput.displayName = "NumberInput";
|
|
145
|
+
export {
|
|
146
|
+
NumberInput
|
|
147
|
+
};
|