@spear-ai/spectral 1.20.0 → 1.20.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.
Files changed (117) hide show
  1. package/dist/Accordion.js +0 -4
  2. package/dist/Accordion.js.map +1 -1
  3. package/dist/Alert/AlertBase.d.ts +0 -1
  4. package/dist/Alert/AlertBase.d.ts.map +1 -1
  5. package/dist/Alert/AlertBase.js +14 -17
  6. package/dist/Alert/AlertBase.js.map +1 -1
  7. package/dist/Alert.js +16 -4
  8. package/dist/Alert.js.map +1 -1
  9. package/dist/AlertDialog.js +3 -3
  10. package/dist/AlertDialog.js.map +1 -1
  11. package/dist/Avatar.js +1 -9
  12. package/dist/Avatar.js.map +1 -1
  13. package/dist/Badge.js +0 -1
  14. package/dist/Badge.js.map +1 -1
  15. package/dist/Button.js +0 -3
  16. package/dist/Button.js.map +1 -1
  17. package/dist/ButtonGroup.js +0 -4
  18. package/dist/ButtonGroup.js.map +1 -1
  19. package/dist/ButtonIcon.js +0 -1
  20. package/dist/ButtonIcon.js.map +1 -1
  21. package/dist/ButtonIconSlideout.js +0 -3
  22. package/dist/ButtonIconSlideout.js.map +1 -1
  23. package/dist/Checkbox.d.ts.map +1 -1
  24. package/dist/Checkbox.js +5 -8
  25. package/dist/Checkbox.js.map +1 -1
  26. package/dist/Combobox.d.ts +1 -0
  27. package/dist/Combobox.d.ts.map +1 -1
  28. package/dist/Combobox.js +0 -4
  29. package/dist/Combobox.js.map +1 -1
  30. package/dist/ControlGroup/ControlGroupSelect.js +0 -5
  31. package/dist/ControlGroup/ControlGroupSelect.js.map +1 -1
  32. package/dist/DataCard/Card.js +0 -6
  33. package/dist/DataCard/Card.js.map +1 -1
  34. package/dist/DataCard.js +1 -7
  35. package/dist/DataCard.js.map +1 -1
  36. package/dist/DateTimePicker/Calendar.js +0 -1
  37. package/dist/DateTimePicker/Calendar.js.map +1 -1
  38. package/dist/DateTimePicker/DateTimeInput.js +0 -1
  39. package/dist/DateTimePicker/DateTimeInput.js.map +1 -1
  40. package/dist/DateTimePicker/TimePeriodSelect.js +0 -4
  41. package/dist/DateTimePicker/TimePeriodSelect.js.map +1 -1
  42. package/dist/DateTimePicker/TimePicker.js +0 -3
  43. package/dist/DateTimePicker/TimePicker.js.map +1 -1
  44. package/dist/DateTimePicker.js +0 -4
  45. package/dist/DateTimePicker.js.map +1 -1
  46. package/dist/Dialog.js +0 -16
  47. package/dist/Dialog.js.map +1 -1
  48. package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js +0 -2
  49. package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js.map +1 -1
  50. package/dist/DirectionalColorWheel/DirectionalColorWheelGlyph.js +0 -1
  51. package/dist/DirectionalColorWheel/DirectionalColorWheelGlyph.js.map +1 -1
  52. package/dist/DirectionalColorWheel.js +9 -21
  53. package/dist/DirectionalColorWheel.js.map +1 -1
  54. package/dist/Drawer.js +6 -29
  55. package/dist/Drawer.js.map +1 -1
  56. package/dist/DropdownMenu.js +1 -9
  57. package/dist/DropdownMenu.js.map +1 -1
  58. package/dist/FormFieldMessage.js +0 -1
  59. package/dist/FormFieldMessage.js.map +1 -1
  60. package/dist/HoverCard.js +0 -3
  61. package/dist/HoverCard.js.map +1 -1
  62. package/dist/Input.js +0 -10
  63. package/dist/Input.js.map +1 -1
  64. package/dist/InputOTP.js +0 -4
  65. package/dist/InputOTP.js.map +1 -1
  66. package/dist/InputSearch.js +3 -15
  67. package/dist/InputSearch.js.map +1 -1
  68. package/dist/Kbd.js +0 -2
  69. package/dist/Kbd.js.map +1 -1
  70. package/dist/Meter.d.ts +23 -0
  71. package/dist/Meter.d.ts.map +1 -0
  72. package/dist/Meter.js +45 -0
  73. package/dist/Meter.js.map +1 -0
  74. package/dist/MultiSelect/MultiSelectBase.js +1 -16
  75. package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
  76. package/dist/Popover.js +0 -3
  77. package/dist/Popover.js.map +1 -1
  78. package/dist/RadialMenu.js +1 -7
  79. package/dist/RadialMenu.js.map +1 -1
  80. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts.map +1 -1
  81. package/dist/RadioButtonGroup/RadioButtonGroupBase.js +1 -4
  82. package/dist/RadioButtonGroup/RadioButtonGroupBase.js.map +1 -1
  83. package/dist/RadioButtonGroup.js +1 -1
  84. package/dist/RadioButtonGroup.js.map +1 -1
  85. package/dist/RadioGroup.js +0 -6
  86. package/dist/RadioGroup.js.map +1 -1
  87. package/dist/Select.js +11 -40
  88. package/dist/Select.js.map +1 -1
  89. package/dist/Slider.js +2 -11
  90. package/dist/Slider.js.map +1 -1
  91. package/dist/Switch.js +0 -6
  92. package/dist/Switch.js.map +1 -1
  93. package/dist/Tabs/TabsBase.js +0 -5
  94. package/dist/Tabs/TabsBase.js.map +1 -1
  95. package/dist/Textarea.js +0 -4
  96. package/dist/Textarea.js.map +1 -1
  97. package/dist/Toast.js +1 -3
  98. package/dist/Toast.js.map +1 -1
  99. package/dist/Toggle.js +0 -1
  100. package/dist/Toggle.js.map +1 -1
  101. package/dist/ToggleGroup/ToggleGroupItem.js +0 -1
  102. package/dist/ToggleGroup/ToggleGroupItem.js.map +1 -1
  103. package/dist/ToggleGroup.js +0 -1
  104. package/dist/ToggleGroup.js.map +1 -1
  105. package/dist/Tooltip.d.ts.map +1 -1
  106. package/dist/Tooltip.js +4 -5
  107. package/dist/Tooltip.js.map +1 -1
  108. package/dist/Tray.js +1 -9
  109. package/dist/Tray.js.map +1 -1
  110. package/dist/index.d.ts +2 -1
  111. package/dist/index.js +2 -1
  112. package/dist/styles/horizon/base.css +31 -16
  113. package/dist/styles/horizon/colors.css +37 -21
  114. package/dist/styles/horizon/theme.css +15 -7
  115. package/dist/styles/horizon/utilities.css +19 -45
  116. package/dist/styles/spectral.css +1 -1
  117. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"file":"FormFieldMessage.js","names":[],"sources":["../src/components/FormFieldMessage/FormFieldMessage.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { type ReactNode } from 'react'\n\nexport type FormFieldMessageValue = string | string[] | Record<string, unknown> | null | undefined\n\ninterface BaseFieldMessageProps {\n className?: string\n containerClassName?: string\n dataTestId?: string\n id: string\n message: FormFieldMessageValue\n messageReserveLines?: number\n messageReserveSpace?: boolean\n}\n\nconst renderFieldMessageContent = (message: FormFieldMessageValue): ReactNode => {\n if (!message) return null\n\n if (typeof message === 'string') {\n return message\n }\n\n if (Array.isArray(message)) {\n return message.join(', ')\n }\n\n if (typeof message === 'object') {\n if ('message' in message && typeof message.message === 'string') return message.message\n if ('error' in message && typeof message.error === 'string') return message.error\n if ('details' in message && typeof message.details === 'string') return message.details\n try {\n return JSON.stringify(message)\n } catch {\n return '[Circular]'\n }\n }\n\n return String(message)\n}\n\nconst FormFieldMessage = ({\n ariaLive,\n className,\n containerClassName,\n dataTestId,\n id,\n message,\n messageReserveLines = 1,\n messageReserveSpace = false,\n role,\n tone,\n}: BaseFieldMessageProps & {\n ariaLive: 'assertive' | 'polite'\n role: 'alert' | 'status'\n tone: 'danger' | 'warning'\n}) => {\n const content = renderFieldMessageContent(message)\n const isVisible = Boolean(content)\n const reservedHeight =\n messageReserveSpace && messageReserveLines > 0\n ? `${Math.max(messageReserveLines, 1) * 1.25 + 0.25}rem`\n : undefined\n const toneClasses = tone === 'danger' ? 'text-danger-400!' : 'text-warning-400!'\n\n return (\n <div\n aria-hidden={!isVisible}\n className={cn(\n 'transition-[opacity,transform] duration-180 pt-1 ease-out motion-reduce:transition-none',\n isVisible ? 'translate-y-0 opacity-100' : '-translate-y-0.5 opacity-0',\n containerClassName,\n )}\n style={reservedHeight ? { minHeight: reservedHeight } : undefined}\n >\n <p\n aria-atomic={isVisible ? 'true' : undefined}\n aria-live={isVisible ? ariaLive : undefined}\n className={cn('m-0! text-sm leading-5 overflow-hidden', toneClasses, className)}\n data-testid={isVisible ? dataTestId : undefined}\n id={id}\n role={isVisible ? role : undefined}\n >\n {content}\n </p>\n </div>\n )\n}\n\nexport const ErrorMessage = ({ className, containerClassName, dataTestId = 'spectral-form-field-error-message', id, message, messageReserveLines, messageReserveSpace }: BaseFieldMessageProps) => {\n return (\n <FormFieldMessage\n ariaLive='polite'\n className={className}\n containerClassName={containerClassName}\n dataTestId={dataTestId}\n id={id}\n message={message}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n role='status'\n tone='danger'\n />\n )\n}\n\nexport const WarningMessage = ({ className, containerClassName, dataTestId = 'spectral-form-field-warning-message', id, message, messageReserveLines, messageReserveSpace }: BaseFieldMessageProps) => {\n return (\n <FormFieldMessage\n ariaLive='polite'\n className={className}\n containerClassName={containerClassName}\n dataTestId={dataTestId}\n id={id}\n message={message}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n role='status'\n tone='warning'\n />\n )\n}\n"],"mappings":";;;;;;AAeA,MAAM,6BAA6B,YAA8C;AAC/E,KAAI,CAAC,QAAS,QAAO;AAErB,KAAI,OAAO,YAAY,SACrB,QAAO;AAGT,KAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,KAAK,KAAK;AAG3B,KAAI,OAAO,YAAY,UAAU;AAC/B,MAAI,aAAa,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO,QAAQ;AAChF,MAAI,WAAW,WAAW,OAAO,QAAQ,UAAU,SAAU,QAAO,QAAQ;AAC5E,MAAI,aAAa,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO,QAAQ;AAChF,MAAI;AACF,UAAO,KAAK,UAAU,QAAQ;UACxB;AACN,UAAO;;;AAIX,QAAO,OAAO,QAAQ;;AAGxB,MAAM,oBAAoB,EACxB,UACA,WACA,oBACA,YACA,IACA,SACA,sBAAsB,GACtB,sBAAsB,OACtB,MACA,WAKI;CACJ,MAAM,UAAU,0BAA0B,QAAQ;CAClD,MAAM,YAAY,QAAQ,QAAQ;CAClC,MAAM,iBACJ,uBAAuB,sBAAsB,IACzC,GAAG,KAAK,IAAI,qBAAqB,EAAE,GAAG,OAAO,IAAK,OAClD;CACN,MAAM,cAAc,SAAS,WAAW,qBAAqB;AAE7D,QACE,oBAAC,OAAD;EACE,eAAa,CAAC;EACd,WAAW,GACT,2FACA,YAAY,8BAA8B,8BAC1C,mBACD;EACD,OAAO,iBAAiB,EAAE,WAAW,gBAAgB,GAAG;YAExD,oBAAC,KAAD;GACE,eAAa,YAAY,SAAS;GAClC,aAAW,YAAY,WAAW;GAClC,WAAW,GAAG,0CAA0C,aAAa,UAAU;GAC/E,eAAa,YAAY,aAAa;GAClC;GACJ,MAAM,YAAY,OAAO;aAExB;GACC;EACA;;AAIV,MAAa,gBAAgB,EAAE,WAAW,oBAAoB,aAAa,qCAAqC,IAAI,SAAS,qBAAqB,0BAAiD;AACjM,QACE,oBAAC,kBAAD;EACE,UAAS;EACE;EACS;EACR;EACR;EACK;EACY;EACA;EACrB,MAAK;EACL,MAAK;EACL;;AAIN,MAAa,kBAAkB,EAAE,WAAW,oBAAoB,aAAa,uCAAuC,IAAI,SAAS,qBAAqB,0BAAiD;AACrM,QACE,oBAAC,kBAAD;EACE,UAAS;EACE;EACS;EACR;EACR;EACK;EACY;EACA;EACrB,MAAK;EACL,MAAK;EACL"}
1
+ {"version":3,"file":"FormFieldMessage.js","names":[],"sources":["../src/components/FormFieldMessage/FormFieldMessage.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { type ReactNode } from 'react'\n\nexport type FormFieldMessageValue = string | string[] | Record<string, unknown> | null | undefined\n\ninterface BaseFieldMessageProps {\n className?: string\n containerClassName?: string\n dataTestId?: string\n id: string\n message: FormFieldMessageValue\n messageReserveLines?: number\n messageReserveSpace?: boolean\n}\n\nconst renderFieldMessageContent = (message: FormFieldMessageValue): ReactNode => {\n if (!message) return null\n\n if (typeof message === 'string') {\n return message\n }\n\n if (Array.isArray(message)) {\n return message.join(', ')\n }\n\n if (typeof message === 'object') {\n if ('message' in message && typeof message.message === 'string') return message.message\n if ('error' in message && typeof message.error === 'string') return message.error\n if ('details' in message && typeof message.details === 'string') return message.details\n try {\n return JSON.stringify(message)\n } catch {\n return '[Circular]'\n }\n }\n\n return String(message)\n}\n\nconst FormFieldMessage = ({\n ariaLive,\n className,\n containerClassName,\n dataTestId,\n id,\n message,\n messageReserveLines = 1,\n messageReserveSpace = false,\n role,\n tone,\n}: BaseFieldMessageProps & {\n ariaLive: 'assertive' | 'polite'\n role: 'alert' | 'status'\n tone: 'danger' | 'warning'\n}) => {\n const content = renderFieldMessageContent(message)\n const isVisible = Boolean(content)\n const reservedHeight =\n messageReserveSpace && messageReserveLines > 0\n ? `${Math.max(messageReserveLines, 1) * 1.25 + 0.25}rem`\n : undefined\n const toneClasses = tone === 'danger' ? 'text-danger-400!' : 'text-warning-400!'\n\n return (\n <div\n aria-hidden={!isVisible}\n className={cn(\n 'transition-[opacity,transform] duration-180 pt-1 ease-out motion-reduce:transition-none',\n isVisible ? 'translate-y-0 opacity-100' : '-translate-y-0.5 opacity-0',\n containerClassName,\n )}\n style={reservedHeight ? { minHeight: reservedHeight } : undefined}\n >\n <p\n aria-atomic={isVisible ? 'true' : undefined}\n aria-live={isVisible ? ariaLive : undefined}\n className={cn('m-0! text-sm leading-5 overflow-hidden', toneClasses, className)}\n data-testid={isVisible ? dataTestId : undefined}\n id={id}\n role={isVisible ? role : undefined}\n >\n {content}\n </p>\n </div>\n )\n}\n\nexport const ErrorMessage = ({ className, containerClassName, dataTestId = 'spectral-form-field-error-message', id, message, messageReserveLines, messageReserveSpace }: BaseFieldMessageProps) => {\n return (\n <FormFieldMessage\n ariaLive='polite'\n className={className}\n containerClassName={containerClassName}\n dataTestId={dataTestId}\n id={id}\n message={message}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n role='status'\n tone='danger'\n />\n )\n}\n\nexport const WarningMessage = ({ className, containerClassName, dataTestId = 'spectral-form-field-warning-message', id, message, messageReserveLines, messageReserveSpace }: BaseFieldMessageProps) => {\n return (\n <FormFieldMessage\n ariaLive='polite'\n className={className}\n containerClassName={containerClassName}\n dataTestId={dataTestId}\n id={id}\n message={message}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n role='status'\n tone='warning'\n />\n )\n}\n"],"mappings":";;;;;;AAeA,MAAM,6BAA6B,YAA8C;AAC/E,KAAI,CAAC,QAAS,QAAO;AAErB,KAAI,OAAO,YAAY,SACrB,QAAO;AAGT,KAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,KAAK,KAAI;AAG1B,KAAI,OAAO,YAAY,UAAU;AAC/B,MAAI,aAAa,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO,QAAQ;AAChF,MAAI,WAAW,WAAW,OAAO,QAAQ,UAAU,SAAU,QAAO,QAAQ;AAC5E,MAAI,aAAa,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO,QAAQ;AAChF,MAAI;AACF,UAAO,KAAK,UAAU,QAAO;UACvB;AACN,UAAO;;;AAIX,QAAO,OAAO,QAAO;;AAGvB,MAAM,oBAAoB,EACxB,UACA,WACA,oBACA,YACA,IACA,SACA,sBAAsB,GACtB,sBAAsB,OACtB,MACA,WAKI;CACJ,MAAM,UAAU,0BAA0B,QAAO;CACjD,MAAM,YAAY,QAAQ,QAAO;CACjC,MAAM,iBACJ,uBAAuB,sBAAsB,IACzC,GAAG,KAAK,IAAI,qBAAqB,EAAE,GAAG,OAAO,IAAK,OAClD;CACN,MAAM,cAAc,SAAS,WAAW,qBAAqB;AAE7D,QACE,oBAAC,OAAD;EACE,eAAa,CAAC;EACd,WAAW,GACT,2FACA,YAAY,8BAA8B,8BAC1C,mBACD;EACD,OAAO,iBAAiB,EAAE,WAAW,gBAAgB,GAAG;YAExD,oBAAC,KAAD;GACE,eAAa,YAAY,SAAS;GAClC,aAAW,YAAY,WAAW;GAClC,WAAW,GAAG,0CAA0C,aAAa,UAAU;GAE3E;GACJ,MAAM,YAAY,OAAO;aAExB;GACA;EACA;;AAIT,MAAa,gBAAgB,EAAE,WAAW,oBAAoB,aAAa,qCAAqC,IAAI,SAAS,qBAAqB,0BAAiD;AACjM,QACE,oBAAC,kBAAD;EACE,UAAS;EACE;EACS;EACR;EACR;EACK;EACY;EACA;EACrB,MAAK;EACL,MAAK;EACN;;AAIL,MAAa,kBAAkB,EAAE,WAAW,oBAAoB,aAAa,uCAAuC,IAAI,SAAS,qBAAqB,0BAAiD;AACrM,QACE,oBAAC,kBAAD;EACE,UAAS;EACE;EACS;EACR;EACR;EACK;EACY;EACA;EACrB,MAAK;EACL,MAAK;EACN"}
package/dist/HoverCard.js CHANGED
@@ -19,7 +19,6 @@ const HoverCard = ({ align, alignOffset, collisionBoundary, collisionPadding, si
19
19
  },
20
20
  children: /* @__PURE__ */ jsx(HoverCardPrimitive.Root, {
21
21
  "data-slot": "hover-card",
22
- "data-testid": "spectral-hover-card",
23
22
  ...props
24
23
  })
25
24
  });
@@ -29,7 +28,6 @@ const HoverCardTrigger = ({ className, ...props }) => {
29
28
  return /* @__PURE__ */ jsx(HoverCardPrimitive.Trigger, {
30
29
  className: cn("font-base", className),
31
30
  "data-slot": "hover-card-trigger",
32
- "data-testid": "spectral-hover-card-trigger",
33
31
  ...props
34
32
  });
35
33
  };
@@ -52,7 +50,6 @@ const HoverCardContent = ({ align: alignProp, alignOffset: alignOffsetProp, clas
52
50
  collisionBoundary,
53
51
  collisionPadding,
54
52
  "data-slot": "hover-card-content",
55
- "data-testid": "spectral-hover-card-content",
56
53
  side,
57
54
  sideOffset,
58
55
  style: { width: typeof width === "number" ? `${width}px` : width === "trigger-width" ? "var(--radix-hover-card-trigger-width)" : "fit-content" },
@@ -1 +1 @@
1
- {"version":3,"file":"HoverCard.js","names":[],"sources":["../src/components/HoverCard/HoverCard.tsx"],"sourcesContent":["import * as HoverCardPrimitive from '@radix-ui/react-hover-card'\nimport { cn } from '@utils/twUtils'\nimport { createContext, useContext, type ComponentProps, type CSSProperties } from 'react'\n\ntype Align = 'start' | 'center' | 'end'\ntype Side = 'top' | 'right' | 'bottom' | 'left'\n\ninterface HoverCardContentProps {\n align?: Align\n alignOffset?: number\n collisionBoundary?: Element | null | (Element | null)[]\n collisionPadding?: number | Partial<Record<Side, number>>\n side?: Side\n sideOffset?: number\n width?: number | 'w-fit' | 'trigger-width'\n}\n\nconst HoverCardContext = createContext<HoverCardContentProps>({})\n\nexport interface HoverCardProps extends ComponentProps<typeof HoverCardPrimitive.Root>, HoverCardContentProps {}\n\nexport const HoverCard = ({ align, alignOffset, collisionBoundary, collisionPadding, side, sideOffset, width, ...props }: HoverCardProps) => {\n return (\n <HoverCardContext.Provider value={{ side, sideOffset, align, alignOffset, collisionBoundary, collisionPadding, width }}>\n <HoverCardPrimitive.Root data-slot='hover-card' data-testid='spectral-hover-card' {...props} />\n </HoverCardContext.Provider>\n )\n}\nHoverCard.displayName = 'HoverCard'\n\nexport const HoverCardTrigger = ({ className, ...props }: ComponentProps<typeof HoverCardPrimitive.Trigger>) => {\n return <HoverCardPrimitive.Trigger className={cn('font-base', className)} data-slot='hover-card-trigger' data-testid='spectral-hover-card-trigger' {...props} />\n}\nHoverCardTrigger.displayName = 'HoverCardTrigger'\n\nexport const HoverCardContent = ({\n align: alignProp,\n alignOffset: alignOffsetProp,\n className,\n collisionBoundary: collisionBoundaryProp,\n collisionPadding: collisionPaddingProp,\n side: sideProp,\n sideOffset: sideOffsetProp,\n width: widthProp,\n ...props\n}: ComponentProps<typeof HoverCardPrimitive.Content> & {\n width?: number | 'w-fit' | 'trigger-width'\n}) => {\n const context = useContext(HoverCardContext)\n\n const align = alignProp ?? context.align ?? 'center'\n const sideOffset = sideOffsetProp ?? context.sideOffset ?? 4\n const side = sideProp ?? context.side ?? 'bottom'\n const width = widthProp ?? context.width ?? 380\n const alignOffset = alignOffsetProp ?? context.alignOffset\n const collisionBoundary = collisionBoundaryProp ?? context.collisionBoundary\n const collisionPadding = collisionPaddingProp ?? context.collisionPadding\n\n return (\n <HoverCardPrimitive.Portal data-slot='hover-card-portal'>\n <HoverCardPrimitive.Content\n align={align}\n alignOffset={alignOffset}\n className={cn(\n 'rounded-lg shadow-lg p-5 z-50 origin-[--radix-hover-card-content-transform-origin] bg-popover-bg text-text-primary outline-none motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95',\n className,\n )}\n collisionBoundary={collisionBoundary}\n collisionPadding={collisionPadding}\n data-slot='hover-card-content'\n data-testid='spectral-hover-card-content'\n side={side}\n sideOffset={sideOffset}\n style={\n {\n width: typeof width === 'number' ? `${width}px` : width === 'trigger-width' ? 'var(--radix-hover-card-trigger-width)' : 'fit-content',\n } as CSSProperties\n }\n {...props}\n />\n </HoverCardPrimitive.Portal>\n )\n}\nHoverCardContent.displayName = 'HoverCardContent'\n"],"mappings":";;;;;;;AAiBA,MAAM,mBAAmB,cAAqC,EAAE,CAAC;AAIjE,MAAa,aAAa,EAAE,OAAO,aAAa,mBAAmB,kBAAkB,MAAM,YAAY,OAAO,GAAG,YAA4B;AAC3I,QACE,oBAAC,iBAAiB,UAAlB;EAA2B,OAAO;GAAE;GAAM;GAAY;GAAO;GAAa;GAAmB;GAAkB;GAAO;YACpH,oBAAC,mBAAmB,MAApB;GAAyB,aAAU;GAAa,eAAY;GAAsB,GAAI;GAAS;EACrE;;AAGhC,UAAU,cAAc;AAExB,MAAa,oBAAoB,EAAE,WAAW,GAAG,YAA+D;AAC9G,QAAO,oBAAC,mBAAmB,SAApB;EAA4B,WAAW,GAAG,aAAa,UAAU;EAAE,aAAU;EAAqB,eAAY;EAA8B,GAAI;EAAS;;AAElK,iBAAiB,cAAc;AAE/B,MAAa,oBAAoB,EAC/B,OAAO,WACP,aAAa,iBACb,WACA,mBAAmB,uBACnB,kBAAkB,sBAClB,MAAM,UACN,YAAY,gBACZ,OAAO,WACP,GAAG,YAGC;CACJ,MAAM,UAAU,WAAW,iBAAiB;CAE5C,MAAM,QAAQ,aAAa,QAAQ,SAAS;CAC5C,MAAM,aAAa,kBAAkB,QAAQ,cAAc;CAC3D,MAAM,OAAO,YAAY,QAAQ,QAAQ;CACzC,MAAM,QAAQ,aAAa,QAAQ,SAAS;CAC5C,MAAM,cAAc,mBAAmB,QAAQ;CAC/C,MAAM,oBAAoB,yBAAyB,QAAQ;CAC3D,MAAM,mBAAmB,wBAAwB,QAAQ;AAEzD,QACE,oBAAC,mBAAmB,QAApB;EAA2B,aAAU;YACnC,oBAAC,mBAAmB,SAApB;GACS;GACM;GACb,WAAW,GACT,4kBACA,UACD;GACkB;GACD;GAClB,aAAU;GACV,eAAY;GACN;GACM;GACZ,OACE,EACE,OAAO,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM,UAAU,kBAAkB,0CAA0C,eACzH;GAEH,GAAI;GACJ;EACwB;;AAGhC,iBAAiB,cAAc"}
1
+ {"version":3,"file":"HoverCard.js","names":[],"sources":["../src/components/HoverCard/HoverCard.tsx"],"sourcesContent":["import * as HoverCardPrimitive from '@radix-ui/react-hover-card'\nimport { cn } from '@utils/twUtils'\nimport { createContext, useContext, type ComponentProps, type CSSProperties } from 'react'\n\ntype Align = 'start' | 'center' | 'end'\ntype Side = 'top' | 'right' | 'bottom' | 'left'\n\ninterface HoverCardContentProps {\n align?: Align\n alignOffset?: number\n collisionBoundary?: Element | null | (Element | null)[]\n collisionPadding?: number | Partial<Record<Side, number>>\n side?: Side\n sideOffset?: number\n width?: number | 'w-fit' | 'trigger-width'\n}\n\nconst HoverCardContext = createContext<HoverCardContentProps>({})\n\nexport interface HoverCardProps extends ComponentProps<typeof HoverCardPrimitive.Root>, HoverCardContentProps {}\n\nexport const HoverCard = ({ align, alignOffset, collisionBoundary, collisionPadding, side, sideOffset, width, ...props }: HoverCardProps) => {\n return (\n <HoverCardContext.Provider value={{ side, sideOffset, align, alignOffset, collisionBoundary, collisionPadding, width }}>\n <HoverCardPrimitive.Root data-slot='hover-card' data-testid='spectral-hover-card' {...props} />\n </HoverCardContext.Provider>\n )\n}\nHoverCard.displayName = 'HoverCard'\n\nexport const HoverCardTrigger = ({ className, ...props }: ComponentProps<typeof HoverCardPrimitive.Trigger>) => {\n return <HoverCardPrimitive.Trigger className={cn('font-base', className)} data-slot='hover-card-trigger' data-testid='spectral-hover-card-trigger' {...props} />\n}\nHoverCardTrigger.displayName = 'HoverCardTrigger'\n\nexport const HoverCardContent = ({\n align: alignProp,\n alignOffset: alignOffsetProp,\n className,\n collisionBoundary: collisionBoundaryProp,\n collisionPadding: collisionPaddingProp,\n side: sideProp,\n sideOffset: sideOffsetProp,\n width: widthProp,\n ...props\n}: ComponentProps<typeof HoverCardPrimitive.Content> & {\n width?: number | 'w-fit' | 'trigger-width'\n}) => {\n const context = useContext(HoverCardContext)\n\n const align = alignProp ?? context.align ?? 'center'\n const sideOffset = sideOffsetProp ?? context.sideOffset ?? 4\n const side = sideProp ?? context.side ?? 'bottom'\n const width = widthProp ?? context.width ?? 380\n const alignOffset = alignOffsetProp ?? context.alignOffset\n const collisionBoundary = collisionBoundaryProp ?? context.collisionBoundary\n const collisionPadding = collisionPaddingProp ?? context.collisionPadding\n\n return (\n <HoverCardPrimitive.Portal data-slot='hover-card-portal'>\n <HoverCardPrimitive.Content\n align={align}\n alignOffset={alignOffset}\n className={cn(\n 'rounded-lg shadow-lg p-5 z-50 origin-[--radix-hover-card-content-transform-origin] bg-popover-bg text-text-primary outline-none motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95',\n className,\n )}\n collisionBoundary={collisionBoundary}\n collisionPadding={collisionPadding}\n data-slot='hover-card-content'\n data-testid='spectral-hover-card-content'\n side={side}\n sideOffset={sideOffset}\n style={\n {\n width: typeof width === 'number' ? `${width}px` : width === 'trigger-width' ? 'var(--radix-hover-card-trigger-width)' : 'fit-content',\n } as CSSProperties\n }\n {...props}\n />\n </HoverCardPrimitive.Portal>\n )\n}\nHoverCardContent.displayName = 'HoverCardContent'\n"],"mappings":";;;;;;;AAiBA,MAAM,mBAAmB,cAAqC,EAAE,CAAA;AAIhE,MAAa,aAAa,EAAE,OAAO,aAAa,mBAAmB,kBAAkB,MAAM,YAAY,OAAO,GAAG,YAA4B;AAC3I,QACE,oBAAC,iBAAiB,UAAlB;EAA2B,OAAO;GAAE;GAAM;GAAY;GAAO;GAAa;GAAmB;GAAkB;GAAO;YACpH,oBAAC,mBAAmB,MAApB;GAAyB,aAAU;GAA+C,GAAI;GAAQ;EACrE;;AAG/B,UAAU,cAAc;AAExB,MAAa,oBAAoB,EAAE,WAAW,GAAG,YAA+D;AAC9G,QAAO,oBAAC,mBAAmB,SAApB;EAA4B,WAAW,GAAG,aAAa,UAAU;EAAE,aAAU;EAA+D,GAAI;EAAQ;;AAEjK,iBAAiB,cAAc;AAE/B,MAAa,oBAAoB,EAC/B,OAAO,WACP,aAAa,iBACb,WACA,mBAAmB,uBACnB,kBAAkB,sBAClB,MAAM,UACN,YAAY,gBACZ,OAAO,WACP,GAAG,YAGC;CACJ,MAAM,UAAU,WAAW,iBAAgB;CAE3C,MAAM,QAAQ,aAAa,QAAQ,SAAS;CAC5C,MAAM,aAAa,kBAAkB,QAAQ,cAAc;CAC3D,MAAM,OAAO,YAAY,QAAQ,QAAQ;CACzC,MAAM,QAAQ,aAAa,QAAQ,SAAS;CAC5C,MAAM,cAAc,mBAAmB,QAAQ;CAC/C,MAAM,oBAAoB,yBAAyB,QAAQ;CAC3D,MAAM,mBAAmB,wBAAwB,QAAQ;AAEzD,QACE,oBAAC,mBAAmB,QAApB;EAA2B,aAAU;YACnC,oBAAC,mBAAmB,SAApB;GACS;GACM;GACb,WAAW,GACT,4kBACA,UACD;GACkB;GACD;GAClB,aAAU;GAEJ;GACM;GACZ,OACE,EACE,OAAO,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM,UAAU,kBAAkB,0CAA0C,eACzH;GAEH,GAAI;GACL;EACwB;;AAG/B,iBAAiB,cAAc"}
package/dist/Input.js CHANGED
@@ -80,7 +80,6 @@ const Input = (allProps) => {
80
80
  "aria-label": isVisible ? `Hide ${label ?? "password"}` : `Show ${label ?? "password"}`,
81
81
  "aria-pressed": isVisible,
82
82
  className: iconClasses,
83
- "data-testid": "spectral-input-password-toggle",
84
83
  onClick: toggleVisibility,
85
84
  type: "button",
86
85
  children: isVisible ? /* @__PURE__ */ jsx(EyeClosedIcon, { size: 22 }) : /* @__PURE__ */ jsx(EyeOpenIcon, { size: 22 })
@@ -88,29 +87,24 @@ const Input = (allProps) => {
88
87
  clear: () => /* @__PURE__ */ jsx("button", {
89
88
  "aria-label": String(`Clear ${label ?? "input"}`),
90
89
  className: iconClasses,
91
- "data-testid": "spectral-input-clear-button",
92
90
  onClick: handleClear,
93
91
  type: "button",
94
92
  children: /* @__PURE__ */ jsx(CloseCircleIcon, { size: 24 })
95
93
  }),
96
94
  loading: () => /* @__PURE__ */ jsx("div", {
97
95
  className: "right-4 text-input-icon absolute top-1/2 -translate-y-1/2",
98
- "data-testid": "spectral-input-loading-icon",
99
96
  children: /* @__PURE__ */ jsx(LoaderIcon, { size: 24 })
100
97
  }),
101
98
  error: () => /* @__PURE__ */ jsx("div", {
102
99
  className: "right-4 absolute top-1/2 -translate-y-1/2 text-danger-400",
103
- "data-testid": "spectral-input-error-icon",
104
100
  children: /* @__PURE__ */ jsx(ErrorIcon, { size: 24 })
105
101
  }),
106
102
  success: () => /* @__PURE__ */ jsx("div", {
107
103
  className: "right-4 absolute top-1/2 -translate-y-1/2 text-success-400",
108
- "data-testid": "spectral-input-success-icon",
109
104
  children: /* @__PURE__ */ jsx(CheckCircleIcon, { size: 24 })
110
105
  }),
111
106
  warning: () => /* @__PURE__ */ jsx("div", {
112
107
  className: "right-4 absolute top-1/2 -translate-y-1/2 text-warning-400",
113
- "data-testid": "spectral-input-warning-icon",
114
108
  children: /* @__PURE__ */ jsx(WarningIcon, { size: 24 })
115
109
  })
116
110
  };
@@ -136,15 +130,12 @@ const Input = (allProps) => {
136
130
  const prefixClasses = cn("inset-y-0 left-4 text-base pointer-events-none absolute flex items-center text-input-text-prefix opacity-100 peer-disabled:opacity-50");
137
131
  return /* @__PURE__ */ jsxs("div", {
138
132
  className: "flex w-full flex-col gap-1.5",
139
- "data-testid": "spectral-input-container",
140
133
  children: [label && /* @__PURE__ */ jsx(Label, {
141
134
  className: cn("mb-2 block", labelClassName, isDisabled && "cursor-not-allowed text-input-text--disabled placeholder:text-input-text-placeholder"),
142
- "data-testid": "spectral-input-label",
143
135
  htmlFor: inputId,
144
136
  children: label
145
137
  }), /* @__PURE__ */ jsxs("div", {
146
138
  className: "relative",
147
- "data-testid": "spectral-input-wrapper",
148
139
  children: [
149
140
  /* @__PURE__ */ jsxs("div", {
150
141
  className: "relative",
@@ -160,7 +151,6 @@ const Input = (allProps) => {
160
151
  autoComplete: getAutoCompleteValue(type),
161
152
  className: inputClasses,
162
153
  "data-state": state,
163
- "data-testid": "spectral-input",
164
154
  disabled: isDisabled,
165
155
  id: inputId,
166
156
  name,
package/dist/Input.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Input.js","names":[],"sources":["../src/components/Input/Input.tsx"],"sourcesContent":["import { CheckCircleIcon, CloseCircleIcon, ErrorIcon, EyeClosedIcon, EyeOpenIcon, LoaderIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { ErrorMessage, WarningMessage, getAriaProps, getFormFieldCSSProperties, getInputClasses, useFormFieldId, useFormFieldState, type BaseFormFieldProps } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, type ChangeEvent, type CSSProperties, type FocusEvent, type InputHTMLAttributes, type ReactElement, type Ref } from 'react'\nimport { useClearOnFocus, usePasswordVisibility, usePrefixWidth } from './InputUtils'\n\nexport type InputType = 'text' | 'email' | 'url' | 'tel' | 'password' | 'number' | 'date' | 'datetime-local'\n\nexport type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'onChange'> &\n BaseFormFieldProps & {\n className?: string\n clearOnFocus?: boolean\n endIcon?: ReactElement\n labelClassName?: string\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n onChange?: (value: string) => void\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void\n placeholder?: string\n prefix?: string\n showClearButton?: boolean\n showStateIcon?: boolean\n startIcon?: ReactElement\n suppressHydrationWarning?: boolean\n type?: InputType\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n }\n\nconst mergeRefs = <T,>(...refs: (Ref<T> | undefined)[]): Ref<T> => {\n return (value: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as { current: T | null }).current = value\n }\n })\n }\n}\n\nconst getAutoCompleteValue = (type: InputType): string => {\n const autoCompleteMap: Record<InputType, string> = {\n date: 'off',\n email: 'email',\n number: 'off',\n password: 'current-password',\n tel: 'tel',\n text: 'off',\n url: 'url',\n 'datetime-local': 'off',\n }\n return autoCompleteMap[type] || 'off'\n}\n\nexport const Input = (\n allProps: InputProps & {\n ref?: Ref<HTMLInputElement>\n },\n): ReactElement => {\n const {\n className,\n clearOnFocus = false,\n defaultValue,\n disabled,\n endIcon,\n errorMessage,\n id,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onBlur,\n onChange,\n onFocus,\n placeholder,\n prefix,\n ref,\n required,\n showClearButton = false,\n showStateIcon = true,\n startIcon,\n state = 'default',\n suppressHydrationWarning = true,\n type = 'text',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const inputId = useFormFieldId(id, name)\n const errorMessageId = `${inputId}-error`\n const warningMessageId = `${inputId}-warning`\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n const normalizedDefaultValue = typeof defaultValue === 'string' ? defaultValue : defaultValue !== undefined && defaultValue !== null ? String(defaultValue) : ''\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue: normalizedDefaultValue,\n onChange,\n })\n\n const internalRef = useRef<HTMLInputElement>(null)\n const inputRef = mergeRefs(ref, internalRef)\n\n const { isVisible, toggleVisibility, inputType } = usePasswordVisibility()\n const { prefixWidth, prefixRef } = usePrefixWidth(prefix)\n const { handleFocus: clearOnFocusHandler } = useClearOnFocus(clearOnFocus, (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value))\n\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n onBlur?.(e)\n },\n [onBlur],\n )\n\n const handleFocus = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n clearOnFocusHandler(e, onFocus)\n },\n [clearOnFocusHandler, onFocus],\n )\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>): void => {\n const newValue = e.target.value\n setValue(newValue)\n },\n [setValue],\n )\n\n const handleClear = useCallback((): void => {\n const element = internalRef.current\n if (element) {\n setValue('')\n element.focus()\n }\n }, [setValue])\n\n const showClearButtonNow = showClearButton && value.length > 0\n\n const getEndIcon = (): ReactElement | null => {\n const iconClasses = 'absolute right-4 top-1/2 -translate-y-1/2 text-input-icon hover:text-input-icon--hover focus:outline-none cursor-pointer'\n\n const iconComponents = {\n password: () => (\n <button aria-controls={inputId} aria-label={isVisible ? `Hide ${label ?? 'password'}` : `Show ${label ?? 'password'}`} aria-pressed={isVisible} className={iconClasses} data-testid='spectral-input-password-toggle' onClick={toggleVisibility} type='button'>\n {isVisible ? <EyeClosedIcon size={22} /> : <EyeOpenIcon size={22} />}\n </button>\n ),\n clear: () => (\n <button aria-label={String(`Clear ${label ?? 'input'}`)} className={iconClasses} data-testid='spectral-input-clear-button' onClick={handleClear} type='button'>\n <CloseCircleIcon size={24} />\n </button>\n ),\n loading: () => (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-loading-icon'>\n <LoaderIcon size={24} />\n </div>\n ),\n error: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-danger-400' data-testid='spectral-input-error-icon'>\n <ErrorIcon size={24} />\n </div>\n ),\n success: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-success-400' data-testid='spectral-input-success-icon'>\n <CheckCircleIcon size={24} />\n </div>\n ),\n warning: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-warning-400' data-testid='spectral-input-warning-icon'>\n <WarningIcon size={24} />\n </div>\n ),\n }\n\n if (endIcon) return <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2'>{endIcon}</div>\n if (type === 'password') return iconComponents.password()\n if (showClearButtonNow) return iconComponents.clear()\n if (isLoading) return iconComponents.loading()\n if (!showStateIcon && (state === 'success' || state === 'warning' || state === 'error')) return null\n if (state === 'success') return iconComponents.success()\n if (state === 'warning') return iconComponents.warning()\n if (state === 'error') return iconComponents.error()\n\n return null\n }\n\n const getStartIcon = (): ReactElement | null => {\n if (startIcon) return startIcon\n return null\n }\n\n const usesTabularNumbers = type === 'number' || type === 'date' || type === 'datetime-local'\n const inputClasses = cn(getInputClasses(state, className), '[text-indent:var(--prefix-width)]', showClearButtonNow && 'pr-10', usesTabularNumbers && 'tabular-nums')\n\n const prefixClasses = cn('inset-y-0 left-4 text-base pointer-events-none absolute flex items-center text-input-text-prefix opacity-100 peer-disabled:opacity-50')\n\n return (\n <div className='flex w-full flex-col gap-1.5' data-testid='spectral-input-container'>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled placeholder:text-input-text-placeholder')} data-testid='spectral-input-label' htmlFor={inputId}>\n {label}\n </Label>\n )}\n <div className='relative' data-testid='spectral-input-wrapper'>\n <div className='relative'>\n {getStartIcon()}\n {prefix && (\n <span ref={prefixRef} className={prefixClasses}>\n {prefix}\n </span>\n )}\n <input\n aria-label={ariaLabel ?? label}\n autoComplete={getAutoCompleteValue(type)}\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input'\n disabled={isDisabled}\n id={inputId}\n name={name}\n onBlur={handleBlur}\n onChange={handleChange}\n onFocus={handleFocus}\n placeholder={placeholder ?? label}\n ref={inputRef}\n required={required}\n style={\n getFormFieldCSSProperties({\n '--prefix-width': prefix ? `${prefixWidth}px` : '0',\n }) as CSSProperties\n }\n suppressHydrationWarning={suppressHydrationWarning}\n type={type === 'password' ? inputType : type}\n value={value}\n {...ariaProps}\n {...props}\n />\n {getEndIcon()}\n </div>\n\n <ErrorMessage\n dataTestId='spectral-input-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n </div>\n )\n}\nInput.displayName = 'Input'\n"],"mappings":"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"}
1
+ {"version":3,"file":"Input.js","names":[],"sources":["../src/components/Input/Input.tsx"],"sourcesContent":["import { CheckCircleIcon, CloseCircleIcon, ErrorIcon, EyeClosedIcon, EyeOpenIcon, LoaderIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { ErrorMessage, WarningMessage, getAriaProps, getFormFieldCSSProperties, getInputClasses, useFormFieldId, useFormFieldState, type BaseFormFieldProps } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, type ChangeEvent, type CSSProperties, type FocusEvent, type InputHTMLAttributes, type ReactElement, type Ref } from 'react'\nimport { useClearOnFocus, usePasswordVisibility, usePrefixWidth } from './InputUtils'\n\nexport type InputType = 'text' | 'email' | 'url' | 'tel' | 'password' | 'number' | 'date' | 'datetime-local'\n\nexport type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'onChange'> &\n BaseFormFieldProps & {\n className?: string\n clearOnFocus?: boolean\n endIcon?: ReactElement\n labelClassName?: string\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n onChange?: (value: string) => void\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void\n placeholder?: string\n prefix?: string\n showClearButton?: boolean\n showStateIcon?: boolean\n startIcon?: ReactElement\n suppressHydrationWarning?: boolean\n type?: InputType\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n }\n\nconst mergeRefs = <T,>(...refs: (Ref<T> | undefined)[]): Ref<T> => {\n return (value: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as { current: T | null }).current = value\n }\n })\n }\n}\n\nconst getAutoCompleteValue = (type: InputType): string => {\n const autoCompleteMap: Record<InputType, string> = {\n date: 'off',\n email: 'email',\n number: 'off',\n password: 'current-password',\n tel: 'tel',\n text: 'off',\n url: 'url',\n 'datetime-local': 'off',\n }\n return autoCompleteMap[type] || 'off'\n}\n\nexport const Input = (\n allProps: InputProps & {\n ref?: Ref<HTMLInputElement>\n },\n): ReactElement => {\n const {\n className,\n clearOnFocus = false,\n defaultValue,\n disabled,\n endIcon,\n errorMessage,\n id,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onBlur,\n onChange,\n onFocus,\n placeholder,\n prefix,\n ref,\n required,\n showClearButton = false,\n showStateIcon = true,\n startIcon,\n state = 'default',\n suppressHydrationWarning = true,\n type = 'text',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const inputId = useFormFieldId(id, name)\n const errorMessageId = `${inputId}-error`\n const warningMessageId = `${inputId}-warning`\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n const normalizedDefaultValue = typeof defaultValue === 'string' ? defaultValue : defaultValue !== undefined && defaultValue !== null ? String(defaultValue) : ''\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue: normalizedDefaultValue,\n onChange,\n })\n\n const internalRef = useRef<HTMLInputElement>(null)\n const inputRef = mergeRefs(ref, internalRef)\n\n const { isVisible, toggleVisibility, inputType } = usePasswordVisibility()\n const { prefixWidth, prefixRef } = usePrefixWidth(prefix)\n const { handleFocus: clearOnFocusHandler } = useClearOnFocus(clearOnFocus, (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value))\n\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n onBlur?.(e)\n },\n [onBlur],\n )\n\n const handleFocus = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n clearOnFocusHandler(e, onFocus)\n },\n [clearOnFocusHandler, onFocus],\n )\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>): void => {\n const newValue = e.target.value\n setValue(newValue)\n },\n [setValue],\n )\n\n const handleClear = useCallback((): void => {\n const element = internalRef.current\n if (element) {\n setValue('')\n element.focus()\n }\n }, [setValue])\n\n const showClearButtonNow = showClearButton && value.length > 0\n\n const getEndIcon = (): ReactElement | null => {\n const iconClasses = 'absolute right-4 top-1/2 -translate-y-1/2 text-input-icon hover:text-input-icon--hover focus:outline-none cursor-pointer'\n\n const iconComponents = {\n password: () => (\n <button aria-controls={inputId} aria-label={isVisible ? `Hide ${label ?? 'password'}` : `Show ${label ?? 'password'}`} aria-pressed={isVisible} className={iconClasses} data-testid='spectral-input-password-toggle' onClick={toggleVisibility} type='button'>\n {isVisible ? <EyeClosedIcon size={22} /> : <EyeOpenIcon size={22} />}\n </button>\n ),\n clear: () => (\n <button aria-label={String(`Clear ${label ?? 'input'}`)} className={iconClasses} data-testid='spectral-input-clear-button' onClick={handleClear} type='button'>\n <CloseCircleIcon size={24} />\n </button>\n ),\n loading: () => (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-loading-icon'>\n <LoaderIcon size={24} />\n </div>\n ),\n error: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-danger-400' data-testid='spectral-input-error-icon'>\n <ErrorIcon size={24} />\n </div>\n ),\n success: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-success-400' data-testid='spectral-input-success-icon'>\n <CheckCircleIcon size={24} />\n </div>\n ),\n warning: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-warning-400' data-testid='spectral-input-warning-icon'>\n <WarningIcon size={24} />\n </div>\n ),\n }\n\n if (endIcon) return <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2'>{endIcon}</div>\n if (type === 'password') return iconComponents.password()\n if (showClearButtonNow) return iconComponents.clear()\n if (isLoading) return iconComponents.loading()\n if (!showStateIcon && (state === 'success' || state === 'warning' || state === 'error')) return null\n if (state === 'success') return iconComponents.success()\n if (state === 'warning') return iconComponents.warning()\n if (state === 'error') return iconComponents.error()\n\n return null\n }\n\n const getStartIcon = (): ReactElement | null => {\n if (startIcon) return startIcon\n return null\n }\n\n const usesTabularNumbers = type === 'number' || type === 'date' || type === 'datetime-local'\n const inputClasses = cn(getInputClasses(state, className), '[text-indent:var(--prefix-width)]', showClearButtonNow && 'pr-10', usesTabularNumbers && 'tabular-nums')\n\n const prefixClasses = cn('inset-y-0 left-4 text-base pointer-events-none absolute flex items-center text-input-text-prefix opacity-100 peer-disabled:opacity-50')\n\n return (\n <div className='flex w-full flex-col gap-1.5' data-testid='spectral-input-container'>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled placeholder:text-input-text-placeholder')} data-testid='spectral-input-label' htmlFor={inputId}>\n {label}\n </Label>\n )}\n <div className='relative' data-testid='spectral-input-wrapper'>\n <div className='relative'>\n {getStartIcon()}\n {prefix && (\n <span ref={prefixRef} className={prefixClasses}>\n {prefix}\n </span>\n )}\n <input\n aria-label={ariaLabel ?? label}\n autoComplete={getAutoCompleteValue(type)}\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input'\n disabled={isDisabled}\n id={inputId}\n name={name}\n onBlur={handleBlur}\n onChange={handleChange}\n onFocus={handleFocus}\n placeholder={placeholder ?? label}\n ref={inputRef}\n required={required}\n style={\n getFormFieldCSSProperties({\n '--prefix-width': prefix ? `${prefixWidth}px` : '0',\n }) as CSSProperties\n }\n suppressHydrationWarning={suppressHydrationWarning}\n type={type === 'password' ? inputType : type}\n value={value}\n {...ariaProps}\n {...props}\n />\n {getEndIcon()}\n </div>\n\n <ErrorMessage\n dataTestId='spectral-input-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n </div>\n )\n}\nInput.displayName = 'Input'\n"],"mappings":"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"}
package/dist/InputOTP.js CHANGED
@@ -36,7 +36,6 @@ const Root = ({ autoFocus = false, children, className, errorMessage, id, inputM
36
36
  "data-dashlane-disabled-on-field": "true",
37
37
  "data-lpignore": "true",
38
38
  "data-protonpass-ignore": "true",
39
- "data-testid": "spectral-input-otp",
40
39
  id: inputId,
41
40
  inputMode,
42
41
  maxLength,
@@ -73,7 +72,6 @@ const Root = ({ autoFocus = false, children, className, errorMessage, id, inputM
73
72
  Root.displayName = "InputOTP";
74
73
  const Group = ({ ref, ...props }) => /* @__PURE__ */ jsx("div", {
75
74
  className: "gap-x-2 flex items-center justify-center",
76
- "data-testid": "spectral-input-otp-group",
77
75
  ref,
78
76
  ...props
79
77
  });
@@ -89,7 +87,6 @@ const Slot = ({ className, index, ref, ...props }) => {
89
87
  return /* @__PURE__ */ jsxs("div", {
90
88
  className: cn("h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none", variant === "filled" ? "border-level-one bg-level-one" : "border-input-otp-border bg-transparent", !isInvalid && "border", isInvalid && "border-2 border-danger-400", slot.isActive && !isInvalid && "z-10 border-input-otp-border--focus", slot.isActive && isInvalid && "z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400", className),
91
89
  "data-index": index,
92
- "data-testid": "spectral-input-otp-slot",
93
90
  "data-variant": variant,
94
91
  ref,
95
92
  ...props,
@@ -139,7 +136,6 @@ const Separator = ({ ref, ...props }) => {
139
136
  ref,
140
137
  role: "separator",
141
138
  ...props,
142
- "data-testid": "spectral-input-otp-separator",
143
139
  "data-variant": variant,
144
140
  children: /* @__PURE__ */ jsx(MinusIcon, {
145
141
  size: 24,
@@ -1 +1 @@
1
- {"version":3,"file":"InputOTP.js","names":[],"sources":["../src/components/InputOTP/InputOTP.tsx"],"sourcesContent":["import { MinusIcon } from '@components/Icons'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId, useFormFieldState, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { OTPInput, REGEXP_ONLY_DIGITS, type OTPInputProps } from 'input-otp'\nimport { createContext, useContext, type ClipboardEvent, type ComponentPropsWithoutRef, type ComponentRef, type Ref } from 'react'\n\nexport interface InputOTPBaseProps extends Omit<OTPInputProps, 'textAlign' | 'pushPasswordManagerStrategy' | 'pasteTransformer' | 'noScriptCSSFallback' | 'placeholder' | 'containerClassName' | 'render' | 'pattern'> {\n onComplete?: (...args: unknown[]) => void\n className?: string\n errorMessage?: string | undefined\n inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'\n messageReserveLines?: number\n messageReserveSpace?: boolean\n /**\n * Regex pattern string to restrict allowed characters.\n * When `inputMode=\"numeric\"`, defaults to digits-only pattern.\n * Set to `undefined` to allow any characters.\n */\n pattern?: string | undefined\n separator?: boolean\n state?: FormFieldState\n variant?: 'outlined' | 'filled'\n}\n\nexport type InputOTPProps = InputOTPBaseProps & ({ value: number | string; onChange: (newValue: number | string) => void } | { value?: never; onChange?: never })\n\nconst InputOTPStateContext = createContext<{ isInvalid?: boolean }>({})\n\nconst OTPInputContext = createContext<{\n maxLength?: number\n slots?: { char: string | null; hasFakeCaret: boolean; isActive: boolean }[]\n variant?: 'outlined' | 'filled'\n} | null>(null)\n\nconst useRoot = () => {\n const context = useContext(OTPInputContext)\n if (!context) {\n throw new Error('useRoot must be used within an InputOTP')\n }\n return context\n}\n\nconst Root = ({\n autoFocus = false,\n children,\n className,\n errorMessage,\n id,\n inputMode = 'numeric',\n messageReserveLines = 1,\n messageReserveSpace = false,\n maxLength,\n name,\n onChange,\n onComplete,\n pattern,\n ref,\n state = 'default',\n value,\n variant = 'outlined',\n ...props\n}: InputOTPProps & {\n ref?: Ref<ComponentRef<typeof OTPInput>>\n}) => {\n const inputId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(inputId)\n const { isInvalid } = useFormFieldState(false, state)\n\n // Apply digits-only pattern when inputMode is numeric (unless explicitly overridden)\n const effectivePattern = pattern ?? (inputMode === 'numeric' ? REGEXP_ONLY_DIGITS : undefined)\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>): void => {\n let pasteData = e.clipboardData.getData('text/plain').trim().replaceAll('-', '')\n\n // Filter to digits only when in numeric mode\n if (inputMode === 'numeric') {\n pasteData = pasteData.replace(/\\D/g, '')\n }\n\n if (pasteData.length === maxLength && typeof onChange === 'function') {\n onChange(pasteData)\n }\n }\n\n return (\n <InputOTPStateContext.Provider value={{ isInvalid }}>\n <div className='gap-y-1 flex w-max flex-col'>\n <OTPInput\n /* eslint-disable-next-line jsx-a11y/no-autofocus -- intentional: consumers can opt in for OTP-first flows; defaults to false */\n autoFocus={autoFocus}\n containerClassName={cn('gap-2 flex items-center disabled:cursor-not-allowed has-[disabled]:opacity-50', className)}\n data-1p-ignore='true'\n data-dashlane-disabled-on-field='true'\n data-lpignore='true'\n data-protonpass-ignore='true'\n data-testid='spectral-input-otp'\n id={inputId}\n inputMode={inputMode}\n maxLength={maxLength}\n onChange={onChange}\n onComplete={onComplete}\n onPaste={handlePaste}\n pasteTransformer={(pasted) => pasted.replaceAll('-', '')}\n pattern={effectivePattern}\n pushPasswordManagerStrategy='none'\n ref={ref}\n aria-describedby={isInvalid && errorMessage ? errorMessageId : undefined}\n aria-invalid={isInvalid}\n textAlign='center'\n value={value}\n {...props}\n render={({ slots }) => (\n <OTPInputContext.Provider value={{ slots, variant, maxLength }}>\n {children ?? (\n <Group>\n <Slots />\n </Group>\n )}\n </OTPInputContext.Provider>\n )}\n />\n <ErrorMessage\n dataTestId='spectral-input-otp-error-message'\n id={errorMessageId}\n message={isInvalid ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n </div>\n </InputOTPStateContext.Provider>\n )\n}\nRoot.displayName = 'InputOTP'\n\nconst Group = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => <div className='gap-x-2 flex items-center justify-center' data-testid='spectral-input-otp-group' ref={ref} {...props} />\nGroup.displayName = 'InputOTP.Group'\n\nconst Slot = ({\n className,\n index,\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n index: number\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined', slots = [] } = useRoot()\n const { isInvalid } = useContext(InputOTPStateContext)\n const slot = slots[index] || { char: '', hasFakeCaret: true, isActive: false }\n\n return (\n <div\n className={cn(\n 'h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none',\n variant === 'filled' ? 'border-level-one bg-level-one' : 'border-input-otp-border bg-transparent',\n !isInvalid && 'border',\n isInvalid && 'border-2 border-danger-400',\n slot.isActive && !isInvalid && 'z-10 border-input-otp-border--focus',\n slot.isActive && isInvalid && 'z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400',\n className,\n )}\n data-index={index}\n data-testid='spectral-input-otp-slot'\n data-variant={variant}\n ref={ref}\n {...props}\n >\n {slot.char}\n {slot.hasFakeCaret && (\n <div className='inset-0 pointer-events-none absolute flex items-center justify-center motion-safe:animate-caret-blink'>\n <div className='h-8 w-px bg-input-otp-caret' />\n </div>\n )}\n </div>\n )\n}\nSlot.displayName = 'InputOTP.Slot'\n\ninterface SlotsProps {\n className?: string\n count?: number\n start?: number\n}\n\n/**\n * Helper component that automatically renders multiple InputOTP.Slot components.\n * Uses the maxLength from the parent InputOTP to determine how many slots to render.\n *\n * @example\n * // Render all 6 slots\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots />\n * </InputOTP.Group>\n * </InputOTP>\n *\n * @example\n * // Render slots in groups with a separator (3-3 split)\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots count={3} />\n * </InputOTP.Group>\n * <InputOTP.Separator />\n * <InputOTP.Group>\n * <InputOTP.Slots start={3} />\n * </InputOTP.Group>\n * </InputOTP>\n */\nconst Slots = ({ start = 0, count, className }: SlotsProps) => {\n const { maxLength = 0 } = useRoot()\n const end = count !== undefined ? start + count : maxLength\n const indices = Array.from({ length: end - start }, (_, i) => start + i)\n\n return (\n <>\n {indices.map((index) => (\n <Slot key={index} index={index} className={className} />\n ))}\n </>\n )\n}\nSlots.displayName = 'InputOTP.Slots'\n\nconst Separator = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined' } = useRoot()\n\n return (\n <div ref={ref} role='separator' {...props} data-testid='spectral-input-otp-separator' data-variant={variant}>\n <MinusIcon size={24} color={variant === 'filled' ? 'var(--color-input-otp-filled-separator)' : 'var(--color-input-otp-border)'} />\n </div>\n )\n}\nSeparator.displayName = 'InputOTP.Separator'\n\nexport const InputOTP = Object.assign(Root, {\n Group,\n Slot,\n Slots,\n Separator,\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"InputOTP.js","names":[],"sources":["../src/components/InputOTP/InputOTP.tsx"],"sourcesContent":["import { MinusIcon } from '@components/Icons'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId, useFormFieldState, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { OTPInput, REGEXP_ONLY_DIGITS, type OTPInputProps } from 'input-otp'\nimport { createContext, useContext, type ClipboardEvent, type ComponentPropsWithoutRef, type ComponentRef, type Ref } from 'react'\n\nexport interface InputOTPBaseProps extends Omit<OTPInputProps, 'textAlign' | 'pushPasswordManagerStrategy' | 'pasteTransformer' | 'noScriptCSSFallback' | 'placeholder' | 'containerClassName' | 'render' | 'pattern'> {\n onComplete?: (...args: unknown[]) => void\n className?: string\n errorMessage?: string | undefined\n inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'\n messageReserveLines?: number\n messageReserveSpace?: boolean\n /**\n * Regex pattern string to restrict allowed characters.\n * When `inputMode=\"numeric\"`, defaults to digits-only pattern.\n * Set to `undefined` to allow any characters.\n */\n pattern?: string | undefined\n separator?: boolean\n state?: FormFieldState\n variant?: 'outlined' | 'filled'\n}\n\nexport type InputOTPProps = InputOTPBaseProps & ({ value: number | string; onChange: (newValue: number | string) => void } | { value?: never; onChange?: never })\n\nconst InputOTPStateContext = createContext<{ isInvalid?: boolean }>({})\n\nconst OTPInputContext = createContext<{\n maxLength?: number\n slots?: { char: string | null; hasFakeCaret: boolean; isActive: boolean }[]\n variant?: 'outlined' | 'filled'\n} | null>(null)\n\nconst useRoot = () => {\n const context = useContext(OTPInputContext)\n if (!context) {\n throw new Error('useRoot must be used within an InputOTP')\n }\n return context\n}\n\nconst Root = ({\n autoFocus = false,\n children,\n className,\n errorMessage,\n id,\n inputMode = 'numeric',\n messageReserveLines = 1,\n messageReserveSpace = false,\n maxLength,\n name,\n onChange,\n onComplete,\n pattern,\n ref,\n state = 'default',\n value,\n variant = 'outlined',\n ...props\n}: InputOTPProps & {\n ref?: Ref<ComponentRef<typeof OTPInput>>\n}) => {\n const inputId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(inputId)\n const { isInvalid } = useFormFieldState(false, state)\n\n // Apply digits-only pattern when inputMode is numeric (unless explicitly overridden)\n const effectivePattern = pattern ?? (inputMode === 'numeric' ? REGEXP_ONLY_DIGITS : undefined)\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>): void => {\n let pasteData = e.clipboardData.getData('text/plain').trim().replaceAll('-', '')\n\n // Filter to digits only when in numeric mode\n if (inputMode === 'numeric') {\n pasteData = pasteData.replace(/\\D/g, '')\n }\n\n if (pasteData.length === maxLength && typeof onChange === 'function') {\n onChange(pasteData)\n }\n }\n\n return (\n <InputOTPStateContext.Provider value={{ isInvalid }}>\n <div className='gap-y-1 flex w-max flex-col'>\n <OTPInput\n /* eslint-disable-next-line jsx-a11y/no-autofocus -- intentional: consumers can opt in for OTP-first flows; defaults to false */\n autoFocus={autoFocus}\n containerClassName={cn('gap-2 flex items-center disabled:cursor-not-allowed has-[disabled]:opacity-50', className)}\n data-1p-ignore='true'\n data-dashlane-disabled-on-field='true'\n data-lpignore='true'\n data-protonpass-ignore='true'\n data-testid='spectral-input-otp'\n id={inputId}\n inputMode={inputMode}\n maxLength={maxLength}\n onChange={onChange}\n onComplete={onComplete}\n onPaste={handlePaste}\n pasteTransformer={(pasted) => pasted.replaceAll('-', '')}\n pattern={effectivePattern}\n pushPasswordManagerStrategy='none'\n ref={ref}\n aria-describedby={isInvalid && errorMessage ? errorMessageId : undefined}\n aria-invalid={isInvalid}\n textAlign='center'\n value={value}\n {...props}\n render={({ slots }) => (\n <OTPInputContext.Provider value={{ slots, variant, maxLength }}>\n {children ?? (\n <Group>\n <Slots />\n </Group>\n )}\n </OTPInputContext.Provider>\n )}\n />\n <ErrorMessage\n dataTestId='spectral-input-otp-error-message'\n id={errorMessageId}\n message={isInvalid ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n </div>\n </InputOTPStateContext.Provider>\n )\n}\nRoot.displayName = 'InputOTP'\n\nconst Group = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => <div className='gap-x-2 flex items-center justify-center' data-testid='spectral-input-otp-group' ref={ref} {...props} />\nGroup.displayName = 'InputOTP.Group'\n\nconst Slot = ({\n className,\n index,\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n index: number\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined', slots = [] } = useRoot()\n const { isInvalid } = useContext(InputOTPStateContext)\n const slot = slots[index] || { char: '', hasFakeCaret: true, isActive: false }\n\n return (\n <div\n className={cn(\n 'h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none',\n variant === 'filled' ? 'border-level-one bg-level-one' : 'border-input-otp-border bg-transparent',\n !isInvalid && 'border',\n isInvalid && 'border-2 border-danger-400',\n slot.isActive && !isInvalid && 'z-10 border-input-otp-border--focus',\n slot.isActive && isInvalid && 'z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400',\n className,\n )}\n data-index={index}\n data-testid='spectral-input-otp-slot'\n data-variant={variant}\n ref={ref}\n {...props}\n >\n {slot.char}\n {slot.hasFakeCaret && (\n <div className='inset-0 pointer-events-none absolute flex items-center justify-center motion-safe:animate-caret-blink'>\n <div className='h-8 w-px bg-input-otp-caret' />\n </div>\n )}\n </div>\n )\n}\nSlot.displayName = 'InputOTP.Slot'\n\ninterface SlotsProps {\n className?: string\n count?: number\n start?: number\n}\n\n/**\n * Helper component that automatically renders multiple InputOTP.Slot components.\n * Uses the maxLength from the parent InputOTP to determine how many slots to render.\n *\n * @example\n * // Render all 6 slots\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots />\n * </InputOTP.Group>\n * </InputOTP>\n *\n * @example\n * // Render slots in groups with a separator (3-3 split)\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots count={3} />\n * </InputOTP.Group>\n * <InputOTP.Separator />\n * <InputOTP.Group>\n * <InputOTP.Slots start={3} />\n * </InputOTP.Group>\n * </InputOTP>\n */\nconst Slots = ({ start = 0, count, className }: SlotsProps) => {\n const { maxLength = 0 } = useRoot()\n const end = count !== undefined ? start + count : maxLength\n const indices = Array.from({ length: end - start }, (_, i) => start + i)\n\n return (\n <>\n {indices.map((index) => (\n <Slot key={index} index={index} className={className} />\n ))}\n </>\n )\n}\nSlots.displayName = 'InputOTP.Slots'\n\nconst Separator = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined' } = useRoot()\n\n return (\n <div ref={ref} role='separator' {...props} data-testid='spectral-input-otp-separator' data-variant={variant}>\n <MinusIcon size={24} color={variant === 'filled' ? 'var(--color-input-otp-filled-separator)' : 'var(--color-input-otp-border)'} />\n </div>\n )\n}\nSeparator.displayName = 'InputOTP.Separator'\n\nexport const InputOTP = Object.assign(Root, {\n Group,\n Slot,\n Slots,\n Separator,\n})\n"],"mappings":"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"}
@@ -197,29 +197,25 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
197
197
  dropdownWidth,
198
198
  triggerWidth: "100%"
199
199
  });
200
- const inputClasses = cn(getInputClasses(state, className), "pe-12", showSearchIcon && "ps-11", showClearButton && "pe-10", state === "loading" && "cursor-wait");
200
+ const inputClasses = cn(getInputClasses(state, className), "pe-4", showSearchIcon && "ps-11", showClearButton && "pe-10!", state === "loading" && "cursor-wait");
201
201
  return /* @__PURE__ */ jsxs("div", {
202
- className: "flex w-full flex-col gap-1.5",
203
- "data-testid": "spectral-input-search-container",
202
+ className: "flex w-full flex-col",
204
203
  ref,
205
204
  children: [
206
205
  label && /* @__PURE__ */ jsx(Label, {
207
206
  className: cn("mb-2 block", labelClassName, isDisabled && "cursor-not-allowed text-input-text--disabled"),
208
- "data-testid": "spectral-input-search-label",
209
207
  htmlFor: fieldId,
210
208
  children: label
211
209
  }),
212
210
  /* @__PURE__ */ jsx("div", {
213
211
  className: "relative",
214
- "data-testid": "spectral-input-search-wrapper",
215
212
  ref: triggerRef,
216
213
  children: /* @__PURE__ */ jsxs("div", {
217
214
  className: "relative",
218
215
  children: [
219
216
  showSearchIcon && /* @__PURE__ */ jsx("span", {
220
217
  "aria-hidden": "true",
221
- className: "left-4 text-input-icon absolute top-1/2 -translate-y-1/2",
222
- "data-testid": "spectral-input-search-icon",
218
+ className: cn("left-4 text-input-icon absolute top-1/2 -translate-y-1/2", isDisabled && "text-input-text--disabled"),
223
219
  children: /* @__PURE__ */ jsx(SearchIcon, { size: 20 })
224
220
  }),
225
221
  /* @__PURE__ */ jsx("input", {
@@ -232,7 +228,6 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
232
228
  autoComplete: "off",
233
229
  className: inputClasses,
234
230
  "data-state": state,
235
- "data-testid": "spectral-input-search",
236
231
  disabled: isDisabled || isLoading,
237
232
  id: fieldId,
238
233
  name,
@@ -255,25 +250,21 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
255
250
  showClearButton ? /* @__PURE__ */ jsx("button", {
256
251
  "aria-label": `Clear ${label ?? "search"}`,
257
252
  className: "right-4 text-input-icon hover:text-input-icon--hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus rounded-sm absolute top-1/2 -translate-y-1/2 cursor-pointer",
258
- "data-testid": "spectral-input-search-clear-button",
259
253
  onClick: handleClear,
260
254
  onMouseDown: (event) => event.preventDefault(),
261
255
  type: "button",
262
256
  children: /* @__PURE__ */ jsx(CloseCircleIcon, { size: 20 })
263
257
  }) : state === "loading" ? /* @__PURE__ */ jsx("div", {
264
258
  className: "right-4 text-input-icon absolute top-1/2 -translate-y-1/2",
265
- "data-testid": "spectral-input-search-loading-icon",
266
259
  children: /* @__PURE__ */ jsx(LoaderIcon, {
267
260
  className: "motion-safe:animate-spin",
268
261
  size: 20
269
262
  })
270
263
  }) : state === "error" ? /* @__PURE__ */ jsx("div", {
271
264
  className: "right-4 text-danger-400 absolute top-1/2 -translate-y-1/2",
272
- "data-testid": "spectral-input-search-error-icon",
273
265
  children: /* @__PURE__ */ jsx(ErrorIcon, { size: 20 })
274
266
  }) : state === "warning" ? /* @__PURE__ */ jsx("div", {
275
267
  className: "right-4 text-warning-400 absolute top-1/2 -translate-y-1/2",
276
- "data-testid": "spectral-input-search-warning-icon",
277
268
  children: /* @__PURE__ */ jsx(WarningIcon, { size: 20 })
278
269
  }) : null
279
270
  ]
@@ -283,7 +274,6 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
283
274
  className: cn("origin-top p-1 z-50 fixed", getDropdownSurfaceClasses(), "max-h-72 overflow-hidden", "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95 motion-safe:slide-in-from-top-2"),
284
275
  "data-dropdown-width-mode": dropdownWidthMode,
285
276
  "data-dropdown-width-value": dropdownWidthMode === "custom" ? dropdownWidth : void 0,
286
- "data-testid": "spectral-input-search-content",
287
277
  ref: setDropdownElement,
288
278
  style: {
289
279
  top: `${dropdownPosition.top}px`,
@@ -304,7 +294,6 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
304
294
  className: cn(getOptionClasses(!!option.disabled, isHighlighted, isSelected), "text-left"),
305
295
  "data-highlighted": isHighlighted ? "" : void 0,
306
296
  "data-index": index,
307
- "data-testid": "spectral-input-search-item",
308
297
  disabled: option.disabled,
309
298
  id: `${fieldId}-option-${index}`,
310
299
  onMouseDown: (event) => {
@@ -325,7 +314,6 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
325
314
  className: cn(getOptionClasses(isCreating, createOptionIndex === highlightedIndex, false), "gap-2 text-left text-accent font-medium"),
326
315
  "data-highlighted": createOptionIndex === highlightedIndex ? "" : void 0,
327
316
  "data-index": createOptionIndex,
328
- "data-testid": "spectral-input-search-create-option",
329
317
  disabled: isCreating,
330
318
  id: `${fieldId}-option-${createOptionIndex}`,
331
319
  onMouseDown: (event) => {
@@ -1 +1 @@
1
- {"version":3,"file":"InputSearch.js","names":[],"sources":["../src/components/InputSearch/InputSearch.tsx"],"sourcesContent":["import { CloseCircleIcon, ErrorIcon, LoaderIcon, PlusIcon, SearchIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport {\n EmptyState,\n ErrorMessage,\n WarningMessage,\n getAriaProps,\n getDropdownSurfaceClasses,\n getDropdownWidthStyles,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getInputClasses,\n getOptionClasses,\n useFormFieldId,\n useFormFieldState,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport type InputSearchOption = BaseOption\n\nexport interface InputSearchProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n createOptionLabel?: (query: string) => ReactNode\n creatingLabel?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n isCreating?: boolean\n labelClassName?: string\n onChange?: (value: string) => void\n onCreate?: (query: string) => void\n onValueChange?: (value: string) => void\n openOnFocus?: boolean\n options: InputSearchOption[]\n placeholder?: string\n renderOption?: (option: InputSearchOption) => ReactNode\n showSearchIcon?: boolean\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nconst defaultCreateOptionLabel = (query: string): ReactNode => (\n <>\n <PlusIcon size={16} className='shrink-0' />\n <span className='truncate'>\n Create <span className='font-semibold'>&ldquo;{query}&rdquo;</span>\n </span>\n </>\n)\n\nexport const InputSearch = ({\n className,\n createOptionLabel = defaultCreateOptionLabel,\n creatingLabel = 'Creating…',\n defaultValue = '',\n disabled,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n id,\n isCreating = false,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onCreate,\n onValueChange,\n openOnFocus = false,\n options,\n placeholder = 'Search…',\n ref,\n renderOption,\n required,\n showSearchIcon = true,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: InputSearchProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('InputSearch: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const fieldId = useFormFieldId(id, name)\n const listboxId = `${fieldId}-listbox`\n const errorMessageId = getErrorMessageId(fieldId)\n const warningMessageId = `${fieldId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue,\n onChange: (nextValue) => {\n if (onChange) {\n onChange(nextValue)\n } else {\n onValueChange?.(nextValue)\n }\n },\n })\n\n const [query, setQuery] = useState('')\n const [isFocused, setIsFocused] = useState(false)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number; width: number } | null>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const blurTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const triggerRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = useMemo(() => options.find((option) => option.value === value) ?? null, [options, value])\n\n useEffect(() => {\n if (!isFocused) {\n setQuery(selectedOption?.label ?? '')\n }\n }, [selectedOption, isFocused])\n\n const trimmedQuery = query.trim()\n\n const filteredOptions = useMemo(() => {\n if (trimmedQuery.length === 0) return options\n const lower = trimmedQuery.toLowerCase()\n return options.filter((option) => option.label.toLowerCase().includes(lower))\n }, [trimmedQuery, options])\n\n const exactMatch = useMemo(() => options.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase()), [options, trimmedQuery])\n\n const showCreateOption = onCreate !== undefined && trimmedQuery.length > 0 && !exactMatch\n const createOptionIndex = showCreateOption ? filteredOptions.length : -1\n const totalItems = filteredOptions.length + (showCreateOption ? 1 : 0)\n const showDropdown = isFocused && !isDisabled && !isLoading && (openOnFocus || trimmedQuery.length > 0)\n const showEmptyState = showDropdown && totalItems === 0\n\n const { dropdownShiftStyle, recalculateDropdownPosition, setDropdownElement } = useAutoDropdownHorizontalShift(showDropdown)\n\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n setDropdownPosition({\n top: rect.bottom + 4,\n left: rect.left,\n width: rect.width,\n })\n recalculateDropdownPosition()\n }, [recalculateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) {\n setDropdownPosition(null)\n return\n }\n updateDropdownPosition()\n const handle = () => updateDropdownPosition()\n window.addEventListener('scroll', handle, true)\n window.addEventListener('resize', handle)\n return () => {\n window.removeEventListener('scroll', handle, true)\n window.removeEventListener('resize', handle)\n }\n }, [showDropdown, updateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) return\n updateDropdownPosition()\n }, [showDropdown, updateDropdownPosition, filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n setHighlightedIndex(-1)\n }, [filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n if (highlightedIndex < 0 || !listRef.current) return\n const element = listRef.current.querySelector<HTMLElement>(`[data-index='${highlightedIndex}']`)\n element?.scrollIntoView({ block: 'nearest' })\n }, [highlightedIndex])\n\n useEffect(\n () => () => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n },\n [],\n )\n\n const commitSelection = useCallback(\n (option: InputSearchOption) => {\n if (option.disabled) return\n setValue(option.value)\n setQuery(option.label)\n setIsFocused(false)\n setHighlightedIndex(-1)\n inputRef.current?.blur()\n },\n [setValue],\n )\n\n const commitCreate = useCallback(() => {\n if (!onCreate || isCreating || trimmedQuery.length === 0) return\n onCreate(trimmedQuery)\n }, [onCreate, isCreating, trimmedQuery])\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowDown': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : prev))\n break\n }\n case 'ArrowUp': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : 0))\n break\n }\n case 'Enter': {\n if (totalItems === 0) return\n event.preventDefault()\n if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {\n const option = filteredOptions[highlightedIndex]\n if (option) commitSelection(option)\n } else if (highlightedIndex === createOptionIndex && !isCreating) {\n commitCreate()\n }\n break\n }\n case 'Escape': {\n event.preventDefault()\n setIsFocused(false)\n setHighlightedIndex(-1)\n if (selectedOption) setQuery(selectedOption.label)\n inputRef.current?.blur()\n break\n }\n default:\n break\n }\n },\n [totalItems, filteredOptions, highlightedIndex, createOptionIndex, commitSelection, commitCreate, isCreating, selectedOption],\n )\n\n const handleFocus = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n blurTimeoutRef.current = null\n }\n setIsFocused(true)\n setQuery('')\n }, [])\n\n const handleBlur = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n blurTimeoutRef.current = setTimeout(() => {\n blurTimeoutRef.current = null\n // Don't write to `query` here — the useEffect tied to selectedOption + isFocused\n // is the single source of truth for the visible query when the field isn't focused.\n // Setting it from this stale closure causes off-by-one display after selection.\n setIsFocused(false)\n setHighlightedIndex(-1)\n }, 150)\n }, [])\n\n const handleClear = useCallback(() => {\n setValue('')\n setQuery('')\n inputRef.current?.focus()\n }, [setValue])\n\n const showClearButton = !isLoading && !isDisabled && (value.length > 0 || (isFocused && query.length > 0))\n\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: '100%',\n })\n\n const inputClasses = cn(getInputClasses(state, className), 'pe-12', showSearchIcon && 'ps-11', showClearButton && 'pe-10', state === 'loading' && 'cursor-wait')\n\n return (\n <div className='flex w-full flex-col gap-1.5' data-testid='spectral-input-search-container' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled')} data-testid='spectral-input-search-label' htmlFor={fieldId}>\n {label}\n </Label>\n )}\n\n <div className='relative' data-testid='spectral-input-search-wrapper' ref={triggerRef}>\n <div className='relative'>\n {showSearchIcon && (\n <span aria-hidden='true' className='left-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-icon'>\n <SearchIcon size={20} />\n </span>\n )}\n\n <input\n aria-activedescendant={highlightedIndex >= 0 ? `${fieldId}-option-${highlightedIndex}` : undefined}\n aria-autocomplete='list'\n aria-controls={listboxId}\n aria-expanded={showDropdown}\n // oxlint-disable-next-line jsx-a11y/role-supports-aria-props -- Valid per WAI-ARIA 1.2 Combobox pattern\n aria-haspopup='listbox'\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input-search'\n disabled={isDisabled || isLoading}\n id={fieldId}\n name={name}\n onBlur={handleBlur}\n onChange={(event) => {\n setQuery(event.target.value)\n if (!isFocused) setIsFocused(true)\n }}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n ref={inputRef}\n required={required}\n role='combobox'\n style={getFormFieldCSSProperties() as CSSProperties}\n type='text'\n value={query}\n {...ariaProps}\n />\n\n {showClearButton ? (\n <button\n aria-label={`Clear ${label ?? 'search'}`}\n className='right-4 text-input-icon hover:text-input-icon--hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus rounded-sm absolute top-1/2 -translate-y-1/2 cursor-pointer'\n data-testid='spectral-input-search-clear-button'\n onClick={handleClear}\n // Prevent input blur from firing before click resolves\n onMouseDown={(event) => event.preventDefault()}\n type='button'\n >\n <CloseCircleIcon size={20} />\n </button>\n ) : state === 'loading' ? (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-loading-icon'>\n <LoaderIcon className='motion-safe:animate-spin' size={20} />\n </div>\n ) : state === 'error' ? (\n <div className='right-4 text-danger-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-error-icon'>\n <ErrorIcon size={20} />\n </div>\n ) : state === 'warning' ? (\n <div className='right-4 text-warning-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-warning-icon'>\n <WarningIcon size={20} />\n </div>\n ) : null}\n </div>\n\n </div>\n\n {showDropdown && dropdownPosition && typeof document !== 'undefined'\n ? createPortal(\n <div\n className={cn('origin-top p-1 z-50 fixed', getDropdownSurfaceClasses(), 'max-h-72 overflow-hidden', 'motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95 motion-safe:slide-in-from-top-2')}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-input-search-content'\n ref={setDropdownElement}\n style={{\n top: `${dropdownPosition.top}px`,\n left: `${dropdownPosition.left}px`,\n ...(dropdownWidthMode === 'trigger' ? { width: `${dropdownPosition.width}px` } : (dropdownWidthStyle as CSSProperties)),\n ...dropdownShiftStyle,\n }}\n >\n <div\n className='max-h-[17.5rem] overflow-y-auto'\n id={listboxId}\n ref={listRef}\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- WAI-ARIA combobox pattern requires role='listbox' on the popup\n role='listbox'\n >\n {showEmptyState ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <>\n {filteredOptions.map((option, index) => {\n const isHighlighted = index === highlightedIndex\n const isSelected = option.value === value\n return (\n <button\n aria-selected={isSelected}\n className={cn(getOptionClasses(!!option.disabled, isHighlighted, isSelected), 'text-left')}\n data-highlighted={isHighlighted ? '' : undefined}\n data-index={index}\n data-testid='spectral-input-search-item'\n disabled={option.disabled}\n id={`${fieldId}-option-${index}`}\n key={option.value}\n onMouseDown={(event) => {\n event.preventDefault()\n commitSelection(option)\n }}\n onMouseEnter={() => setHighlightedIndex(index)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n <span className='min-w-0 flex-1 truncate'>{renderOption ? renderOption(option) : option.label}</span>\n </button>\n )\n })}\n\n {showCreateOption && (\n <button\n aria-selected={createOptionIndex === highlightedIndex}\n className={cn(getOptionClasses(isCreating, createOptionIndex === highlightedIndex, false), 'gap-2 text-left text-accent font-medium')}\n data-highlighted={createOptionIndex === highlightedIndex ? '' : undefined}\n data-index={createOptionIndex}\n data-testid='spectral-input-search-create-option'\n disabled={isCreating}\n id={`${fieldId}-option-${createOptionIndex}`}\n onMouseDown={(event) => {\n event.preventDefault()\n commitCreate()\n }}\n onMouseEnter={() => setHighlightedIndex(createOptionIndex)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n {isCreating ? (\n <span className='gap-2 flex items-center'>\n <LoaderIcon className='shrink-0 motion-safe:animate-spin' size={16} />\n <span>{creatingLabel}</span>\n </span>\n ) : (\n <span className='gap-2 flex items-center min-w-0'>{createOptionLabel(trimmedQuery)}</span>\n )}\n </button>\n )}\n </>\n )}\n </div>\n </div>,\n document.body,\n )\n : null}\n\n <ErrorMessage\n dataTestId='spectral-input-search-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-search-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nInputSearch.displayName = 'InputSearch'\n"],"mappings":"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"}
1
+ {"version":3,"file":"InputSearch.js","names":[],"sources":["../src/components/InputSearch/InputSearch.tsx"],"sourcesContent":["import { CloseCircleIcon, ErrorIcon, LoaderIcon, PlusIcon, SearchIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport {\n EmptyState,\n ErrorMessage,\n WarningMessage,\n getAriaProps,\n getDropdownSurfaceClasses,\n getDropdownWidthStyles,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getInputClasses,\n getOptionClasses,\n useFormFieldId,\n useFormFieldState,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport type InputSearchOption = BaseOption\n\nexport interface InputSearchProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n createOptionLabel?: (query: string) => ReactNode\n creatingLabel?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n isCreating?: boolean\n labelClassName?: string\n onChange?: (value: string) => void\n onCreate?: (query: string) => void\n onValueChange?: (value: string) => void\n openOnFocus?: boolean\n options: InputSearchOption[]\n placeholder?: string\n renderOption?: (option: InputSearchOption) => ReactNode\n showSearchIcon?: boolean\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nconst defaultCreateOptionLabel = (query: string): ReactNode => (\n <>\n <PlusIcon size={16} className='shrink-0' />\n <span className='truncate'>\n Create <span className='font-semibold'>&ldquo;{query}&rdquo;</span>\n </span>\n </>\n)\n\nexport const InputSearch = ({\n className,\n createOptionLabel = defaultCreateOptionLabel,\n creatingLabel = 'Creating…',\n defaultValue = '',\n disabled,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n id,\n isCreating = false,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onCreate,\n onValueChange,\n openOnFocus = false,\n options,\n placeholder = 'Search…',\n ref,\n renderOption,\n required,\n showSearchIcon = true,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: InputSearchProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('InputSearch: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const fieldId = useFormFieldId(id, name)\n const listboxId = `${fieldId}-listbox`\n const errorMessageId = getErrorMessageId(fieldId)\n const warningMessageId = `${fieldId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue,\n onChange: (nextValue) => {\n if (onChange) {\n onChange(nextValue)\n } else {\n onValueChange?.(nextValue)\n }\n },\n })\n\n const [query, setQuery] = useState('')\n const [isFocused, setIsFocused] = useState(false)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number; width: number } | null>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const blurTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const triggerRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = useMemo(() => options.find((option) => option.value === value) ?? null, [options, value])\n\n useEffect(() => {\n if (!isFocused) {\n setQuery(selectedOption?.label ?? '')\n }\n }, [selectedOption, isFocused])\n\n const trimmedQuery = query.trim()\n\n const filteredOptions = useMemo(() => {\n if (trimmedQuery.length === 0) return options\n const lower = trimmedQuery.toLowerCase()\n return options.filter((option) => option.label.toLowerCase().includes(lower))\n }, [trimmedQuery, options])\n\n const exactMatch = useMemo(() => options.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase()), [options, trimmedQuery])\n\n const showCreateOption = onCreate !== undefined && trimmedQuery.length > 0 && !exactMatch\n const createOptionIndex = showCreateOption ? filteredOptions.length : -1\n const totalItems = filteredOptions.length + (showCreateOption ? 1 : 0)\n const showDropdown = isFocused && !isDisabled && !isLoading && (openOnFocus || trimmedQuery.length > 0)\n const showEmptyState = showDropdown && totalItems === 0\n\n const { dropdownShiftStyle, recalculateDropdownPosition, setDropdownElement } = useAutoDropdownHorizontalShift(showDropdown)\n\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n setDropdownPosition({\n top: rect.bottom + 4,\n left: rect.left,\n width: rect.width,\n })\n recalculateDropdownPosition()\n }, [recalculateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) {\n setDropdownPosition(null)\n return\n }\n updateDropdownPosition()\n const handle = () => updateDropdownPosition()\n window.addEventListener('scroll', handle, true)\n window.addEventListener('resize', handle)\n return () => {\n window.removeEventListener('scroll', handle, true)\n window.removeEventListener('resize', handle)\n }\n }, [showDropdown, updateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) return\n updateDropdownPosition()\n }, [showDropdown, updateDropdownPosition, filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n setHighlightedIndex(-1)\n }, [filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n if (highlightedIndex < 0 || !listRef.current) return\n const element = listRef.current.querySelector<HTMLElement>(`[data-index='${highlightedIndex}']`)\n element?.scrollIntoView({ block: 'nearest' })\n }, [highlightedIndex])\n\n useEffect(\n () => () => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n },\n [],\n )\n\n const commitSelection = useCallback(\n (option: InputSearchOption) => {\n if (option.disabled) return\n setValue(option.value)\n setQuery(option.label)\n setIsFocused(false)\n setHighlightedIndex(-1)\n inputRef.current?.blur()\n },\n [setValue],\n )\n\n const commitCreate = useCallback(() => {\n if (!onCreate || isCreating || trimmedQuery.length === 0) return\n onCreate(trimmedQuery)\n }, [onCreate, isCreating, trimmedQuery])\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowDown': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : prev))\n break\n }\n case 'ArrowUp': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : 0))\n break\n }\n case 'Enter': {\n if (totalItems === 0) return\n event.preventDefault()\n if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {\n const option = filteredOptions[highlightedIndex]\n if (option) commitSelection(option)\n } else if (highlightedIndex === createOptionIndex && !isCreating) {\n commitCreate()\n }\n break\n }\n case 'Escape': {\n event.preventDefault()\n setIsFocused(false)\n setHighlightedIndex(-1)\n if (selectedOption) setQuery(selectedOption.label)\n inputRef.current?.blur()\n break\n }\n default:\n break\n }\n },\n [totalItems, filteredOptions, highlightedIndex, createOptionIndex, commitSelection, commitCreate, isCreating, selectedOption],\n )\n\n const handleFocus = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n blurTimeoutRef.current = null\n }\n setIsFocused(true)\n setQuery('')\n }, [])\n\n const handleBlur = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n blurTimeoutRef.current = setTimeout(() => {\n blurTimeoutRef.current = null\n // Don't write to `query` here — the useEffect tied to selectedOption + isFocused\n // is the single source of truth for the visible query when the field isn't focused.\n // Setting it from this stale closure causes off-by-one display after selection.\n setIsFocused(false)\n setHighlightedIndex(-1)\n }, 150)\n }, [])\n\n const handleClear = useCallback(() => {\n setValue('')\n setQuery('')\n inputRef.current?.focus()\n }, [setValue])\n\n const showClearButton = !isLoading && !isDisabled && (value.length > 0 || (isFocused && query.length > 0))\n\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: '100%',\n })\n\n const inputClasses = cn(getInputClasses(state, className), 'pe-4', showSearchIcon && 'ps-11', showClearButton && 'pe-10!', state === 'loading' && 'cursor-wait')\n\n return (\n <div className='flex w-full flex-col' data-testid='spectral-input-search-container' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled')} data-testid='spectral-input-search-label' htmlFor={fieldId}>\n {label}\n </Label>\n )}\n\n <div className='relative' data-testid='spectral-input-search-wrapper' ref={triggerRef}>\n <div className='relative'>\n {showSearchIcon && (\n <span aria-hidden='true' className={cn('left-4 text-input-icon absolute top-1/2 -translate-y-1/2', isDisabled && 'text-input-text--disabled')} data-testid='spectral-input-search-icon'>\n <SearchIcon size={20} />\n </span>\n )}\n\n <input\n aria-activedescendant={highlightedIndex >= 0 ? `${fieldId}-option-${highlightedIndex}` : undefined}\n aria-autocomplete='list'\n aria-controls={listboxId}\n aria-expanded={showDropdown}\n // oxlint-disable-next-line jsx-a11y/role-supports-aria-props -- Valid per WAI-ARIA 1.2 Combobox pattern\n aria-haspopup='listbox'\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input-search'\n disabled={isDisabled || isLoading}\n id={fieldId}\n name={name}\n onBlur={handleBlur}\n onChange={(event) => {\n setQuery(event.target.value)\n if (!isFocused) setIsFocused(true)\n }}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n ref={inputRef}\n required={required}\n role='combobox'\n style={getFormFieldCSSProperties() as CSSProperties}\n type='text'\n value={query}\n {...ariaProps}\n />\n\n {showClearButton ? (\n <button\n aria-label={`Clear ${label ?? 'search'}`}\n className='right-4 text-input-icon hover:text-input-icon--hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus rounded-sm absolute top-1/2 -translate-y-1/2 cursor-pointer'\n data-testid='spectral-input-search-clear-button'\n onClick={handleClear}\n // Prevent input blur from firing before click resolves\n onMouseDown={(event) => event.preventDefault()}\n type='button'\n >\n <CloseCircleIcon size={20} />\n </button>\n ) : state === 'loading' ? (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-loading-icon'>\n <LoaderIcon className='motion-safe:animate-spin' size={20} />\n </div>\n ) : state === 'error' ? (\n <div className='right-4 text-danger-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-error-icon'>\n <ErrorIcon size={20} />\n </div>\n ) : state === 'warning' ? (\n <div className='right-4 text-warning-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-warning-icon'>\n <WarningIcon size={20} />\n </div>\n ) : null}\n </div>\n\n </div>\n\n {showDropdown && dropdownPosition && typeof document !== 'undefined'\n ? createPortal(\n <div\n className={cn('origin-top p-1 z-50 fixed', getDropdownSurfaceClasses(), 'max-h-72 overflow-hidden', 'motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95 motion-safe:slide-in-from-top-2')}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-input-search-content'\n ref={setDropdownElement}\n style={{\n top: `${dropdownPosition.top}px`,\n left: `${dropdownPosition.left}px`,\n ...(dropdownWidthMode === 'trigger' ? { width: `${dropdownPosition.width}px` } : (dropdownWidthStyle as CSSProperties)),\n ...dropdownShiftStyle,\n }}\n >\n <div\n className='max-h-[17.5rem] overflow-y-auto'\n id={listboxId}\n ref={listRef}\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- WAI-ARIA combobox pattern requires role='listbox' on the popup\n role='listbox'\n >\n {showEmptyState ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <>\n {filteredOptions.map((option, index) => {\n const isHighlighted = index === highlightedIndex\n const isSelected = option.value === value\n return (\n <button\n aria-selected={isSelected}\n className={cn(getOptionClasses(!!option.disabled, isHighlighted, isSelected), 'text-left')}\n data-highlighted={isHighlighted ? '' : undefined}\n data-index={index}\n data-testid='spectral-input-search-item'\n disabled={option.disabled}\n id={`${fieldId}-option-${index}`}\n key={option.value}\n onMouseDown={(event) => {\n event.preventDefault()\n commitSelection(option)\n }}\n onMouseEnter={() => setHighlightedIndex(index)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n <span className='min-w-0 flex-1 truncate'>{renderOption ? renderOption(option) : option.label}</span>\n </button>\n )\n })}\n\n {showCreateOption && (\n <button\n aria-selected={createOptionIndex === highlightedIndex}\n className={cn(getOptionClasses(isCreating, createOptionIndex === highlightedIndex, false), 'gap-2 text-left text-accent font-medium')}\n data-highlighted={createOptionIndex === highlightedIndex ? '' : undefined}\n data-index={createOptionIndex}\n data-testid='spectral-input-search-create-option'\n disabled={isCreating}\n id={`${fieldId}-option-${createOptionIndex}`}\n onMouseDown={(event) => {\n event.preventDefault()\n commitCreate()\n }}\n onMouseEnter={() => setHighlightedIndex(createOptionIndex)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n {isCreating ? (\n <span className='gap-2 flex items-center'>\n <LoaderIcon className='shrink-0 motion-safe:animate-spin' size={16} />\n <span>{creatingLabel}</span>\n </span>\n ) : (\n <span className='gap-2 flex items-center min-w-0'>{createOptionLabel(trimmedQuery)}</span>\n )}\n </button>\n )}\n </>\n )}\n </div>\n </div>,\n document.body,\n )\n : null}\n\n <ErrorMessage\n dataTestId='spectral-input-search-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-search-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nInputSearch.displayName = 'InputSearch'\n"],"mappings":"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"}
package/dist/Kbd.js CHANGED
@@ -168,7 +168,6 @@ const Kbd = ({ className, symbol, symbolPosition = "start", children, ...props }
168
168
  return /* @__PURE__ */ jsxs("kbd", {
169
169
  className: cn("h-5 min-w-5 gap-1 rounded-sm px-1 text-xs font-medium pointer-events-none inline-flex w-fit items-center justify-center bg-kbd-bg font-mono! text-kbd-text select-none", `[&_svg:not([class*='size-'])]:size-3 in-data-[slot=tooltip-content]:bg-level-three`, className),
170
170
  "data-slot": "kbd",
171
- "data-testid": "spectral-kbd",
172
171
  ...props,
173
172
  "aria-label": ariaLabel,
174
173
  children: [
@@ -183,7 +182,6 @@ const KbdGroup = ({ className, ...props }) => {
183
182
  return /* @__PURE__ */ jsx("kbd", {
184
183
  className: cn("gap-1 inline-flex items-center", className),
185
184
  "data-slot": "kbd-group",
186
- "data-testid": "spectral-kbd-group",
187
185
  ...props
188
186
  });
189
187
  };
package/dist/Kbd.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Kbd.js","names":[],"sources":["../src/components/Kbd/Kbd.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { type ComponentProps } from 'react'\n\nexport type KbdSymbol = 'arrowLeft' | 'arrowRight' | 'arrowUp' | 'arrowDown' | 'command' | 'option' | 'shift' | 'control' | 'return' | 'delete'\n\nexport interface KbdProps {\n className?: string\n symbol?: KbdSymbol\n symbolPosition?: 'start' | 'end'\n}\n\nexport interface KbdGroupProps {\n className?: string\n}\n\nconst symbolMap = {\n arrowLeft: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M10 5.75L3.75 12L10 18.25M4.5 12H20.25' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n arrowRight: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M14 5.75L20.25 12L14 18.25M19.5 12H3.75' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n arrowUp: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M5.75 10L12 3.75L18.25 10M12 20.25V4.5' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n arrowDown: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M18 14L12 20L6 14M12 19V4' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n command: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M9.25 9.25V6.5C9.25 4.98122 8.01878 3.75 6.5 3.75C4.98122 3.75 3.75 4.98122 3.75 6.5C3.75 8.01878 4.98122 9.25 6.5 9.25H9.25ZM9.25 9.25H14.75M9.25 9.25V14.75M14.75 9.25V6.5C14.75 4.98122 15.9812 3.75 17.5 3.75C19.0188 3.75 20.25 4.98122 20.25 6.5C20.25 8.01878 19.0188 9.25 17.5 9.25H14.75ZM14.75 9.25V14.75M14.75 14.75H9.25M14.75 14.75V17.5C14.75 19.0188 15.9812 20.25 17.5 20.25C19.0188 20.25 20.25 19.0188 20.25 17.5C20.25 15.9812 19.0188 14.75 17.5 14.75H14.75ZM9.25 14.75V17.5C9.25 19.0188 8.01878 20.25 6.5 20.25C4.98122 20.25 3.75 19.0188 3.75 17.5C3.75 15.9812 4.98122 14.75 6.5 14.75H9.25Z'\n stroke='currentColor'\n strokeWidth='1.5'\n strokeLinecap='square'\n />\n </svg>\n ),\n option: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M3.75 4.75H7.40962C7.77384 4.75 8.10925 4.94802 8.2852 5.26692L15.7148 18.7331C15.8907 19.052 16.2262 19.25 16.5904 19.25H20.25M15.75 4.75H20.25' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n shift: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M2.91032 11.5511L11.2848 2.98182C11.6771 2.5804 12.3229 2.5804 12.7152 2.98182L21.0897 11.5511C21.7085 12.1843 21.2599 13.25 20.3745 13.25H17.1316V19.25C17.1316 19.8023 16.6839 20.25 16.1316 20.25H7.86842C7.31614 20.25 6.86842 19.8023 6.86842 19.25V13.25H3.62551C2.74013 13.25 2.2915 12.1843 2.91032 11.5511Z'\n stroke='currentColor'\n strokeWidth='1.5'\n strokeLinecap='square'\n strokeLinejoin='round'\n />\n </svg>\n ),\n control: (\n <svg className='-translate-y-0.5' width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M8 13.9999L11.6464 10.3535C11.8417 10.1582 12.1583 10.1582 12.3536 10.3535L16 13.9999' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n return: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n fillRule='evenodd'\n clipRule='evenodd'\n d='M20.25 4C19.8358 4 19.5 4.33579 19.5 4.75V14.25C19.5 14.3881 19.3881 14.5 19.25 14.5H5.56066L8.28033 11.7803C8.57322 11.4874 8.57322 11.0126 8.28033 10.7197C7.98744 10.4268 7.51256 10.4268 7.21967 10.7197L3.21967 14.7197C2.92678 15.0126 2.92678 15.4874 3.21967 15.7803L7.21967 19.7803C7.51256 20.0732 7.98744 20.0732 8.28033 19.7803C8.57322 19.4874 8.57322 19.0126 8.28033 18.7197L5.56066 16H19.25C20.2165 16 21 15.2165 21 14.25V4.75C21 4.33579 20.6642 4 20.25 4Z'\n fill='currentColor'\n />\n </svg>\n ),\n delete: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M14.9988 9.75L12.7488 12M12.7488 12L10.4988 14.25M12.7488 12L10.4988 9.75M12.7488 12L14.9988 14.25M6.55509 4.75H20.2488C20.8011 4.75 21.2488 5.19772 21.2488 5.75V18.25C21.2488 18.8023 20.8011 19.25 20.2488 19.25H6.55509C6.2092 19.25 5.88786 19.0712 5.70545 18.7774L1.82614 12.5274C1.62566 12.2044 1.62566 11.7956 1.82614 11.4726L5.70545 5.22264C5.88786 4.92875 6.2092 4.75 6.55509 4.75Z'\n stroke='currentColor'\n strokeWidth='1.5'\n strokeLinecap='round'\n strokeLinejoin='round'\n />\n </svg>\n ),\n} as const\n\nconst symbolLabelMap: Record<KbdSymbol, string> = {\n arrowLeft: 'Left Arrow',\n arrowRight: 'Right Arrow',\n arrowUp: 'Up Arrow',\n arrowDown: 'Down Arrow',\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n return: 'Return',\n delete: 'Delete',\n}\n\nexport const Kbd = ({ className, symbol, symbolPosition = 'start', children, ...props }: KbdProps & ComponentProps<'kbd'>) => {\n const hasChildren = children !== undefined && children !== null && children !== ''\n const symbolElement = symbol && <span aria-hidden='true'>{symbolMap[symbol]}</span>\n\n // Derive aria-label from symbol when no children are provided, unless explicitly overridden\n const derivedAriaLabel = !hasChildren && symbol ? symbolLabelMap[symbol] : undefined\n const ariaLabel = props['aria-label'] ?? derivedAriaLabel\n\n return (\n <kbd\n className={cn(\n 'h-5 min-w-5 gap-1 rounded-sm px-1 text-xs font-medium pointer-events-none inline-flex w-fit items-center justify-center bg-kbd-bg font-mono! text-kbd-text select-none',\n `[&_svg:not([class*='size-'])]:size-3 in-data-[slot=tooltip-content]:bg-level-three`,\n className,\n )}\n data-slot='kbd'\n data-testid='spectral-kbd'\n {...props}\n aria-label={ariaLabel}\n >\n {symbolPosition === 'start' && symbolElement}\n {children}\n {symbolPosition === 'end' && symbolElement}\n </kbd>\n )\n}\nKbd.displayName = 'Kbd'\n\nexport const KbdGroup = ({ className, ...props }: KbdGroupProps & ComponentProps<'kbd'>) => {\n return <kbd className={cn('gap-1 inline-flex items-center', className)} data-slot='kbd-group' data-testid='spectral-kbd-group' {...props} />\n}\nKbdGroup.displayName = 'KbdGroup'\n"],"mappings":"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"}
1
+ {"version":3,"file":"Kbd.js","names":[],"sources":["../src/components/Kbd/Kbd.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { type ComponentProps } from 'react'\n\nexport type KbdSymbol = 'arrowLeft' | 'arrowRight' | 'arrowUp' | 'arrowDown' | 'command' | 'option' | 'shift' | 'control' | 'return' | 'delete'\n\nexport interface KbdProps {\n className?: string\n symbol?: KbdSymbol\n symbolPosition?: 'start' | 'end'\n}\n\nexport interface KbdGroupProps {\n className?: string\n}\n\nconst symbolMap = {\n arrowLeft: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M10 5.75L3.75 12L10 18.25M4.5 12H20.25' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n arrowRight: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M14 5.75L20.25 12L14 18.25M19.5 12H3.75' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n arrowUp: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M5.75 10L12 3.75L18.25 10M12 20.25V4.5' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n arrowDown: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M18 14L12 20L6 14M12 19V4' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n command: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M9.25 9.25V6.5C9.25 4.98122 8.01878 3.75 6.5 3.75C4.98122 3.75 3.75 4.98122 3.75 6.5C3.75 8.01878 4.98122 9.25 6.5 9.25H9.25ZM9.25 9.25H14.75M9.25 9.25V14.75M14.75 9.25V6.5C14.75 4.98122 15.9812 3.75 17.5 3.75C19.0188 3.75 20.25 4.98122 20.25 6.5C20.25 8.01878 19.0188 9.25 17.5 9.25H14.75ZM14.75 9.25V14.75M14.75 14.75H9.25M14.75 14.75V17.5C14.75 19.0188 15.9812 20.25 17.5 20.25C19.0188 20.25 20.25 19.0188 20.25 17.5C20.25 15.9812 19.0188 14.75 17.5 14.75H14.75ZM9.25 14.75V17.5C9.25 19.0188 8.01878 20.25 6.5 20.25C4.98122 20.25 3.75 19.0188 3.75 17.5C3.75 15.9812 4.98122 14.75 6.5 14.75H9.25Z'\n stroke='currentColor'\n strokeWidth='1.5'\n strokeLinecap='square'\n />\n </svg>\n ),\n option: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M3.75 4.75H7.40962C7.77384 4.75 8.10925 4.94802 8.2852 5.26692L15.7148 18.7331C15.8907 19.052 16.2262 19.25 16.5904 19.25H20.25M15.75 4.75H20.25' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n shift: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M2.91032 11.5511L11.2848 2.98182C11.6771 2.5804 12.3229 2.5804 12.7152 2.98182L21.0897 11.5511C21.7085 12.1843 21.2599 13.25 20.3745 13.25H17.1316V19.25C17.1316 19.8023 16.6839 20.25 16.1316 20.25H7.86842C7.31614 20.25 6.86842 19.8023 6.86842 19.25V13.25H3.62551C2.74013 13.25 2.2915 12.1843 2.91032 11.5511Z'\n stroke='currentColor'\n strokeWidth='1.5'\n strokeLinecap='square'\n strokeLinejoin='round'\n />\n </svg>\n ),\n control: (\n <svg className='-translate-y-0.5' width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path d='M8 13.9999L11.6464 10.3535C11.8417 10.1582 12.1583 10.1582 12.3536 10.3535L16 13.9999' stroke='currentColor' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />\n </svg>\n ),\n return: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n fillRule='evenodd'\n clipRule='evenodd'\n d='M20.25 4C19.8358 4 19.5 4.33579 19.5 4.75V14.25C19.5 14.3881 19.3881 14.5 19.25 14.5H5.56066L8.28033 11.7803C8.57322 11.4874 8.57322 11.0126 8.28033 10.7197C7.98744 10.4268 7.51256 10.4268 7.21967 10.7197L3.21967 14.7197C2.92678 15.0126 2.92678 15.4874 3.21967 15.7803L7.21967 19.7803C7.51256 20.0732 7.98744 20.0732 8.28033 19.7803C8.57322 19.4874 8.57322 19.0126 8.28033 18.7197L5.56066 16H19.25C20.2165 16 21 15.2165 21 14.25V4.75C21 4.33579 20.6642 4 20.25 4Z'\n fill='currentColor'\n />\n </svg>\n ),\n delete: (\n <svg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>\n <path\n d='M14.9988 9.75L12.7488 12M12.7488 12L10.4988 14.25M12.7488 12L10.4988 9.75M12.7488 12L14.9988 14.25M6.55509 4.75H20.2488C20.8011 4.75 21.2488 5.19772 21.2488 5.75V18.25C21.2488 18.8023 20.8011 19.25 20.2488 19.25H6.55509C6.2092 19.25 5.88786 19.0712 5.70545 18.7774L1.82614 12.5274C1.62566 12.2044 1.62566 11.7956 1.82614 11.4726L5.70545 5.22264C5.88786 4.92875 6.2092 4.75 6.55509 4.75Z'\n stroke='currentColor'\n strokeWidth='1.5'\n strokeLinecap='round'\n strokeLinejoin='round'\n />\n </svg>\n ),\n} as const\n\nconst symbolLabelMap: Record<KbdSymbol, string> = {\n arrowLeft: 'Left Arrow',\n arrowRight: 'Right Arrow',\n arrowUp: 'Up Arrow',\n arrowDown: 'Down Arrow',\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n return: 'Return',\n delete: 'Delete',\n}\n\nexport const Kbd = ({ className, symbol, symbolPosition = 'start', children, ...props }: KbdProps & ComponentProps<'kbd'>) => {\n const hasChildren = children !== undefined && children !== null && children !== ''\n const symbolElement = symbol && <span aria-hidden='true'>{symbolMap[symbol]}</span>\n\n // Derive aria-label from symbol when no children are provided, unless explicitly overridden\n const derivedAriaLabel = !hasChildren && symbol ? symbolLabelMap[symbol] : undefined\n const ariaLabel = props['aria-label'] ?? derivedAriaLabel\n\n return (\n <kbd\n className={cn(\n 'h-5 min-w-5 gap-1 rounded-sm px-1 text-xs font-medium pointer-events-none inline-flex w-fit items-center justify-center bg-kbd-bg font-mono! text-kbd-text select-none',\n `[&_svg:not([class*='size-'])]:size-3 in-data-[slot=tooltip-content]:bg-level-three`,\n className,\n )}\n data-slot='kbd'\n data-testid='spectral-kbd'\n {...props}\n aria-label={ariaLabel}\n >\n {symbolPosition === 'start' && symbolElement}\n {children}\n {symbolPosition === 'end' && symbolElement}\n </kbd>\n )\n}\nKbd.displayName = 'Kbd'\n\nexport const KbdGroup = ({ className, ...props }: KbdGroupProps & ComponentProps<'kbd'>) => {\n return <kbd className={cn('gap-1 inline-flex items-center', className)} data-slot='kbd-group' data-testid='spectral-kbd-group' {...props} />\n}\nKbdGroup.displayName = 'KbdGroup'\n"],"mappings":"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"}