@@ -30,7 +30,6 @@ type Story = StoryObj<typeof meta>;
3030export const Default : Story = {
3131 args : {
3232 magnitude : "md" ,
33- tone : "neutral" ,
3433 orientation : "vertical" ,
3534 label : "Email" ,
3635 placeholder : "you@example.com" ,
@@ -43,7 +42,6 @@ export const Default: Story = {
4342export const Horizontal : Story = {
4443 args : {
4544 magnitude : "md" ,
46- tone : "neutral" ,
4745 orientation : "horizontal" ,
4846 label : "Email" ,
4947 placeholder : "you@example.com" ,
@@ -57,29 +55,21 @@ export const Horizontal: Story = {
5755 */
5856export const HorizontalShowcase : Story = {
5957 // Required axes for the args table; the custom `render` ignores them.
60- args : { magnitude : "md" , tone : "neutral" , orientation : "horizontal" } ,
58+ args : { magnitude : "md" , orientation : "horizontal" } ,
6159 parameters : { controls : { disable : true } } ,
6260 render : ( ) => (
6361 < div className = "flex w-110 flex-col gap-4" >
64- < InputField
65- orientation = "horizontal"
66- magnitude = "md"
67- tone = "neutral"
68- label = "Email"
69- placeholder = "md"
70- />
62+ < InputField orientation = "horizontal" magnitude = "md" label = "Email" placeholder = "md" />
7163 < InputField
7264 orientation = "horizontal"
7365 magnitude = "lg"
74- tone = "neutral"
7566 label = "Email"
7667 placeholder = "lg"
7768 hint = "Use your work email."
7869 />
7970 < InputField
8071 orientation = "horizontal"
8172 magnitude = "xl"
82- tone = "neutral"
8373 label = "Email"
8474 placeholder = "xl"
8575 description = "We never share your email with anyone."
@@ -88,7 +78,6 @@ export const HorizontalShowcase: Story = {
8878 orientation = "horizontal"
8979 magnitude = "md"
9080 label = "Email"
91- tone = "danger"
9281 error = "Enter a valid email address"
9382 defaultValue = "not-an-email"
9483 />
@@ -100,7 +89,6 @@ export const HorizontalShowcase: Story = {
10089export const WithIcons : Story = {
10190 args : {
10291 magnitude : "md" ,
103- tone : "neutral" ,
10492 orientation : "vertical" ,
10593 label : "Search" ,
10694 placeholder : "Search…" ,
@@ -111,47 +99,37 @@ export const WithIcons: Story = {
11199
112100/**
113101 * The element-driven states side by side. Hover/focus/filled aren't props — they come from
114- * interacting with the control; `disabled` and the error treatment (`tone="danger" `) are shown
102+ * interacting with the control; `disabled` and the error treatment (set via `error `) are shown
115103 * statically.
116104 */
117105export const States : Story = {
118106 // Required axes for the args table; the custom `render` ignores them.
119- args : { magnitude : "md" , tone : "neutral" , orientation : "vertical" } ,
107+ args : { magnitude : "md" , orientation : "vertical" } ,
120108 parameters : { controls : { disable : true } } ,
121109 render : ( ) => (
122110 < div className = "flex w-72 flex-col gap-4" >
111+ < InputField magnitude = "md" orientation = "vertical" label = "Default" placeholder = "Placeholder" />
123112 < InputField
124113 magnitude = "md"
125- tone = "neutral"
126- orientation = "vertical"
127- label = "Default"
128- placeholder = "Placeholder"
129- />
130- < InputField
131- magnitude = "md"
132- tone = "neutral"
133114 orientation = "vertical"
134115 label = "Focus / hover (interact)"
135116 placeholder = "Click me"
136117 />
137118 < InputField
138119 magnitude = "md"
139- tone = "neutral"
140120 orientation = "vertical"
141121 label = "Filled"
142122 defaultValue = "Ada Lovelace"
143123 />
144124 < InputField
145125 magnitude = "md"
146- tone = "neutral"
147126 orientation = "vertical"
148127 label = "Disabled"
149128 placeholder = "Placeholder"
150129 disabled
151130 />
152131 < InputField
153132 magnitude = "md"
154- tone = "danger"
155133 orientation = "vertical"
156134 label = "Error"
157135 defaultValue = "not-an-email"
@@ -163,7 +141,7 @@ export const States: Story = {
163141
164142/** Every magnitude (`md` / `lg` / `xl`) stacked. */
165143export const Magnitudes : Story = {
166- args : { magnitude : "md" , tone : "neutral" , orientation : "vertical" , placeholder : "Placeholder" } ,
144+ args : { magnitude : "md" , orientation : "vertical" , placeholder : "Placeholder" } ,
167145 argTypes : { magnitude : { control : false } , label : { control : false } } ,
168146 render : ( args ) => (
169147 < div className = "flex w-72 flex-col gap-4" >
@@ -175,12 +153,11 @@ export const Magnitudes: Story = {
175153} ;
176154
177155/** The error treatment: danger border, danger helper text, and `aria-invalid`. */
178- export const Error : Story = {
156+ export const Invalid : Story = {
179157 args : {
180158 magnitude : "md" ,
181159 orientation : "vertical" ,
182160 label : "Email" ,
183- tone : "danger" ,
184161 defaultValue : "not-an-email" ,
185162 error : "Enter a valid email address." ,
186163 required : true ,
@@ -196,13 +173,12 @@ export const Error: Story = {
196173export const RtlVerify : Story = {
197174 name : "RTL Verify" ,
198175 tags : [ "!autodocs" , "!manifest" ] ,
199- args : { magnitude : "md" , tone : "neutral" , orientation : "vertical" } ,
176+ args : { magnitude : "md" , orientation : "vertical" } ,
200177 parameters : { controls : { disable : true } } ,
201178 render : ( ) => (
202179 < div dir = "rtl" className = "flex w-80 flex-col gap-6" >
203180 < InputField
204181 magnitude = "md"
205- tone = "neutral"
206182 orientation = "vertical"
207183 label = "البريد الإلكتروني"
208184 placeholder = "you@example.com"
@@ -211,14 +187,12 @@ export const RtlVerify: Story = {
211187 />
212188 < InputField
213189 magnitude = "md"
214- tone = "neutral"
215190 orientation = "horizontal"
216191 label = "البريد"
217192 placeholder = "you@example.com"
218193 />
219194 < InputField
220195 magnitude = "md"
221- tone = "neutral"
222196 orientation = "vertical"
223197 label = "بحث"
224198 placeholder = "Search…"
@@ -228,7 +202,6 @@ export const RtlVerify: Story = {
228202 < InputField
229203 magnitude = "md"
230204 orientation = "vertical"
231- tone = "danger"
232205 label = "البريد الإلكتروني"
233206 required
234207 defaultValue = "not-an-email"
@@ -242,13 +215,12 @@ export const RtlVerify: Story = {
242215export const LtrVerify : Story = {
243216 name : "LTR Verify" ,
244217 tags : [ "!autodocs" , "!manifest" ] ,
245- args : { magnitude : "md" , tone : "neutral" , orientation : "vertical" } ,
218+ args : { magnitude : "md" , orientation : "vertical" } ,
246219 parameters : { controls : { disable : true } } ,
247220 render : ( ) => (
248221 < div dir = "ltr" className = "flex w-80 flex-col gap-6" >
249222 < InputField
250223 magnitude = "md"
251- tone = "neutral"
252224 orientation = "vertical"
253225 label = "Email"
254226 placeholder = "you@example.com"
@@ -257,14 +229,12 @@ export const LtrVerify: Story = {
257229 />
258230 < InputField
259231 magnitude = "md"
260- tone = "neutral"
261232 orientation = "horizontal"
262233 label = "Email"
263234 placeholder = "you@example.com"
264235 />
265236 < InputField
266237 magnitude = "md"
267- tone = "neutral"
268238 orientation = "vertical"
269239 label = "Search"
270240 placeholder = "Search…"
@@ -274,7 +244,6 @@ export const LtrVerify: Story = {
274244 < InputField
275245 magnitude = "md"
276246 orientation = "vertical"
277- tone = "danger"
278247 label = "Email"
279248 required
280249 defaultValue = "not-an-email"
@@ -292,7 +261,6 @@ export const TypingUpdatesValue: Story = {
292261 tags : [ "!dev" , "!autodocs" , "!manifest" ] ,
293262 args : {
294263 magnitude : "md" ,
295- tone : "neutral" ,
296264 orientation : "vertical" ,
297265 label : "Name" ,
298266 placeholder : "Your name" ,
@@ -311,7 +279,6 @@ export const DisabledBlocksInput: Story = {
311279 tags : [ "!dev" , "!autodocs" , "!manifest" ] ,
312280 args : {
313281 magnitude : "md" ,
314- tone : "neutral" ,
315282 orientation : "vertical" ,
316283 label : "Name" ,
317284 placeholder : "Your name" ,
@@ -325,14 +292,13 @@ export const DisabledBlocksInput: Story = {
325292 } ,
326293} ;
327294
328- /** `tone="danger"` sets `aria-invalid` and renders the announced error text. */
295+ /** Setting `error` marks the field invalid ( `aria-invalid`) and renders the announced error text. */
329296export const ErrorAnnouncesInvalid : Story = {
330297 tags : [ "!dev" , "!autodocs" , "!manifest" ] ,
331298 args : {
332299 magnitude : "md" ,
333300 orientation : "vertical" ,
334301 label : "Email" ,
335- tone : "danger" ,
336302 defaultValue : "x" ,
337303 error : "Enter a valid email address." ,
338304 } ,
@@ -348,7 +314,6 @@ export const NativeAriaLabel: Story = {
348314 tags : [ "!dev" , "!autodocs" , "!manifest" ] ,
349315 args : {
350316 magnitude : "md" ,
351- tone : "neutral" ,
352317 orientation : "vertical" ,
353318 "aria-label" : "Search projects" ,
354319 placeholder : "Search" ,
@@ -357,3 +322,44 @@ export const NativeAriaLabel: Story = {
357322 await expect ( canvas . getByRole ( "textbox" , { name : "Search projects" } ) ) . toBeInTheDocument ( ) ;
358323 } ,
359324} ;
325+
326+ /**
327+ * Setting `error` marks the field invalid; Base UI's `Field.Root` propagates that validity to the
328+ * control as `data-invalid`, and the wrapping `InputBox` recolors its border to `danger` via
329+ * `:has([data-invalid])` — no `tone` prop. A resting field is shown alongside so the danger border
330+ * is assertably different.
331+ */
332+ export const InvalidShowsDangerBorder : Story = {
333+ tags : [ "!dev" , "!autodocs" , "!manifest" ] ,
334+ parameters : { controls : { disable : true } } ,
335+ args : { magnitude : "md" , orientation : "vertical" } ,
336+ render : ( ) => (
337+ < div className = "flex w-72 flex-col gap-4" >
338+ < InputField magnitude = "md" orientation = "vertical" label = "Resting" placeholder = "Resting" />
339+ < InputField
340+ magnitude = "md"
341+ orientation = "vertical"
342+ label = "Invalid"
343+ defaultValue = "not-an-email"
344+ error = "Enter a valid email address."
345+ />
346+ </ div >
347+ ) ,
348+ play : async ( { canvas } ) => {
349+ const resting = canvas . getByRole < HTMLInputElement > ( "textbox" , { name : "Resting" } ) ;
350+ const invalid = canvas . getByRole < HTMLInputElement > ( "textbox" , { name : "Invalid" } ) ;
351+ await expect ( invalid ) . toHaveAttribute ( "aria-invalid" , "true" ) ;
352+ // The box is the input's wrapping `div`; danger keys off `:has([data-invalid])` on it.
353+ const restingBox = resting . parentElement ;
354+ const invalidBox = invalid . parentElement ;
355+ // `Error` is shadowed by this file's `Error` story, so reach for the global constructor.
356+ if ( restingBox == null || invalidBox == null ) {
357+ throw new Error ( "expected an InputBox wrapper" ) ;
358+ }
359+ await expect ( invalidBox ) . toHaveClass ( "has-[[data-invalid]]:border-danger-strong" ) ;
360+ // ...and the danger border actually renders: its color differs from the resting box's border.
361+ await expect ( getComputedStyle ( invalidBox ) . borderColor ) . not . toBe (
362+ getComputedStyle ( restingBox ) . borderColor ,
363+ ) ;
364+ } ,
365+ } ;
0 commit comments