@inglorious/ui 7.2.0 → 7.2.2

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.
@@ -21,66 +21,9 @@ import {
21
21
 
22
22
  const NO_FOCUSED_INDEX = -1
23
23
 
24
- /**
25
- * Registered outside-pointer listeners, keyed by the dropdown element they
26
- * belong to. Used only to deduplicate: the ref callback is handed a fresh arrow
27
- * on every re-render, so without this each pass would stack another listener on
28
- * the document.
29
- * @type {WeakMap<Element, { props: ComboboxProps, listener: (event: Event) => void }>}
30
- */
31
- const outsideListeners = new WeakMap()
32
-
33
- /**
34
- * Listen on the document for presses outside the dropdown, closing it when one
35
- * happens.
36
- *
37
- * This listens for `pointerdown` rather than `click` on purpose. Mobile browsers
38
- * synthesise `click` asynchronously after `touchend`, and drop it entirely when
39
- * the touch drifts into a scroll gesture or the target is replaced meanwhile.
40
- * A suppressed click would leave the listener armed, and it would then close the
41
- * *next* dropdown on the tap that opened it, leaving the combobox looking stuck.
42
- * `pointerdown` is dispatched synchronously for mouse, touch and pen.
43
- *
44
- * No id is needed: the element keys the listener, and it retires itself once its
45
- * dropdown has left the DOM.
46
- * @param {ComboboxProps} props
47
- * @param {Element|null|undefined} el dropdown element, or undefined on unmount
48
- */
49
- function trackOutsideClick(props, el) {
50
- if (!el) return
51
-
52
- const existing = outsideListeners.get(el)
53
- if (existing) {
54
- // A re-render of the same dropdown: keep the one listener, but point it at
55
- // the current props so onClose is not captured from the first render.
56
- existing.props = props
57
- return
58
- }
59
-
60
- const entry = { props }
61
- entry.listener = (event) => {
62
- // The dropdown left the DOM: this press is the last one to hear from it.
63
- // Retiring here is what keeps a torn-down dropdown from closing whatever
64
- // opens next.
65
- if (!el.isConnected) {
66
- document.removeEventListener("pointerdown", entry.listener)
67
- return
68
- }
69
-
70
- // The root, not the dropdown: the control is a sibling of the dropdown, and a
71
- // press on it has to count as inside. Resolved per event rather than at mount
72
- // because the dropdown is not connected yet when the ref first runs.
73
- const root = el.closest(".iw-combobox") ?? el
74
- if (!event.composedPath().includes(root)) entry.props.onClose?.()
75
- }
76
-
77
- outsideListeners.set(el, entry)
78
- document.addEventListener("pointerdown", entry.listener)
79
- }
80
-
81
24
  export const Combobox = {
82
25
  /**
83
- * Main entrypoint for the combobox control. It renders the wrapper, label, control, and dropdown when open.
26
+ * Main entrypoint for the combobox control. It renders the frame, label, control, and dropdown when open.
84
27
  * Comboboxes support search, single or multi-select values, and clearable selection.
85
28
  * @param {ComboboxProps} props
86
29
  * @returns {TemplateResult}
@@ -90,72 +33,144 @@ export const Combobox = {
90
33
  },
91
34
 
92
35
  /**
93
- * Main entrypoint for the combobox control. It renders the wrapper, label, control, and dropdown when open.
36
+ * Main entrypoint for the combobox control. It renders the frame, label, control, and dropdown when open.
94
37
  * Comboboxes support search, single or multi-select values, and clearable selection.
95
38
  * @param {ComboboxProps} props
96
39
  * @returns {TemplateResult}
97
40
  */
98
41
  renderCombobox(props) {
99
- return html`<div
100
- class=${classMap({
101
- "iw-combobox": true,
102
- "iw-combobox-full-width": !!props.isFullWidth,
103
- [`iw-combobox-${props.size}`]: props.size !== "md",
104
- })}
105
- >
106
- ${
107
- props.label
108
- ? html`<label class="iw-combobox-label">${props.label}</label>`
109
- : null
110
- }
111
- ${this.renderControl?.(props)}
112
- ${when(props.isOpen, () => this.renderDropdown?.(props))}
42
+ const {
43
+ id,
44
+ type, // eslint-disable-line no-unused-vars
45
+ label,
46
+ hint,
47
+ error,
48
+ isRequired = false,
49
+ isFullWidth = false,
50
+ isOpen = false,
51
+ } = props
52
+ // The control is a div, not a labellable element, so a `for` binding is not
53
+ // available. It is referenced by id from the control's aria-labelledby
54
+ // instead, which is what a screen reader actually reads here.
55
+ // Only minted when there is something to point at. A reference to an id
56
+ // that is not in the DOM is worse than no reference at all.
57
+ const labelId = label && id ? `${id}-label` : ""
58
+ const messageId = (error || hint) && id ? `${id}-message` : ""
59
+
60
+ const comboboxClasses = {
61
+ "iw-combobox": true,
62
+ "iw-combobox-full-width": !!isFullWidth,
63
+ }
64
+
65
+ return html`<div class=${classMap(comboboxClasses)}>
66
+ ${when(
67
+ label,
68
+ () =>
69
+ html`<label id=${labelId || ""} class="iw-combobox-label">
70
+ ${label}
71
+ ${when(
72
+ isRequired,
73
+ () => html`<span class="iw-combobox-required">*</span>`,
74
+ )}
75
+ </label>`,
76
+ )}
77
+ ${this.renderControl?.({ ...props, labelId, messageId })}
78
+ ${when(isOpen, () => this.renderDropdown?.(props))}
79
+ ${when(
80
+ error,
81
+ () =>
82
+ html`<span id=${messageId ?? ""} class="iw-combobox-error-message"
83
+ >${error}</span
84
+ >`,
85
+ )}
86
+ ${when(
87
+ !error && hint,
88
+ () =>
89
+ html`<span id=${messageId || ""} class="iw-combobox-hint"
90
+ >${hint}</span
91
+ >`,
92
+ )}
113
93
  </div>`
114
94
  },
115
95
 
116
96
  /**
117
97
  * Renders the clickable control element that shows the current selection and toggles the dropdown.
118
98
  * Handles placeholder, clear button, and the open/disabled state.
99
+ *
100
+ * The control is the only way into a closed combobox, so it has to be
101
+ * focusable: it was a plain div with a click handler, which meant tabbing
102
+ * skipped the field entirely and none of the keyboard handling was reachable.
103
+ * It keeps focus while the dropdown is open unless a search input takes it.
119
104
  * @param {ComboboxProps} props
120
105
  * @returns {TemplateResult}
121
106
  */
122
107
  renderControl(props) {
108
+ const {
109
+ id,
110
+ labelId,
111
+ messageId,
112
+ selectedValue,
113
+ error,
114
+ size = "md",
115
+ isDisabled = false,
116
+ isRequired = false,
117
+ isOpen = false,
118
+ isMulti = false,
119
+ isClearable = false,
120
+ onToggle,
121
+ onClear,
122
+ } = props
123
+
124
+ const controlId = id ? `${id}-control` : ""
125
+
126
+ const frameClasses = {
127
+ "iw-combobox-frame": true,
128
+ [`iw-combobox-${size}`]: true,
129
+ "iw-combobox-frame-open": isOpen,
130
+ "iw-combobox-frame-disabled": isDisabled,
131
+ "iw-combobox-frame-error": !!error,
132
+ "iw-combobox-frame-selection":
133
+ isMulti && Array.isArray(selectedValue) && selectedValue.length,
134
+ }
135
+
123
136
  return html`<div
124
- class="iw-combobox-control ${classMap({
125
- "iw-combobox-control-open": props.isOpen,
126
- "iw-combobox-control-disabled": props.isDisabled,
127
- "iw-combobox-control-selection":
128
- props.isMulti &&
129
- Array.isArray(props.selectedValue) &&
130
- props.selectedValue.length,
131
- })}"
132
- @click=${() => !props.isDisabled && props.onToggle?.()}
137
+ id=${controlId}
138
+ class="${classMap(frameClasses)}"
139
+ role="combobox"
140
+ aria-haspopup="listbox"
141
+ aria-expanded=${isOpen ? "true" : "false"}
142
+ aria-controls=${controlId ? `${controlId}-listbox` : ""}
143
+ aria-labelledby=${labelId || ""}
144
+ aria-describedby=${messageId || ""}
145
+ aria-required=${isRequired ? "true" : "false"}
146
+ aria-invalid=${error ? "true" : "false"}
147
+ tabindex=${isDisabled ? -1 : 0}
148
+ @keydown=${(event) => handleControlKeyDown(props, event)}
149
+ @click=${() => !isDisabled && onToggle?.()}
133
150
  >
134
151
  ${when(
135
- props.isMulti,
152
+ isMulti,
136
153
  () => this.renderMultiValue?.(props),
137
154
  () => this.renderSingleValue?.(props),
138
155
  )}
139
156
  ${when(
140
- props.isClearable &&
141
- ((props.isMulti &&
142
- Array.isArray(props.selectedValue) &&
143
- props.selectedValue.length) ||
144
- (!props.isMulti && props.selectedValue !== null)),
157
+ isClearable &&
158
+ ((isMulti && Array.isArray(selectedValue) && selectedValue.length) ||
159
+ (!isMulti && selectedValue !== null)),
145
160
  () =>
146
161
  html`<button
147
162
  class="iw-combobox-clear"
148
163
  type="button"
149
164
  @click=${(event) => {
150
165
  event.stopPropagation()
151
- props.onClear?.()
166
+ onClear?.()
152
167
  }}
153
168
  >
154
169
  ×
155
170
  </button>`,
156
171
  )}
157
172
 
158
- <div class="iw-combobox-arrow"><span>▼</span></div>
173
+ <span class="iw-combobox-arrow" aria-hidden="true">▼</span>
159
174
  </div>`
160
175
  },
161
176
 
@@ -166,21 +181,21 @@ export const Combobox = {
166
181
  * @returns {TemplateResult}
167
182
  */
168
183
  renderSingleValue(props) {
169
- if (props.selectedValue === null) {
170
- return html`<span class="iw-combobox-placeholder"
171
- >${props.placeholder}</span
184
+ const { options, selectedValue, placeholder } = props
185
+
186
+ if (selectedValue === null) {
187
+ return html`<span class="iw-combobox-control iw-combobox-placeholder"
188
+ >${placeholder}</span
172
189
  >`
173
190
  }
174
191
 
175
- const selectedOption = props.options.find(
176
- (option) => getOptionValue(option) === props.selectedValue,
192
+ const selectedOption = options.find(
193
+ (option) => getOptionValue(option) === selectedValue,
177
194
  )
178
195
 
179
- return html`<span class="iw-combobox-value"
196
+ return html`<span class="iw-combobox-control iw-combobox-value"
180
197
  >${
181
- selectedOption
182
- ? getOptionLabel(selectedOption)
183
- : String(props.selectedValue)
198
+ selectedOption ? getOptionLabel(selectedOption) : String(selectedValue)
184
199
  }</span
185
200
  >`
186
201
  },
@@ -192,15 +207,17 @@ export const Combobox = {
192
207
  * @returns {TemplateResult}
193
208
  */
194
209
  renderMultiValue(props) {
195
- if (!Array.isArray(props.selectedValue) || !props.selectedValue.length) {
196
- return html`<span class="iw-combobox-placeholder"
197
- >${props.placeholder}</span
210
+ const { selectedValue, placeholder } = props
211
+
212
+ if (!Array.isArray(selectedValue) || !selectedValue.length) {
213
+ return html`<span class="iw-combobox-control iw-combobox-placeholder"
214
+ >${placeholder}</span
198
215
  >`
199
216
  }
200
217
 
201
- return html`<div class="iw-combobox-multi-value">
218
+ return html`<div class="iw-combobox-control iw-combobox-multi-value">
202
219
  ${repeat(
203
- props.selectedValue,
220
+ selectedValue,
204
221
  (value) => value,
205
222
  (value) => this.renderMultiValueTag?.(props, value),
206
223
  )}
@@ -215,9 +232,11 @@ export const Combobox = {
215
232
  * @returns {TemplateResult}
216
233
  */
217
234
  renderMultiValueTag(props, value) {
218
- const option = props.options.find((opt) => getOptionValue(opt) === value)
219
- const tagId = `${props.id}-tag-${String(value)}`
220
- const chipSize = props.size === "lg" ? "md" : "sm"
235
+ const { id, options, size, onOptionSelect, onChange } = props
236
+
237
+ const option = options.find((opt) => getOptionValue(opt) === value)
238
+ const tagId = `${id}-tag-${String(value)}`
239
+ const chipSize = size === "lg" ? "md" : "sm"
221
240
 
222
241
  return html`<span
223
242
  class="iw-combobox-multi-value-tag"
@@ -230,8 +249,8 @@ export const Combobox = {
230
249
  size: chipSize,
231
250
  shape: "rounded",
232
251
  onClick: () => {
233
- props.onOptionSelect?.(option)
234
- props.onChange?.(option)
252
+ onOptionSelect?.(option)
253
+ onChange?.(option)
235
254
  },
236
255
  })}
237
256
  </span>`
@@ -244,18 +263,22 @@ export const Combobox = {
244
263
  * @returns {TemplateResult}
245
264
  */
246
265
  renderDropdown(props) {
247
- const filteredOptions = filterOptions(props.options, props.searchTerm)
266
+ const { id, options, searchTerm, isSearchable, isLoading } = props
267
+
268
+ const filteredOptions = filterOptions(options, searchTerm)
248
269
 
249
270
  return html`<div
271
+ id=${id ? `${id}-control-listbox` : ""}
250
272
  class="iw-combobox-dropdown"
273
+ role="listbox"
251
274
  ${ref((el) => trackOutsideClick(props, el))}
252
275
  >
253
- ${when(props.isSearchable, () => this.renderSearchInput?.(props))}
254
- ${when(props.isLoading, () => this.renderLoading?.(props))}
255
- ${when(!props.isLoading && !filteredOptions.length, () =>
276
+ ${when(isSearchable, () => this.renderSearchInput?.(props))}
277
+ ${when(isLoading, () => this.renderLoading?.(props))}
278
+ ${when(!isLoading && !filteredOptions.length, () =>
256
279
  this.renderNoOptions?.(props),
257
280
  )}
258
- ${when(!props.isLoading && filteredOptions.length, () =>
281
+ ${when(!isLoading && filteredOptions.length, () =>
259
282
  this.renderOptions?.(props),
260
283
  )}
261
284
  </div>`
@@ -268,15 +291,17 @@ export const Combobox = {
268
291
  * @returns {TemplateResult}
269
292
  */
270
293
  renderSearchInput(props) {
294
+ const { searchTerm, isOpen, onSearchChange } = props
295
+
271
296
  return html`<input
272
297
  class="iw-combobox-dropdown-search"
273
298
  type="text"
274
299
  placeholder="Search..."
275
- .value=${props.searchTerm ?? ""}
276
- @input=${(event) => props.onSearchChange?.(event.target.value)}
300
+ .value=${searchTerm ?? ""}
301
+ @input=${(event) => onSearchChange?.(event.target.value)}
277
302
  @keydown=${(event) => handleKeyDown(props, event)}
278
303
  ${ref((el) => {
279
- if (el && props.isOpen) queueMicrotask(() => el.focus())
304
+ if (el && isOpen) queueMicrotask(() => el.focus())
280
305
  })}
281
306
  />`
282
307
  },
@@ -288,7 +313,9 @@ export const Combobox = {
288
313
  * @returns {TemplateResult}
289
314
  */
290
315
  renderOptions(props) {
291
- const filteredOptions = filterOptions(props.options, props.searchTerm)
316
+ const { options, searchTerm } = props
317
+
318
+ const filteredOptions = filterOptions(options, searchTerm)
292
319
 
293
320
  return html`<div class="iw-combobox-dropdown-options">
294
321
  ${repeat(
@@ -307,15 +334,24 @@ export const Combobox = {
307
334
  * @returns {TemplateResult}
308
335
  */
309
336
  renderOption(props, { option, index }) {
337
+ const {
338
+ id,
339
+ selectedValue,
340
+ focusedIndex,
341
+ isMulti,
342
+ onOptionSelect,
343
+ onChange,
344
+ onFocusSet,
345
+ } = props
346
+
310
347
  const normalized = formatOption(option)
311
- const isSelected = isOptionSelected(
312
- option,
313
- props.selectedValue,
314
- props.isMulti,
315
- )
316
- const isFocused = index === props.focusedIndex
348
+ const isSelected = isOptionSelected(option, selectedValue, isMulti)
349
+ const isFocused = index === focusedIndex
317
350
 
318
351
  return html`<div
352
+ id=${id ? `${id}-option-${index}` : ""}
353
+ role="option"
354
+ aria-selected=${isSelected ? "true" : "false"}
319
355
  class="iw-combobox-dropdown-options-option ${classMap({
320
356
  "iw-combobox-dropdown-options-option-selected": isSelected,
321
357
  "iw-combobox-dropdown-options-option-focused": isFocused,
@@ -323,13 +359,13 @@ export const Combobox = {
323
359
  })}"
324
360
  @click=${() => {
325
361
  if (normalized.isDisabled) return
326
- props.onOptionSelect?.(normalized)
327
- props.onChange?.(normalized)
362
+ onOptionSelect?.(normalized)
363
+ onChange?.(normalized)
328
364
  }}
329
- @mouseenter=${() => props.onFocusSet?.(index)}
365
+ @mouseenter=${() => onFocusSet?.(index)}
330
366
  >
331
367
  ${when(
332
- props.isMulti,
368
+ isMulti,
333
369
  () =>
334
370
  html`<input
335
371
  type="checkbox"
@@ -348,7 +384,9 @@ export const Combobox = {
348
384
  * @returns {TemplateResult}
349
385
  */
350
386
  renderLoading(props) {
351
- return html`<div class="iw-combobox-loading">${props.loadingMessage}</div>`
387
+ const { loadingMessage } = props
388
+
389
+ return html`<div class="iw-combobox-loading">${loadingMessage}</div>`
352
390
  },
353
391
 
354
392
  /**
@@ -358,55 +396,148 @@ export const Combobox = {
358
396
  * @returns {TemplateResult}
359
397
  */
360
398
  renderNoOptions(props) {
361
- return html`<div class="iw-combobox-no-options">
362
- ${props.noOptionsMessage}
363
- </div>`
399
+ const { noOptionsMessage } = props
400
+ return html`<div class="iw-combobox-no-options">${noOptionsMessage}</div>`
364
401
  },
365
402
  }
366
403
 
404
+ /**
405
+ * Opens a closed combobox from the keyboard, and navigates an open one.
406
+ *
407
+ * The control keeps DOM focus whenever the combobox is not searchable — there
408
+ * is no search input to hand focus to — so while open it drives the same
409
+ * navigation handleKeyDown() gives the search input, rather than toggling.
410
+ * @param {ComboboxProps} props
411
+ * @param {KeyboardEvent} event
412
+ */
413
+ function handleControlKeyDown(props, event) {
414
+ const { isDisabled, isOpen, onToggle } = props
415
+
416
+ if (isDisabled) return
417
+
418
+ if (isOpen) {
419
+ handleKeyDown(props, event)
420
+ return
421
+ }
422
+
423
+ if (event.key !== "Enter" && event.key !== " " && event.key !== "ArrowDown") {
424
+ return
425
+ }
426
+ // Space would otherwise scroll the page out from under the dropdown.
427
+ event.preventDefault()
428
+ onToggle?.()
429
+ }
430
+
367
431
  /**
368
432
  * Handle key navigation.
369
433
  * @param {ComboboxProps} props
370
434
  * @param {KeyboardEvent} event
371
435
  */
372
436
  function handleKeyDown(props, event) {
373
- const filteredOptions = filterOptions(props.options, props.searchTerm)
437
+ const {
438
+ options,
439
+ searchTerm,
440
+ focusedIndex,
441
+ onFocusFirst,
442
+ onFocusPrev,
443
+ onFocusNext,
444
+ onFocusLast,
445
+ onOptionSelect,
446
+ onChange,
447
+ onClose,
448
+ } = props
449
+
450
+ const filteredOptions = filterOptions(options, searchTerm)
374
451
 
375
452
  switch (event.key) {
376
453
  case "ArrowDown":
377
454
  event.preventDefault()
378
- props.onFocusNext?.()
455
+ onFocusNext?.()
379
456
  break
380
457
  case "ArrowUp":
381
458
  event.preventDefault()
382
- props.onFocusPrev?.()
459
+ onFocusPrev?.()
383
460
  break
384
461
  case "Enter":
385
462
  event.preventDefault()
386
- if (
387
- props.focusedIndex > NO_FOCUSED_INDEX &&
388
- filteredOptions[props.focusedIndex]
389
- ) {
390
- const option = formatOption(filteredOptions[props.focusedIndex])
463
+ if (focusedIndex > NO_FOCUSED_INDEX && filteredOptions[focusedIndex]) {
464
+ const option = formatOption(filteredOptions[focusedIndex])
391
465
  if (!option.isDisabled) {
392
- props.onOptionSelect?.(option)
393
- props.onChange?.(option)
466
+ onOptionSelect?.(option)
467
+ onChange?.(option)
394
468
  }
395
469
  }
396
470
  break
397
471
  case "Escape":
398
472
  event.preventDefault()
399
- props.onClose?.()
473
+ onClose?.()
400
474
  break
401
475
  case "Home":
402
476
  event.preventDefault()
403
- props.onFocusFirst?.()
477
+ onFocusFirst?.()
404
478
  break
405
479
  case "End":
406
480
  event.preventDefault()
407
- props.onFocusLast?.()
481
+ onFocusLast?.()
408
482
  break
409
483
  default:
410
484
  break
411
485
  }
412
486
  }
487
+
488
+ /**
489
+ * Registered outside-pointer listeners, keyed by the dropdown element they
490
+ * belong to. Used only to deduplicate: the ref callback is handed a fresh arrow
491
+ * on every re-render, so without this each pass would stack another listener on
492
+ * the document.
493
+ * @type {WeakMap<Element, { props: ComboboxProps, listener: (event: Event) => void }>}
494
+ */
495
+ const outsideListeners = new WeakMap()
496
+
497
+ /**
498
+ * Listen on the document for presses outside the dropdown, closing it when one
499
+ * happens.
500
+ *
501
+ * This listens for `pointerdown` rather than `click` on purpose. Mobile browsers
502
+ * synthesise `click` asynchronously after `touchend`, and drop it entirely when
503
+ * the touch drifts into a scroll gesture or the target is replaced meanwhile.
504
+ * A suppressed click would leave the listener armed, and it would then close the
505
+ * *next* dropdown on the tap that opened it, leaving the combobox looking stuck.
506
+ * `pointerdown` is dispatched synchronously for mouse, touch and pen.
507
+ *
508
+ * No id is needed: the element keys the listener, and it retires itself once its
509
+ * dropdown has left the DOM.
510
+ * @param {ComboboxProps} props
511
+ * @param {Element|null|undefined} el dropdown element, or undefined on unmount
512
+ */
513
+ function trackOutsideClick(props, el) {
514
+ if (!el) return
515
+
516
+ const existing = outsideListeners.get(el)
517
+ if (existing) {
518
+ // A re-render of the same dropdown: keep the one listener, but point it at
519
+ // the current props so onClose is not captured from the first render.
520
+ existing.props = props
521
+ return
522
+ }
523
+
524
+ const entry = { props }
525
+ entry.listener = (event) => {
526
+ // The dropdown left the DOM: this press is the last one to hear from it.
527
+ // Retiring here is what keeps a torn-down dropdown from closing whatever
528
+ // opens next.
529
+ if (!el.isConnected) {
530
+ document.removeEventListener("pointerdown", entry.listener)
531
+ return
532
+ }
533
+
534
+ // The root, not the dropdown: the control is a sibling of the dropdown, and a
535
+ // press on it has to count as inside. Resolved per event rather than at mount
536
+ // because the dropdown is not connected yet when the ref first runs.
537
+ const root = el.closest(".iw-combobox") ?? el
538
+ if (!event.composedPath().includes(root)) entry.props.onClose?.()
539
+ }
540
+
541
+ outsideListeners.set(el, entry)
542
+ document.addEventListener("pointerdown", entry.listener)
543
+ }
@@ -1,3 +1,3 @@
1
- import * as renderers from "./template.js"
1
+ import { Input as renderers } from "./template.js"
2
2
 
3
3
  export const Input = { ...renderers }
@@ -174,6 +174,25 @@ WithError.args = {
174
174
  error: "Please enter a valid email address",
175
175
  }
176
176
 
177
+ export const WithIcon = {}
178
+ WithIcon.args = {
179
+ ...Default.args,
180
+ icon: "@",
181
+ }
182
+
183
+ export const WithIconAfter = {}
184
+ WithIconAfter.args = {
185
+ ...Default.args,
186
+ iconAfter: "@",
187
+ }
188
+
189
+ export const WithBothIcons = {}
190
+ WithBothIcons.args = {
191
+ ...Default.args,
192
+ icon: "@",
193
+ iconAfter: "@",
194
+ }
195
+
177
196
  export const Required = {}
178
197
  Required.args = {
179
198
  ...Default.args,
@@ -206,6 +225,13 @@ Small.args = {
206
225
  size: "sm",
207
226
  }
208
227
 
228
+ export const Medium = {}
229
+ Medium.args = {
230
+ ...Default.args,
231
+ label: "Medium Input",
232
+ size: "md",
233
+ }
234
+
209
235
  export const Large = {}
210
236
  Large.args = {
211
237
  ...Default.args,
@@ -286,3 +312,12 @@ Color.args = {
286
312
  inputType: "color",
287
313
  value: "#7c3aed",
288
314
  }
315
+
316
+ export const Password = {}
317
+ Password.args = {
318
+ ...Default.args,
319
+ label: "Password",
320
+ inputType: "password",
321
+ placeholder: "••••••••",
322
+ iconAfter: "@",
323
+ }