@flamingo-stack/openframe-frontend-core 0.0.659-2347.6820.1 → 0.0.659
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/{chunk-D4Z7TYWU.js → chunk-2DLM3NXE.js} +4 -4
- package/dist/{chunk-NIKCNKD2.js → chunk-6DVSTVUU.js} +6 -6
- package/dist/{chunk-VAXW73AH.js → chunk-6KR74SOJ.js} +2 -2
- package/dist/{chunk-VZLPXDYN.cjs → chunk-AOOORZI5.cjs} +3 -3
- package/dist/{chunk-VZLPXDYN.cjs.map → chunk-AOOORZI5.cjs.map} +1 -1
- package/dist/{chunk-DMJ636H5.js → chunk-APWQTZCR.js} +2 -2
- package/dist/chunk-BXWXBBAC.js +228 -0
- package/dist/chunk-BXWXBBAC.js.map +1 -0
- package/dist/{chunk-JBL365GX.js → chunk-E7C3F2SK.js} +237 -223
- package/dist/chunk-E7C3F2SK.js.map +1 -0
- package/dist/{chunk-VXTTNIRO.cjs → chunk-EOGXNLZC.cjs} +17 -17
- package/dist/{chunk-VXTTNIRO.cjs.map → chunk-EOGXNLZC.cjs.map} +1 -1
- package/dist/{chunk-BOZSCBHS.cjs → chunk-GDHV5W5C.cjs} +33 -33
- package/dist/{chunk-BOZSCBHS.cjs.map → chunk-GDHV5W5C.cjs.map} +1 -1
- package/dist/{chunk-FLWYFXNK.cjs → chunk-ITVLOEGF.cjs} +55 -55
- package/dist/{chunk-FLWYFXNK.cjs.map → chunk-ITVLOEGF.cjs.map} +1 -1
- package/dist/{chunk-K5HPBVUH.cjs → chunk-K4W3II7M.cjs} +3 -3
- package/dist/{chunk-K5HPBVUH.cjs.map → chunk-K4W3II7M.cjs.map} +1 -1
- package/dist/{chunk-J7OY7QBA.js → chunk-KP4K6I5O.js} +3 -3
- package/dist/{chunk-XBPTM4IT.cjs → chunk-L4YJU3AG.cjs} +16 -16
- package/dist/{chunk-XBPTM4IT.cjs.map → chunk-L4YJU3AG.cjs.map} +1 -1
- package/dist/{chunk-FXUFGHVU.js → chunk-MEFCA6VD.js} +2 -2
- package/dist/{chunk-S5OQILYF.js → chunk-MW7LEMFG.js} +2 -2
- package/dist/{chunk-PDLHEQ6C.cjs → chunk-NXAXZPEO.cjs} +10 -10
- package/dist/{chunk-PDLHEQ6C.cjs.map → chunk-NXAXZPEO.cjs.map} +1 -1
- package/dist/{chunk-I6Q5RA5M.js → chunk-P2ROGTMF.js} +2 -2
- package/dist/{chunk-ZUOXB5YX.cjs → chunk-PUE4XMZK.cjs} +279 -265
- package/dist/chunk-PUE4XMZK.cjs.map +1 -0
- package/dist/chunk-QVSVRG7I.cjs +228 -0
- package/dist/chunk-QVSVRG7I.cjs.map +1 -0
- package/dist/{chunk-XNR7DJP3.js → chunk-QVZD2LLU.js} +4 -4
- package/dist/{chunk-3CTLV2BR.cjs → chunk-RRGTBH3L.cjs} +3 -3
- package/dist/{chunk-3CTLV2BR.cjs.map → chunk-RRGTBH3L.cjs.map} +1 -1
- package/dist/{chunk-7Q4UWN5X.js → chunk-T6LNAX4R.js} +3 -3
- package/dist/{chunk-TUXQWRF7.cjs → chunk-UIAXQJPV.cjs} +24 -24
- package/dist/{chunk-TUXQWRF7.cjs.map → chunk-UIAXQJPV.cjs.map} +1 -1
- package/dist/{chunk-ALMCR73J.js → chunk-VFGBRGFB.js} +3 -3
- package/dist/{chunk-5IXTYDUH.cjs → chunk-VJMPYY5O.cjs} +18 -18
- package/dist/{chunk-5IXTYDUH.cjs.map → chunk-VJMPYY5O.cjs.map} +1 -1
- package/dist/{chunk-XGJUNSUY.js → chunk-VVOI2OC3.js} +2 -2
- package/dist/{chunk-MCGLT2GN.cjs → chunk-VW7NXM2O.cjs} +36 -36
- package/dist/{chunk-MCGLT2GN.cjs.map → chunk-VW7NXM2O.cjs.map} +1 -1
- package/dist/{chunk-WK4464NX.cjs → chunk-VWGFJJD6.cjs} +4 -4
- package/dist/{chunk-WK4464NX.cjs.map → chunk-VWGFJJD6.cjs.map} +1 -1
- package/dist/{chunk-NP2XVKA6.cjs → chunk-WMIB6JSA.cjs} +4 -4
- package/dist/{chunk-NP2XVKA6.cjs.map → chunk-WMIB6JSA.cjs.map} +1 -1
- package/dist/{chunk-OTDNJTDJ.js → chunk-WWEQT2WX.js} +5 -5
- package/dist/{chunk-XRD5FFHK.cjs → chunk-Z7NI2TQD.cjs} +3 -3
- package/dist/{chunk-XRD5FFHK.cjs.map → chunk-Z7NI2TQD.cjs.map} +1 -1
- package/dist/{chunk-CCGP4PRB.js → chunk-ZT3CZCDS.js} +2 -2
- package/dist/components/case-studies/index.cjs +12 -12
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +33 -33
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +27 -27
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/autocomplete.d.ts.map +1 -1
- package/dist/components/ui/date-picker.d.ts +2 -2
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/field-wrapper.d.ts +15 -1
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +4 -4
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/index.cjs +7 -7
- package/dist/index.js +6 -6
- package/package.json +1 -1
- package/src/components/ui/__tests__/field-wrapper-error-slot.test.tsx +82 -0
- package/src/components/ui/autocomplete.tsx +7 -1
- package/src/components/ui/date-picker.tsx +40 -38
- package/src/components/ui/field-wrapper.tsx +27 -3
- package/src/components/ui/input.tsx +131 -121
- package/src/components/ui/select.tsx +5 -2
- package/src/components/ui/textarea.tsx +173 -175
- package/dist/chunk-IWISVG4P.js +0 -198
- package/dist/chunk-IWISVG4P.js.map +0 -1
- package/dist/chunk-JBL365GX.js.map +0 -1
- package/dist/chunk-YWUZL3RT.cjs +0 -198
- package/dist/chunk-YWUZL3RT.cjs.map +0 -1
- package/dist/chunk-ZUOXB5YX.cjs.map +0 -1
- /package/dist/{chunk-D4Z7TYWU.js.map → chunk-2DLM3NXE.js.map} +0 -0
- /package/dist/{chunk-NIKCNKD2.js.map → chunk-6DVSTVUU.js.map} +0 -0
- /package/dist/{chunk-VAXW73AH.js.map → chunk-6KR74SOJ.js.map} +0 -0
- /package/dist/{chunk-DMJ636H5.js.map → chunk-APWQTZCR.js.map} +0 -0
- /package/dist/{chunk-J7OY7QBA.js.map → chunk-KP4K6I5O.js.map} +0 -0
- /package/dist/{chunk-FXUFGHVU.js.map → chunk-MEFCA6VD.js.map} +0 -0
- /package/dist/{chunk-S5OQILYF.js.map → chunk-MW7LEMFG.js.map} +0 -0
- /package/dist/{chunk-I6Q5RA5M.js.map → chunk-P2ROGTMF.js.map} +0 -0
- /package/dist/{chunk-XNR7DJP3.js.map → chunk-QVZD2LLU.js.map} +0 -0
- /package/dist/{chunk-7Q4UWN5X.js.map → chunk-T6LNAX4R.js.map} +0 -0
- /package/dist/{chunk-ALMCR73J.js.map → chunk-VFGBRGFB.js.map} +0 -0
- /package/dist/{chunk-XGJUNSUY.js.map → chunk-VVOI2OC3.js.map} +0 -0
- /package/dist/{chunk-OTDNJTDJ.js.map → chunk-WWEQT2WX.js.map} +0 -0
- /package/dist/{chunk-CCGP4PRB.js.map → chunk-ZT3CZCDS.js.map} +0 -0
|
@@ -37,192 +37,190 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
37
37
|
hideBorder?: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const hasAdornment = !!endIcon || !!startIcon;
|
|
40
|
+
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>((allProps, ref) => {
|
|
41
|
+
const {
|
|
42
|
+
className,
|
|
43
|
+
invalid = false,
|
|
44
|
+
label,
|
|
45
|
+
labelVariant,
|
|
46
|
+
error,
|
|
47
|
+
endIcon,
|
|
48
|
+
endIconAsButton = false,
|
|
49
|
+
endIconButtonProps,
|
|
50
|
+
startIcon,
|
|
51
|
+
hideBorder = false,
|
|
52
|
+
...props
|
|
53
|
+
} = allProps;
|
|
54
|
+
// Wired for a message even while `error` is undefined — see `FieldWrapper.errorSlot`.
|
|
55
|
+
const errorSlot = 'error' in allProps;
|
|
56
|
+
const isInvalid = invalid || !!error;
|
|
57
|
+
const hasAdornment = !!endIcon || !!startIcon;
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
// The adorned layout wraps the field in a `<label>`. Without an explicit
|
|
60
|
+
// `htmlFor`, the label's control defaults to its FIRST labelable descendant
|
|
61
|
+
// — which is `startIcon` (e.g. the composer `+` button) when present, so a
|
|
62
|
+
// click anywhere on the field synthesises a click on that button. Bind the
|
|
63
|
+
// label to the textarea explicitly to keep clicks (and focus) on the input.
|
|
64
|
+
const reactId = useId();
|
|
65
|
+
const fieldId = props.id ?? reactId;
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<textarea
|
|
74
|
-
className={cn(
|
|
75
|
-
'flex min-h-[96px] w-full rounded-[6px] border p-3',
|
|
76
|
-
'text-h4',
|
|
77
|
-
'focus:border-ods-accent focus-visible:outline-none',
|
|
78
|
-
'touch-manipulation transition-colors duration-200',
|
|
79
|
-
'border-ods-border bg-ods-card text-ods-text-primary placeholder:text-ods-text-secondary',
|
|
80
|
-
!props.disabled &&
|
|
81
|
-
'hover:border-ods-border-hover hover:bg-ods-bg-hover active:border-ods-border-active active:bg-ods-bg-active',
|
|
82
|
-
props.disabled && '!cursor-not-allowed bg-ods-bg disabled:placeholder:text-ods-border',
|
|
83
|
-
'relative z-10 cursor-text',
|
|
84
|
-
'resize-y',
|
|
85
|
-
isInvalid && 'border-ods-error hover:border-ods-error focus:border-ods-error',
|
|
86
|
-
className,
|
|
87
|
-
)}
|
|
88
|
-
ref={ref}
|
|
89
|
-
{...props}
|
|
90
|
-
/>
|
|
91
|
-
</FieldWrapper>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// With an end icon we lift border/bg onto a `<label>` wrapper so the icon
|
|
96
|
-
// sits visually inside the same field. The textarea becomes a transparent
|
|
97
|
-
// child. Wrapper classes mirror `Input` 1:1 (same items-center, same
|
|
98
|
-
// `has-[:focus]` selectors, same active state, same outline reset) so
|
|
99
|
-
// hover / focus / cursor behaviour matches the standard input exactly.
|
|
100
|
-
//
|
|
101
|
-
// Focus selector: historically `has-[:focus]` (any focusable descendant —
|
|
102
|
-
// textarea OR the end-icon submit button — lights the accent). We narrow it
|
|
103
|
-
// to `textarea:focus` ONLY when a `startIcon` is present, because that's the
|
|
104
|
-
// composer-context layout where the start adornment hosts the context
|
|
105
|
-
// picker's own `<input>` (search field) — which must NOT light the field
|
|
106
|
-
// chrome. End-icon-only consumers (ticket reply, contact form, …) keep the
|
|
107
|
-
// original any-focus behaviour so the submit-button focus ring is unchanged.
|
|
108
|
-
const accentBorder = startIcon ? 'has-[textarea:focus]:border-ods-accent' : 'has-[:focus]:border-ods-accent';
|
|
109
|
-
const accentBorderError = startIcon ? 'has-[textarea:focus]:border-ods-error' : 'has-[:focus]:border-ods-error';
|
|
110
|
-
const adornmentAccent = startIcon
|
|
111
|
-
? 'group-has-[textarea:focus]:text-ods-accent'
|
|
112
|
-
: 'group-has-[:focus]:text-ods-accent';
|
|
113
|
-
const content = (
|
|
114
|
-
<label
|
|
115
|
-
htmlFor={fieldId}
|
|
116
|
-
data-invalid={isInvalid || undefined}
|
|
117
|
-
className={cn(
|
|
118
|
-
// Wrapper mirrors `Input`, but uses `items-end` so the icon sticks
|
|
119
|
-
// to the bottom-right when the textarea grows multi-line. Vertical
|
|
120
|
-
// padding lives here (not on the textarea) so both children share
|
|
121
|
-
// the same bottom baseline at any height.
|
|
122
|
-
//
|
|
123
|
-
// Padding is sized so the single-line height matches `Input` exactly
|
|
124
|
-
// (44px mobile / 48px desktop): line `leading-6` (24px) + 2× padding
|
|
125
|
-
// + 1px border top/bottom. `min-h-11 md:min-h-12` is the matching
|
|
126
|
-
// floor; with symmetric padding the single line stays centered.
|
|
127
|
-
'flex min-h-11 w-full cursor-text items-end gap-2 px-3 py-[9px] md:min-h-12 md:py-[11px]',
|
|
128
|
-
'has-[:focus-visible]:outline-none',
|
|
129
|
-
'group',
|
|
130
|
-
'transition-colors duration-200',
|
|
131
|
-
// Card chrome (border / bg / radius / hover / focus). Suppressed when
|
|
132
|
-
// `hideBorder` — an outer container owns it instead.
|
|
133
|
-
!hideBorder && cn('rounded-[6px] border border-ods-border bg-ods-card', accentBorder),
|
|
134
|
-
!hideBorder &&
|
|
135
|
-
!props.disabled &&
|
|
136
|
-
'hover:border-ods-border-hover hover:bg-ods-bg-hover active:border-ods-border-active active:bg-ods-bg-active',
|
|
137
|
-
!hideBorder && props.disabled && '!cursor-not-allowed bg-ods-bg',
|
|
138
|
-
// Start / end icons carry their own colour — grey them with the value
|
|
139
|
-
// so a disabled field reads as one flat colour. Scoped to the DIRECT
|
|
140
|
-
// span children (the two icon wrappers): the nodes inside them are
|
|
141
|
-
// rendered verbatim so callers can pass an interactive trigger and own
|
|
142
|
-
// its styling, and a descendant rule would overwrite exactly that.
|
|
143
|
-
'has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled',
|
|
144
|
-
!hideBorder && isInvalid && cn('border-ods-error hover:border-ods-error', accentBorderError),
|
|
145
|
-
hideBorder && 'bg-transparent',
|
|
146
|
-
)}
|
|
147
|
-
>
|
|
148
|
-
{startIcon && (
|
|
149
|
-
// Start adornment (e.g. the composer `+`). `items-end` + `h-6`
|
|
150
|
-
// co-centers it with the first text line, matching `endIcon`. The
|
|
151
|
-
// node is rendered verbatim so callers can pass an interactive
|
|
152
|
-
// button / menu trigger and own its styling + active color.
|
|
153
|
-
<span className="flex h-6 shrink-0 items-center">{startIcon}</span>
|
|
154
|
-
)}
|
|
67
|
+
// Without any adornment we keep the historical layout: bare textarea, the
|
|
68
|
+
// border/bg/hover all live on the textarea itself.
|
|
69
|
+
if (!hasAdornment) {
|
|
70
|
+
return (
|
|
71
|
+
<FieldWrapper label={label} labelVariant={labelVariant} error={error} errorSlot={errorSlot}>
|
|
155
72
|
<textarea
|
|
156
|
-
ref={ref}
|
|
157
|
-
rows={1}
|
|
158
|
-
id={fieldId}
|
|
159
73
|
className={cn(
|
|
160
|
-
'
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
'
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
'
|
|
171
|
-
// Hard cap on growth: beyond `max-h` the textarea scrolls
|
|
172
|
-
// internally instead of pushing the icon out of frame.
|
|
173
|
-
'max-h-[160px] overflow-y-auto',
|
|
174
|
-
'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30',
|
|
175
|
-
// Disabled - match Input exactly (value greys out, placeholder dims further)
|
|
176
|
-
'disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border',
|
|
74
|
+
'flex min-h-[96px] w-full rounded-[6px] border p-3',
|
|
75
|
+
'text-h4',
|
|
76
|
+
'focus:border-ods-accent focus-visible:outline-none',
|
|
77
|
+
'touch-manipulation transition-colors duration-200',
|
|
78
|
+
'border-ods-border bg-ods-card text-ods-text-primary placeholder:text-ods-text-secondary',
|
|
79
|
+
!props.disabled &&
|
|
80
|
+
'hover:border-ods-border-hover hover:bg-ods-bg-hover active:border-ods-border-active active:bg-ods-bg-active',
|
|
81
|
+
props.disabled && '!cursor-not-allowed bg-ods-bg disabled:placeholder:text-ods-border',
|
|
82
|
+
'relative z-10 cursor-text',
|
|
83
|
+
'resize-y',
|
|
84
|
+
isInvalid && 'border-ods-error hover:border-ods-error focus:border-ods-error',
|
|
177
85
|
className,
|
|
178
86
|
)}
|
|
87
|
+
ref={ref}
|
|
179
88
|
{...props}
|
|
180
89
|
/>
|
|
90
|
+
</FieldWrapper>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
181
93
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
94
|
+
// With an end icon we lift border/bg onto a `<label>` wrapper so the icon
|
|
95
|
+
// sits visually inside the same field. The textarea becomes a transparent
|
|
96
|
+
// child. Wrapper classes mirror `Input` 1:1 (same items-center, same
|
|
97
|
+
// `has-[:focus]` selectors, same active state, same outline reset) so
|
|
98
|
+
// hover / focus / cursor behaviour matches the standard input exactly.
|
|
99
|
+
//
|
|
100
|
+
// Focus selector: historically `has-[:focus]` (any focusable descendant —
|
|
101
|
+
// textarea OR the end-icon submit button — lights the accent). We narrow it
|
|
102
|
+
// to `textarea:focus` ONLY when a `startIcon` is present, because that's the
|
|
103
|
+
// composer-context layout where the start adornment hosts the context
|
|
104
|
+
// picker's own `<input>` (search field) — which must NOT light the field
|
|
105
|
+
// chrome. End-icon-only consumers (ticket reply, contact form, …) keep the
|
|
106
|
+
// original any-focus behaviour so the submit-button focus ring is unchanged.
|
|
107
|
+
const accentBorder = startIcon ? 'has-[textarea:focus]:border-ods-accent' : 'has-[:focus]:border-ods-accent';
|
|
108
|
+
const accentBorderError = startIcon ? 'has-[textarea:focus]:border-ods-error' : 'has-[:focus]:border-ods-error';
|
|
109
|
+
const adornmentAccent = startIcon
|
|
110
|
+
? 'group-has-[textarea:focus]:text-ods-accent'
|
|
111
|
+
: 'group-has-[:focus]:text-ods-accent';
|
|
112
|
+
const content = (
|
|
113
|
+
<label
|
|
114
|
+
htmlFor={fieldId}
|
|
115
|
+
data-invalid={isInvalid || undefined}
|
|
116
|
+
className={cn(
|
|
117
|
+
// Wrapper mirrors `Input`, but uses `items-end` so the icon sticks
|
|
118
|
+
// to the bottom-right when the textarea grows multi-line. Vertical
|
|
119
|
+
// padding lives here (not on the textarea) so both children share
|
|
120
|
+
// the same bottom baseline at any height.
|
|
121
|
+
//
|
|
122
|
+
// Padding is sized so the single-line height matches `Input` exactly
|
|
123
|
+
// (44px mobile / 48px desktop): line `leading-6` (24px) + 2× padding
|
|
124
|
+
// + 1px border top/bottom. `min-h-11 md:min-h-12` is the matching
|
|
125
|
+
// floor; with symmetric padding the single line stays centered.
|
|
126
|
+
'flex min-h-11 w-full cursor-text items-end gap-2 px-3 py-[9px] md:min-h-12 md:py-[11px]',
|
|
127
|
+
'has-[:focus-visible]:outline-none',
|
|
128
|
+
'group',
|
|
129
|
+
'transition-colors duration-200',
|
|
130
|
+
// Card chrome (border / bg / radius / hover / focus). Suppressed when
|
|
131
|
+
// `hideBorder` — an outer container owns it instead.
|
|
132
|
+
!hideBorder && cn('rounded-[6px] border border-ods-border bg-ods-card', accentBorder),
|
|
133
|
+
!hideBorder &&
|
|
134
|
+
!props.disabled &&
|
|
135
|
+
'hover:border-ods-border-hover hover:bg-ods-bg-hover active:border-ods-border-active active:bg-ods-bg-active',
|
|
136
|
+
!hideBorder && props.disabled && '!cursor-not-allowed bg-ods-bg',
|
|
137
|
+
// Start / end icons carry their own colour — grey them with the value
|
|
138
|
+
// so a disabled field reads as one flat colour. Scoped to the DIRECT
|
|
139
|
+
// span children (the two icon wrappers): the nodes inside them are
|
|
140
|
+
// rendered verbatim so callers can pass an interactive trigger and own
|
|
141
|
+
// its styling, and a descendant rule would overwrite exactly that.
|
|
142
|
+
'has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled',
|
|
143
|
+
!hideBorder && isInvalid && cn('border-ods-error hover:border-ods-error', accentBorderError),
|
|
144
|
+
hideBorder && 'bg-transparent',
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
{startIcon && (
|
|
148
|
+
// Start adornment (e.g. the composer `+`). `items-end` + `h-6`
|
|
149
|
+
// co-centers it with the first text line, matching `endIcon`. The
|
|
150
|
+
// node is rendered verbatim so callers can pass an interactive
|
|
151
|
+
// button / menu trigger and own its styling + active color.
|
|
152
|
+
<span className="flex h-6 shrink-0 items-center">{startIcon}</span>
|
|
153
|
+
)}
|
|
154
|
+
<textarea
|
|
155
|
+
ref={ref}
|
|
156
|
+
rows={1}
|
|
157
|
+
id={fieldId}
|
|
158
|
+
className={cn(
|
|
159
|
+
'm-0 box-border min-w-0 flex-1 resize-none border-none bg-transparent p-0 outline-none',
|
|
160
|
+
// Native CSS auto-grow (Chrome 123+, FF 124+, Safari 16.4+) — no
|
|
161
|
+
// JS needed to size to content. Falls back to `rows={1}` natural
|
|
162
|
+
// height on older browsers.
|
|
163
|
+
'[field-sizing:content]',
|
|
164
|
+
// `!leading-6` (not plain `leading-6`): on mobile `text-h4` sets a
|
|
165
|
+
// 20px line-height of its own that otherwise overrides the leading,
|
|
166
|
+
// leaving the 20px text box short of the 24px icon box and dropping
|
|
167
|
+
// the text below center under `items-end`. Forcing 24px keeps text
|
|
168
|
+
// and icon co-centered and the single-line height at 44px.
|
|
169
|
+
'!leading-6 text-ods-text-primary text-h4 placeholder:text-ods-text-secondary',
|
|
170
|
+
// Hard cap on growth: beyond `max-h` the textarea scrolls
|
|
171
|
+
// internally instead of pushing the icon out of frame.
|
|
172
|
+
'max-h-[160px] overflow-y-auto',
|
|
173
|
+
'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30',
|
|
174
|
+
// Disabled - match Input exactly (value greys out, placeholder dims further)
|
|
175
|
+
'disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border',
|
|
176
|
+
className,
|
|
215
177
|
)}
|
|
216
|
-
|
|
217
|
-
|
|
178
|
+
{...props}
|
|
179
|
+
/>
|
|
218
180
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
181
|
+
{endIconAsButton ? (
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
aria-label={endIconButtonProps?.['aria-label'] ?? 'Submit'}
|
|
185
|
+
{...endIconButtonProps}
|
|
186
|
+
className={cn(
|
|
187
|
+
// `h-6` matches the textarea's `leading-6` line box so the icon's
|
|
188
|
+
// visual center lines up with the text on the first line. Without
|
|
189
|
+
// it, `items-end` bottom-aligns the smaller mobile icon (size-4)
|
|
190
|
+
// against the 24px text line and drops it below the text center.
|
|
191
|
+
'flex h-6 shrink-0 items-center text-ods-text-secondary transition-colors duration-200',
|
|
192
|
+
adornmentAccent,
|
|
193
|
+
'group-data-[invalid]:text-ods-error',
|
|
194
|
+
'[&_svg]:size-4 md:[&_svg]:size-6',
|
|
195
|
+
'cursor-pointer hover:text-ods-text-primary',
|
|
196
|
+
'focus-visible:text-ods-accent focus-visible:outline-none',
|
|
197
|
+
'disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:text-ods-text-secondary',
|
|
198
|
+
endIconButtonProps?.className,
|
|
199
|
+
)}
|
|
200
|
+
>
|
|
201
|
+
{endIcon}
|
|
202
|
+
</button>
|
|
203
|
+
) : (
|
|
204
|
+
<span
|
|
205
|
+
className={cn(
|
|
206
|
+
'flex h-6 shrink-0 items-center text-ods-text-secondary transition-colors duration-200',
|
|
207
|
+
adornmentAccent,
|
|
208
|
+
'group-data-[invalid]:text-ods-error',
|
|
209
|
+
'[&_svg]:size-4 md:[&_svg]:size-6',
|
|
210
|
+
)}
|
|
211
|
+
>
|
|
212
|
+
{endIcon}
|
|
213
|
+
</span>
|
|
214
|
+
)}
|
|
215
|
+
</label>
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<FieldWrapper label={label} labelVariant={labelVariant} error={error} errorSlot={errorSlot}>
|
|
220
|
+
{content}
|
|
221
|
+
</FieldWrapper>
|
|
222
|
+
);
|
|
223
|
+
});
|
|
226
224
|
Textarea.displayName = 'Textarea';
|
|
227
225
|
|
|
228
226
|
export { Textarea };
|
package/dist/chunk-IWISVG4P.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import {
|
|
3
|
-
Label
|
|
4
|
-
} from "./chunk-XBTZOO74.js";
|
|
5
|
-
import {
|
|
6
|
-
CheckboxCheckmarkIcon
|
|
7
|
-
} from "./chunk-6U3IUD57.js";
|
|
8
|
-
import {
|
|
9
|
-
cn
|
|
10
|
-
} from "./chunk-OXB6LRBK.js";
|
|
11
|
-
|
|
12
|
-
// src/components/ui/required-mark.tsx
|
|
13
|
-
import { jsx } from "react/jsx-runtime";
|
|
14
|
-
function RequiredMark() {
|
|
15
|
-
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-ods-accent", children: "*" });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// src/components/ui/field-wrapper.tsx
|
|
19
|
-
import { forwardRef } from "react";
|
|
20
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
21
|
-
var errorVariantClasses = {
|
|
22
|
-
error: "text-ods-error",
|
|
23
|
-
warning: "text-ods-warning",
|
|
24
|
-
success: "text-ods-success",
|
|
25
|
-
muted: "text-ods-text-secondary"
|
|
26
|
-
};
|
|
27
|
-
var FieldWrapper = forwardRef(
|
|
28
|
-
({ label, htmlFor, error, errorVariant = "error", labelVariant = "default", required = false, className, children }, ref) => {
|
|
29
|
-
const hasChrome = label != null || error != null;
|
|
30
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: cn(hasChrome ? "relative flex w-full flex-col" : "contents", className), children: [
|
|
31
|
-
label && // Default is text-h6, NOT text-h4: `Field` (ui/field.tsx) renders its label via
|
|
32
|
-
// `Label variant="small"` = text-h6, and every form that mixes
|
|
33
|
-
// Field-wrapped controls with `label=`-prop controls showed two label
|
|
34
|
-
// sizes side by side — the exact inconsistency the admin editors kept
|
|
35
|
-
// reporting. One label scale, owned here and in Field together.
|
|
36
|
-
/* @__PURE__ */ jsxs(Label, { className: "mb-1", htmlFor, variant: labelVariant, children: [
|
|
37
|
-
label,
|
|
38
|
-
required && /* @__PURE__ */ jsx2(RequiredMark, {})
|
|
39
|
-
] }),
|
|
40
|
-
children,
|
|
41
|
-
error && /* @__PURE__ */ jsx2(
|
|
42
|
-
"p",
|
|
43
|
-
{
|
|
44
|
-
className: cn(
|
|
45
|
-
"absolute bottom-0 left-0 right-0 translate-y-full truncate text-h6",
|
|
46
|
-
errorVariantClasses[errorVariant]
|
|
47
|
-
),
|
|
48
|
-
title: error,
|
|
49
|
-
children: error
|
|
50
|
-
}
|
|
51
|
-
)
|
|
52
|
-
] });
|
|
53
|
-
}
|
|
54
|
-
);
|
|
55
|
-
FieldWrapper.displayName = "FieldWrapper";
|
|
56
|
-
|
|
57
|
-
// src/components/ui/input.tsx
|
|
58
|
-
import { Loader2 } from "lucide-react";
|
|
59
|
-
import { forwardRef as forwardRef2 } from "react";
|
|
60
|
-
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
61
|
-
var invalidBorderClasses = {
|
|
62
|
-
error: "border-ods-error hover:border-ods-error has-[:focus]:border-ods-error",
|
|
63
|
-
warning: "!border-ods-warning hover:!border-ods-warning has-[:focus]:!border-ods-warning"
|
|
64
|
-
};
|
|
65
|
-
var Input = forwardRef2(
|
|
66
|
-
({
|
|
67
|
-
className,
|
|
68
|
-
type,
|
|
69
|
-
invalid = false,
|
|
70
|
-
startAdornment,
|
|
71
|
-
endAdornment,
|
|
72
|
-
label,
|
|
73
|
-
labelVariant,
|
|
74
|
-
error,
|
|
75
|
-
errorVariant = "error",
|
|
76
|
-
loading = false,
|
|
77
|
-
...props
|
|
78
|
-
}, ref) => {
|
|
79
|
-
const variantIsInvalid = errorVariant === "error" || errorVariant === "warning";
|
|
80
|
-
const isInvalid = invalid || !!error && variantIsInvalid;
|
|
81
|
-
if (type === "range") {
|
|
82
|
-
const rangeInput = /* @__PURE__ */ jsx3(
|
|
83
|
-
"input",
|
|
84
|
-
{
|
|
85
|
-
type: "range",
|
|
86
|
-
className: cn(
|
|
87
|
-
"h-1 w-full cursor-pointer appearance-none rounded-full bg-white/30",
|
|
88
|
-
// Webkit (Chrome/Safari) thumb
|
|
89
|
-
"[&::-webkit-slider-thumb]:appearance-none",
|
|
90
|
-
"[&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3",
|
|
91
|
-
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white",
|
|
92
|
-
"[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:shadow-sm",
|
|
93
|
-
// Firefox thumb
|
|
94
|
-
"[&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:w-3",
|
|
95
|
-
"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-white",
|
|
96
|
-
"[&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:border-0",
|
|
97
|
-
// Firefox track
|
|
98
|
-
"[&::-moz-range-track]:bg-transparent",
|
|
99
|
-
// Disabled
|
|
100
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
101
|
-
className
|
|
102
|
-
),
|
|
103
|
-
ref,
|
|
104
|
-
...props
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
return label ? /* @__PURE__ */ jsx3(FieldWrapper, { label, labelVariant, error, errorVariant, children: rangeInput }) : rangeInput;
|
|
108
|
-
}
|
|
109
|
-
const content = /* @__PURE__ */ jsxs2(
|
|
110
|
-
"label",
|
|
111
|
-
{
|
|
112
|
-
"data-invalid": isInvalid || void 0,
|
|
113
|
-
className: cn(
|
|
114
|
-
// Layout & spacing
|
|
115
|
-
"flex h-11 w-full cursor-text items-center gap-2 rounded-[6px] border px-3 md:h-12",
|
|
116
|
-
// Focus-within states
|
|
117
|
-
"has-[:focus-visible]:outline-none",
|
|
118
|
-
"group",
|
|
119
|
-
// Animations & touch UX
|
|
120
|
-
"transition-colors duration-200",
|
|
121
|
-
// Theme palette
|
|
122
|
-
"border-ods-border bg-ods-card has-[:focus]:border-ods-accent",
|
|
123
|
-
// Hover & active (not disabled)
|
|
124
|
-
!props.disabled && "hover:border-ods-border-hover hover:bg-ods-bg-hover active:border-ods-border-active active:bg-ods-bg-active",
|
|
125
|
-
// Disabled. The adornments (and any icon inside them) carry their own
|
|
126
|
-
// `text-ods-text-secondary`, so grey them from here — a disabled field
|
|
127
|
-
// must read as one flat colour, not grey text next to a live icon.
|
|
128
|
-
// Scoped to the DIRECT span children (the two adornment wrappers): a
|
|
129
|
-
// descendant rule would also repaint whatever the caller renders INSIDE
|
|
130
|
-
// an adornment, which is passed verbatim precisely so it can own its
|
|
131
|
-
// colour (a `Tag` there went invisible against its own fill).
|
|
132
|
-
props.disabled && "!cursor-not-allowed bg-ods-bg",
|
|
133
|
-
"has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled",
|
|
134
|
-
// Invalid
|
|
135
|
-
isInvalid && invalidBorderClasses[variantIsInvalid ? errorVariant : "error"],
|
|
136
|
-
className
|
|
137
|
-
),
|
|
138
|
-
children: [
|
|
139
|
-
startAdornment && /* @__PURE__ */ jsx3("span", { className: "flex-shrink-0 text-ods-text-secondary transition-colors duration-200 text-h6 group-has-[:focus]:text-ods-accent group-data-[invalid]:text-ods-error [&_svg]:size-4 md:[&_svg]:size-6", children: startAdornment }),
|
|
140
|
-
/* @__PURE__ */ jsx3(
|
|
141
|
-
"input",
|
|
142
|
-
{
|
|
143
|
-
type,
|
|
144
|
-
className: cn(
|
|
145
|
-
// Layout
|
|
146
|
-
"min-w-0 flex-1 border-none bg-transparent outline-none",
|
|
147
|
-
// Typography
|
|
148
|
-
"text-h4",
|
|
149
|
-
// Colors
|
|
150
|
-
"text-ods-text-primary placeholder:text-ods-text-secondary",
|
|
151
|
-
// File input adjustments
|
|
152
|
-
"file:border-0 file:bg-transparent",
|
|
153
|
-
// Disabled
|
|
154
|
-
"disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border",
|
|
155
|
-
// Touch
|
|
156
|
-
"touch-manipulation",
|
|
157
|
-
// Autofill override
|
|
158
|
-
"[&:-webkit-autofill]:[-webkit-box-shadow:0_0_0_9999px_transparent_inset] [&:-webkit-autofill]:[-webkit-text-fill-color:var(--color-text-primary)] [&:-webkit-autofill]:[caret-color:var(--color-text-primary)] [&:-webkit-autofill]:[transition:background-color_9999s_ease-in-out_0s]"
|
|
159
|
-
),
|
|
160
|
-
ref,
|
|
161
|
-
...props
|
|
162
|
-
}
|
|
163
|
-
),
|
|
164
|
-
loading && /* @__PURE__ */ jsx3(Loader2, { className: "size-4 flex-shrink-0 animate-spin text-ods-text-secondary md:size-6" }),
|
|
165
|
-
!loading && endAdornment && /* @__PURE__ */ jsx3("span", { className: "flex-shrink-0 text-ods-text-secondary transition-colors duration-200 text-h6 group-has-[:focus]:text-ods-accent group-data-[invalid]:text-ods-error [&_svg]:size-4 md:[&_svg]:size-6", children: endAdornment })
|
|
166
|
-
]
|
|
167
|
-
}
|
|
168
|
-
);
|
|
169
|
-
return /* @__PURE__ */ jsx3(FieldWrapper, { label, labelVariant, error, errorVariant, children: content });
|
|
170
|
-
}
|
|
171
|
-
);
|
|
172
|
-
Input.displayName = "Input";
|
|
173
|
-
|
|
174
|
-
// src/components/ui/checkbox.tsx
|
|
175
|
-
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
176
|
-
import { forwardRef as forwardRef3 } from "react";
|
|
177
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
178
|
-
var Checkbox = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
|
|
179
|
-
CheckboxPrimitive.Root,
|
|
180
|
-
{
|
|
181
|
-
ref,
|
|
182
|
-
className: cn(
|
|
183
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-ods-border bg-ods-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-ods-accent data-[state=checked]:bg-ods-accent",
|
|
184
|
-
className
|
|
185
|
-
),
|
|
186
|
-
...props,
|
|
187
|
-
children: /* @__PURE__ */ jsx4(CheckboxPrimitive.Indicator, { className: cn("flex items-center justify-center text-ods-text-on-accent"), children: /* @__PURE__ */ jsx4(CheckboxCheckmarkIcon, { size: 10 }) })
|
|
188
|
-
}
|
|
189
|
-
));
|
|
190
|
-
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
|
191
|
-
|
|
192
|
-
export {
|
|
193
|
-
RequiredMark,
|
|
194
|
-
FieldWrapper,
|
|
195
|
-
Input,
|
|
196
|
-
Checkbox
|
|
197
|
-
};
|
|
198
|
-
//# sourceMappingURL=chunk-IWISVG4P.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ui/required-mark.tsx","../src/components/ui/field-wrapper.tsx","../src/components/ui/input.tsx","../src/components/ui/checkbox.tsx"],"sourcesContent":["/**\n * The design system's required-field mark: an accent asterisk after the label.\n * `aria-hidden` on purpose — the CONTROL carries `required`/`aria-required` for\n * assistive tech; the star is only the visual. ONE owner, used by `Field`,\n * `FieldWrapper` and every form that marks its own labels.\n */\nexport function RequiredMark() {\n return (\n <span aria-hidden className=\"text-ods-accent\">\n *\n </span>\n );\n}\n","'use client';\n\nimport { type ReactNode, forwardRef } from 'react';\nimport { cn } from '../../utils/cn';\nimport { Label } from './label';\nimport { RequiredMark } from './required-mark';\n\nexport interface FieldWrapperProps {\n /** Label text displayed above the field */\n label?: string;\n /**\n * `id` of the control the label names. Without it the label is decoration:\n * clicking it focuses nothing and assistive tech reads the field unnamed,\n * which is why every caller that cared was rendering its own <Label> instead\n * of using `label` here.\n */\n htmlFor?: string;\n /**\n * Status message displayed below the field, on ONE line, ellipsized on\n * overflow (full text via the native `title`).\n *\n * It is positioned OUT OF FLOW (absolute, hanging below the wrapper), so it\n * never changes the field's height when it appears — the same treatment\n * `CheckboxBlock` uses. The trade is that it overlaps whatever sits directly\n * below: a form stacking fields needs at least ~20px of vertical gap (e.g.\n * `gap-[var(--spacing-system-lf)]`) for the message to land in clear space.\n */\n error?: string;\n /** Color variant for the message: \"error\" (red), \"warning\" (yellow), \"success\" (green) or \"muted\" (grey) */\n errorVariant?: 'error' | 'warning' | 'success' | 'muted';\n /**\n * Label scale. Default is the standard form-label scale (text-h6).\n * 'large' (text-h4) is for screens whose design specifies body-scale field\n * titles (e.g. the devices New Device page) — opt in per call site, the\n * default stays the converged small scale.\n */\n labelVariant?: 'default' | 'large';\n /**\n * Marks the label with the `RequiredMark` `ContactForm` draws on its required\n * fields, so a form mixing both never shows two conventions. Visual only — the\n * CONTROL carries `required`/`aria-required` for assistive tech; the star is\n * `aria-hidden`.\n */\n required?: boolean;\n /** Additional className for the outer wrapper */\n className?: string;\n children: ReactNode;\n}\n\nconst errorVariantClasses = {\n error: 'text-ods-error',\n warning: 'text-ods-warning',\n success: 'text-ods-success',\n muted: 'text-ods-text-secondary',\n} as const;\n\nconst FieldWrapper = forwardRef<HTMLDivElement, FieldWrapperProps>(\n (\n { label, htmlFor, error, errorVariant = 'error', labelVariant = 'default', required = false, className, children },\n ref,\n ) => {\n const hasChrome = label != null || error != null;\n\n return (\n <div ref={ref} className={cn(hasChrome ? 'relative flex w-full flex-col' : 'contents', className)}>\n {label && (\n // Default is text-h6, NOT text-h4: `Field` (ui/field.tsx) renders its label via\n // `Label variant=\"small\"` = text-h6, and every form that mixes\n // Field-wrapped controls with `label=`-prop controls showed two label\n // sizes side by side — the exact inconsistency the admin editors kept\n // reporting. One label scale, owned here and in Field together.\n <Label className=\"mb-1\" htmlFor={htmlFor} variant={labelVariant}>\n {label}\n {required && <RequiredMark />}\n </Label>\n )}\n {children}\n {error && (\n <p\n className={cn(\n 'absolute bottom-0 left-0 right-0 translate-y-full truncate text-h6',\n errorVariantClasses[errorVariant],\n )}\n title={error}\n >\n {error}\n </p>\n )}\n </div>\n );\n },\n);\nFieldWrapper.displayName = 'FieldWrapper';\n\nexport { FieldWrapper };\n","'use client';\n\nimport { Loader2 } from 'lucide-react';\nimport { type InputHTMLAttributes, type ReactNode, forwardRef } from 'react';\nimport { cn } from '../../utils/cn';\nimport { FieldWrapper } from './field-wrapper';\n\nexport interface InputProps extends InputHTMLAttributes<HTMLInputElement> {\n /** When true, renders error border & ring */\n invalid?: boolean;\n /** Element displayed at the start (left) of the input */\n startAdornment?: ReactNode;\n /** Element displayed at the end (right) of the input */\n endAdornment?: ReactNode;\n /** Label text displayed above the input */\n label?: string;\n /** Label scale forwarded to FieldWrapper ('large' = text-h4 for designs with body-scale field titles) */\n labelVariant?: 'default' | 'large';\n /** Status message displayed below the input */\n error?: string;\n /** Color variant for the message: \"error\" (red), \"warning\" (yellow), \"success\" (green) or \"muted\" (grey).\n Only \"error\" and \"warning\" mark the field invalid (colored border). */\n errorVariant?: 'error' | 'warning' | 'success' | 'muted';\n /** When true, shows a loading spinner as end adornment */\n loading?: boolean;\n}\n\nconst invalidBorderClasses = {\n error: 'border-ods-error hover:border-ods-error has-[:focus]:border-ods-error',\n warning: '!border-ods-warning hover:!border-ods-warning has-[:focus]:!border-ods-warning',\n} as const;\n\nconst Input = forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n type,\n invalid = false,\n startAdornment,\n endAdornment,\n label,\n labelVariant,\n error,\n errorVariant = 'error',\n loading = false,\n ...props\n },\n ref,\n ) => {\n // success/muted are informational — they never paint the invalid border\n const variantIsInvalid = errorVariant === 'error' || errorVariant === 'warning';\n const isInvalid = invalid || (!!error && variantIsInvalid);\n\n // Range inputs get a clean slider rendering — no label wrapper, borders, or adornments\n if (type === 'range') {\n const rangeInput = (\n <input\n type=\"range\"\n className={cn(\n 'h-1 w-full cursor-pointer appearance-none rounded-full bg-white/30',\n // Webkit (Chrome/Safari) thumb\n '[&::-webkit-slider-thumb]:appearance-none',\n '[&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3',\n '[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white',\n '[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:shadow-sm',\n // Firefox thumb\n '[&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:w-3',\n '[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-white',\n '[&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:border-0',\n // Firefox track\n '[&::-moz-range-track]:bg-transparent',\n // Disabled\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n return label ? (\n <FieldWrapper label={label} labelVariant={labelVariant} error={error} errorVariant={errorVariant}>\n {rangeInput}\n </FieldWrapper>\n ) : (\n rangeInput\n );\n }\n\n const content = (\n <label\n data-invalid={isInvalid || undefined}\n className={cn(\n // Layout & spacing\n 'flex h-11 w-full cursor-text items-center gap-2 rounded-[6px] border px-3 md:h-12',\n // Focus-within states\n 'has-[:focus-visible]:outline-none',\n 'group',\n // Animations & touch UX\n 'transition-colors duration-200',\n // Theme palette\n 'border-ods-border bg-ods-card has-[:focus]:border-ods-accent',\n // Hover & active (not disabled)\n !props.disabled &&\n 'hover:border-ods-border-hover hover:bg-ods-bg-hover active:border-ods-border-active active:bg-ods-bg-active',\n // Disabled. The adornments (and any icon inside them) carry their own\n // `text-ods-text-secondary`, so grey them from here — a disabled field\n // must read as one flat colour, not grey text next to a live icon.\n // Scoped to the DIRECT span children (the two adornment wrappers): a\n // descendant rule would also repaint whatever the caller renders INSIDE\n // an adornment, which is passed verbatim precisely so it can own its\n // colour (a `Tag` there went invisible against its own fill).\n props.disabled && '!cursor-not-allowed bg-ods-bg',\n 'has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled',\n // Invalid\n isInvalid && invalidBorderClasses[variantIsInvalid ? errorVariant : 'error'],\n className,\n )}\n >\n {startAdornment && (\n <span className=\"flex-shrink-0 text-ods-text-secondary transition-colors duration-200 text-h6 group-has-[:focus]:text-ods-accent group-data-[invalid]:text-ods-error [&_svg]:size-4 md:[&_svg]:size-6\">\n {startAdornment}\n </span>\n )}\n <input\n type={type}\n className={cn(\n // Layout\n 'min-w-0 flex-1 border-none bg-transparent outline-none',\n // Typography\n 'text-h4',\n // Colors\n 'text-ods-text-primary placeholder:text-ods-text-secondary',\n // File input adjustments\n 'file:border-0 file:bg-transparent',\n // Disabled\n 'disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border',\n // Touch\n 'touch-manipulation',\n // Autofill override\n '[&:-webkit-autofill]:[-webkit-box-shadow:0_0_0_9999px_transparent_inset] [&:-webkit-autofill]:[-webkit-text-fill-color:var(--color-text-primary)] [&:-webkit-autofill]:[caret-color:var(--color-text-primary)] [&:-webkit-autofill]:[transition:background-color_9999s_ease-in-out_0s]',\n )}\n ref={ref}\n {...props}\n />\n {loading && <Loader2 className=\"size-4 flex-shrink-0 animate-spin text-ods-text-secondary md:size-6\" />}\n {!loading && endAdornment && (\n <span className=\"flex-shrink-0 text-ods-text-secondary transition-colors duration-200 text-h6 group-has-[:focus]:text-ods-accent group-data-[invalid]:text-ods-error [&_svg]:size-4 md:[&_svg]:size-6\">\n {endAdornment}\n </span>\n )}\n </label>\n );\n\n return (\n <FieldWrapper label={label} labelVariant={labelVariant} error={error} errorVariant={errorVariant}>\n {content}\n </FieldWrapper>\n );\n },\n);\nInput.displayName = 'Input';\n\nexport { Input };\n","'use client';\n\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox';\nimport { type ComponentPropsWithoutRef, type ElementRef, forwardRef } from 'react';\n\nimport { cn } from '../../utils/cn';\nimport { CheckboxCheckmarkIcon } from '../icons-v2-generated/signs-and-symbols/checkbox-checkmark-icon';\n\nconst Checkbox = forwardRef<\n ElementRef<typeof CheckboxPrimitive.Root>,\n ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n 'peer h-4 w-4 shrink-0 rounded-sm border border-ods-border bg-ods-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-ods-accent data-[state=checked]:bg-ods-accent',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-ods-text-on-accent')}>\n <CheckboxCheckmarkIcon size={10} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n));\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\n\nexport { Checkbox };\n"],"mappings":"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","names":["jsx","forwardRef","jsx","jsxs","forwardRef","forwardRef","jsx","forwardRef"]}
|