@spear-ai/spectral 1.14.1 → 1.14.3

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 (77) hide show
  1. package/dist/Button.d.ts +4 -0
  2. package/dist/Button.d.ts.map +1 -1
  3. package/dist/Button.js +4 -2
  4. package/dist/Button.js.map +1 -1
  5. package/dist/Checkbox.d.ts +4 -0
  6. package/dist/Checkbox.d.ts.map +1 -1
  7. package/dist/Checkbox.js +7 -3
  8. package/dist/Checkbox.js.map +1 -1
  9. package/dist/Combobox.d.ts +2 -0
  10. package/dist/Combobox.d.ts.map +1 -1
  11. package/dist/Combobox.js +7 -3
  12. package/dist/Combobox.js.map +1 -1
  13. package/dist/ControlGroup/ControlGroupSelect.d.ts +5 -1
  14. package/dist/ControlGroup/ControlGroupSelect.d.ts.map +1 -1
  15. package/dist/ControlGroup/ControlGroupSelect.js +3 -1
  16. package/dist/ControlGroup/ControlGroupSelect.js.map +1 -1
  17. package/dist/ControlGroup.d.ts +4 -0
  18. package/dist/ControlGroup.d.ts.map +1 -1
  19. package/dist/ControlGroup.js +7 -3
  20. package/dist/ControlGroup.js.map +1 -1
  21. package/dist/DateTimePicker.d.ts +4 -0
  22. package/dist/DateTimePicker.d.ts.map +1 -1
  23. package/dist/DateTimePicker.js +4 -2
  24. package/dist/DateTimePicker.js.map +1 -1
  25. package/dist/FormFieldMessage.d.ts +8 -2
  26. package/dist/FormFieldMessage.d.ts.map +1 -1
  27. package/dist/FormFieldMessage.js +13 -7
  28. package/dist/FormFieldMessage.js.map +1 -1
  29. package/dist/Input.js +7 -3
  30. package/dist/Input.js.map +1 -1
  31. package/dist/InputNumeric.d.ts +3 -1
  32. package/dist/InputNumeric.d.ts.map +1 -1
  33. package/dist/InputNumeric.js.map +1 -1
  34. package/dist/InputOTP.d.ts +4 -0
  35. package/dist/InputOTP.d.ts.map +1 -1
  36. package/dist/InputOTP.js +4 -2
  37. package/dist/InputOTP.js.map +1 -1
  38. package/dist/MultiSelect/MultiSelectBase.d.ts +4 -0
  39. package/dist/MultiSelect/MultiSelectBase.d.ts.map +1 -1
  40. package/dist/MultiSelect/MultiSelectBase.js +7 -3
  41. package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
  42. package/dist/RadioButton.d.ts +2 -0
  43. package/dist/RadioButton.d.ts.map +1 -1
  44. package/dist/RadioButton.js +4 -3
  45. package/dist/RadioButton.js.map +1 -1
  46. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts +3 -1
  47. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts.map +1 -1
  48. package/dist/RadioButtonGroup/RadioButtonGroupBase.js +8 -5
  49. package/dist/RadioButtonGroup/RadioButtonGroupBase.js.map +1 -1
  50. package/dist/RadioGroup.d.ts +2 -0
  51. package/dist/RadioGroup.d.ts.map +1 -1
  52. package/dist/RadioGroup.js +7 -3
  53. package/dist/RadioGroup.js.map +1 -1
  54. package/dist/Select.js +7 -3
  55. package/dist/Select.js.map +1 -1
  56. package/dist/Switch.d.ts +4 -0
  57. package/dist/Switch.d.ts.map +1 -1
  58. package/dist/Switch.js +7 -3
  59. package/dist/Switch.js.map +1 -1
  60. package/dist/Textarea.d.ts +3 -1
  61. package/dist/Textarea.d.ts.map +1 -1
  62. package/dist/Textarea.js +7 -3
  63. package/dist/Textarea.js.map +1 -1
  64. package/dist/ToggleGroup/ToggleGroupBase.d.ts +6 -3
  65. package/dist/ToggleGroup/ToggleGroupBase.d.ts.map +1 -1
  66. package/dist/ToggleGroup/ToggleGroupBase.js +9 -1
  67. package/dist/ToggleGroup/ToggleGroupBase.js.map +1 -1
  68. package/dist/ToggleGroup/ToggleGroupItem.js +1 -1
  69. package/dist/ToggleGroup/ToggleGroupItem.js.map +1 -1
  70. package/dist/ToggleGroup.js +1 -1
  71. package/dist/ToggleGroup.js.map +1 -1
  72. package/dist/styles/horizon/colors.css +1 -0
  73. package/dist/styles/spectral.css +1 -1
  74. package/dist/utils/formFieldUtils.d.ts +2 -0
  75. package/dist/utils/formFieldUtils.d.ts.map +1 -1
  76. package/dist/utils/formFieldUtils.js.map +1 -1
  77. package/package.json +2 -2
package/dist/Button.d.ts CHANGED
@@ -17,6 +17,8 @@ interface ButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonE
17
17
  endIcon?: ReactNode;
18
18
  errorMessage?: string | ReactNode;
19
19
  label?: string;
20
+ messageReserveLines?: number;
21
+ messageReserveSpace?: boolean;
20
22
  onClick?: (event: MouseEvent<HTMLElement>) => void;
21
23
  size?: 'small' | 'default' | 'fullWidth';
22
24
  startIcon?: ReactNode;
@@ -34,6 +36,8 @@ declare function Button({
34
36
  errorMessage,
35
37
  id,
36
38
  label,
39
+ messageReserveLines,
40
+ messageReserveSpace,
37
41
  onClick,
38
42
  ref,
39
43
  size,
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.ts","names":[],"sources":["../src/components/Button/Button.tsx"],"mappings":";;;;;;;;cAOM,cAAA,GAAc,KAAA;;;;IA4BnB,iCAAA,CAAA,SAAA;AAAA,UAEgB,WAAA,SAAoB,WAAA,EAAa,IAAA,CAAK,oBAAA,CAAqB,iBAAA,aAA8B,YAAA,QAAoB,cAAA;EAC5H,UAAA;EACA,QAAA;EACA,OAAA,GAAU,SAAA;EACV,YAAA,YAAwB,SAAA;EACxB,KAAA;EACA,OAAA,IAAW,KAAA,EAAO,UAAA,CAAW,WAAA;EAC7B,IAAA;EACA,SAAA,GAAY,SAAA;EACZ,KAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA;EAIA,OAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,OAAA;EACA,YAAA;EACA,EAAA;EACA,KAAA;EACA,OAAA;EACA,GAAA;EACA,IAAA;EACA,SAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EAAA,GACG;AAAA,GACF,WAAA;EACD,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"Button.d.ts","names":[],"sources":["../src/components/Button/Button.tsx"],"mappings":";;;;;;;;cAOM,cAAA,GAAc,KAAA;;;;IA4BnB,iCAAA,CAAA,SAAA;AAAA,UAEgB,WAAA,SAAoB,WAAA,EAAa,IAAA,CAAK,oBAAA,CAAqB,iBAAA,aAA8B,YAAA,QAAoB,cAAA;EAC5H,UAAA;EACA,QAAA;EACA,OAAA,GAAU,SAAA;EACV,YAAA,YAAwB,SAAA;EACxB,KAAA;EACA,mBAAA;EACA,mBAAA;EACA,OAAA,IAAW,KAAA,EAAO,UAAA,CAAW,WAAA;EAC7B,IAAA;EACA,SAAA,GAAY,SAAA;EACZ,KAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA;EAIA,OAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,OAAA;EACA,YAAA;EACA,EAAA;EACA,KAAA;EACA,mBAAA;EACA,mBAAA;EACA,OAAA;EACA,GAAA;EACA,IAAA;EACA,SAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EAAA,GACG;AAAA,GACF,WAAA;EACD,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
package/dist/Button.js CHANGED
@@ -34,7 +34,7 @@ const buttonVariants = cva(`gap-2 rounded-lg font-semibold [&_svg]:size-4 relati
34
34
  size: "default"
35
35
  }
36
36
  });
37
- const Button = ({ asChild = false, children, className, dataTestId, disabled, endIcon, errorMessage, id, label, onClick, ref, size, startIcon, state, type = "button", variant = "primary", ...props }) => {
37
+ const Button = ({ asChild = false, children, className, dataTestId, disabled, endIcon, errorMessage, id, label, messageReserveLines = 1, messageReserveSpace = true, onClick, ref, size, startIcon, state, type = "button", variant = "primary", ...props }) => {
38
38
  const stateStyles = {
39
39
  error: {
40
40
  primary: "bg-button-danger border-button-danger text-text-primary hover:bg-button-danger--hover hover:text-text-primary",
@@ -108,7 +108,9 @@ const Button = ({ asChild = false, children, className, dataTestId, disabled, en
108
108
  }), customErrorMessageNode ?? /* @__PURE__ */ jsx(ErrorMessage, {
109
109
  dataTestId: "spectral-button-error-message",
110
110
  id: errorMessageId,
111
- message: errorMessageValue
111
+ message: errorMessageValue,
112
+ messageReserveLines,
113
+ messageReserveSpace
112
114
  })]
113
115
  });
114
116
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","names":[],"sources":["../src/components/Button/Button.tsx"],"sourcesContent":["import { LoaderIcon } from '@components/Icons'\nimport { Slot, type AsChildProp } from '@primitives/slot'\nimport { ErrorMessage } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { Children, isValidElement, useId, type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from 'react'\n\nconst buttonVariants = cva(\n `gap-2 rounded-lg font-semibold [&_svg]:size-4 relative flex cursor-pointer items-center justify-center border font-sans! whitespace-nowrap transition-colors focus:outline-none focus-visible:outline-1 focus-visible:outline-offset-2 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0`,\n {\n variants: {\n variant: {\n primary: `border-button-primary-border bg-button-primary-bg text-button-primary-text hover:border-button-primary-border--hover hover:bg-button-primary-bg--hover hover:text-button-primary-text--hover focus-visible:outline-button-primary-border disabled:border-button-primary-border--disabled disabled:bg-button-primary-bg--disabled disabled:text-button-primary-text--disabled`,\n secondary: `border-button-secondary-border bg-button-secondary-bg text-button-secondary-text hover:bg-button-secondary-bg--hover hover:text-button-secondary-text--hover focus-visible:outline-button-secondary-border disabled:border-button-secondary-border--disabled disabled:bg-button-secondary-bg--disabled disabled:text-button-secondary-text--disabled`,\n outline: `border-button-outline-border bg-button-outline-bg text-button-outline-text hover:bg-button-outline-bg--hover hover:text-button-outline-text--hover focus-visible:outline-button-outline-border disabled:border-button-outline-border--disabled disabled:text-button-outline-text--disabled`,\n ghost: 'border-none bg-button-ghost-bg text-button-ghost-text hover:bg-button-ghost-bg--hover hover:text-button-ghost-text--hover focus-visible:outline-none disabled:text-button-ghost-text--disabled',\n unstyled: 'font-inherit! font-normal gap-0 p-0! inline-flex w-auto! justify-start rounded-none! border-none! bg-transparent text-inherit shadow-none! hover:cursor-pointer hover:bg-transparent focus-visible:outline-button-outline-border disabled:opacity-50',\n },\n size: {\n small: 'py-1 px-4 text-sm! w-fit',\n default: 'py-2 px-6 text-base! w-fit',\n fullWidth: 'py-2 px-6 text-base! w-full',\n },\n state: {\n default: '',\n error: 'bg-button-danger text-text-primary transition focus-visible:outline-button-danger',\n loading: 'pointer-events-none cursor-wait',\n },\n },\n defaultVariants: {\n variant: 'primary',\n state: 'default',\n size: 'default',\n },\n },\n)\n\nexport interface ButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'>, VariantProps<typeof buttonVariants> {\n dataTestId?: string\n disabled?: boolean\n endIcon?: ReactNode\n errorMessage?: string | ReactNode\n label?: string\n onClick?: (event: MouseEvent<HTMLElement>) => void\n size?: 'small' | 'default' | 'fullWidth'\n startIcon?: ReactNode\n state?: 'default' | 'error' | 'loading'\n type?: 'button' | 'submit' | 'reset'\n variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'unstyled'\n}\n\nexport const Button = ({\n asChild = false,\n children,\n className,\n dataTestId,\n disabled,\n endIcon,\n errorMessage,\n id,\n label,\n onClick,\n ref,\n size,\n startIcon,\n state,\n type = 'button',\n variant = 'primary',\n ...props\n}: ButtonProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const stateStyles = {\n error: {\n primary: 'bg-button-danger border-button-danger text-text-primary hover:bg-button-danger--hover hover:text-text-primary',\n secondary: 'bg-button-danger border-button-danger text-text-primary hover:bg-button-danger--hover hover:text-text-primary',\n outline: 'bg-transparent border-button-danger text-button-danger hover:border-button-danger--hover hover:text-button-danger/80',\n ghost: 'bg-transparent text-button-danger hover:text-button-danger/80',\n unstyled: 'bg-transparent text-button-danger',\n },\n loading: {\n primary: 'bg-button-primary-bg--disabled border-button-primary-border--disabled text-button-primary-text--disabled pointer-events-none',\n secondary: 'bg-button-secondary-bg--disabled border-button-secondary-border--disabled text-button-secondary-text--disabled pointer-events-none',\n outline: 'bg-button-outline-bg--disabled border-button-outline-border--disabled text-button-outline-text--disabled pointer-events-none',\n ghost: 'bg-transparent text-button-ghost-text--disabled pointer-events-none',\n unstyled: 'bg-transparent opacity-50 pointer-events-none',\n },\n }\n\n const classes = cn(buttonVariants({ variant, state, size }), state === 'error' && stateStyles.error[variant as keyof typeof stateStyles.error], state === 'loading' && stateStyles.loading[variant as keyof typeof stateStyles.loading], className)\n const generatedId = useId()\n const errorMessageId = id ? `${id}-error` : `${generatedId}-error`\n\n const canUseAsChild = asChild && isValidElement(children) && Children.count(children) === 1\n const content = canUseAsChild ? undefined : (children ?? label)\n const errorMessageValue = state === 'error' && typeof errorMessage === 'string' ? errorMessage : null\n const customErrorMessageNode = state === 'error' && isValidElement(errorMessage) ? errorMessage : null\n\n if (!canUseAsChild && !content) {\n throw new Error('Button requires either `label` or `children`')\n }\n\n const handleDisabledClickCapture = (event: MouseEvent<HTMLElement>) => {\n if (!disabled) return\n event.preventDefault()\n event.stopPropagation()\n }\n\n const Comp = canUseAsChild ? Slot : 'button'\n\n return (\n <div className={cn('flex flex-col justify-items-center', size)}>\n <Comp\n aria-disabled={disabled}\n className={cn(classes, canUseAsChild && 'no-underline! before:content-none after:content-none', canUseAsChild && disabled && 'pointer-events-none opacity-50')}\n data-as-child={canUseAsChild ? 'true' : undefined}\n data-state={state}\n data-testid={dataTestId ?? `spectral-button-${variant}`}\n data-variant={variant}\n disabled={canUseAsChild ? undefined : disabled}\n id={id}\n onClick={onClick}\n onClickCapture={canUseAsChild ? handleDisabledClickCapture : undefined}\n ref={ref}\n tabIndex={canUseAsChild && disabled ? -1 : undefined}\n type={canUseAsChild ? undefined : type}\n {...props}\n >\n {canUseAsChild ? (\n children\n ) : (\n <>\n {startIcon && (\n <span className={cn('flex', variant !== 'unstyled' && 'pr-1')} aria-hidden data-testid='spectral-button-start-icon'>\n {startIcon}\n </span>\n )}\n {state === 'loading' && <LoaderIcon className='ml-2 motion-safe:animate-spin' size={16} />}\n {content}\n {endIcon && state !== 'loading' && (\n <span className={cn('flex', variant !== 'unstyled' && 'pl-2')} aria-hidden data-testid='spectral-button-end-icon'>\n {endIcon}\n </span>\n )}\n </>\n )}\n </Comp>\n {customErrorMessageNode ?? <ErrorMessage dataTestId='spectral-button-error-message' id={errorMessageId} message={errorMessageValue} />}\n </div>\n )\n}\nButton.displayName = 'Button'\n"],"mappings":";;;;;;;;;;AAOA,MAAM,iBAAiB,IACrB,oTACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,WAAW;GACX,SAAS;GACT,OAAO;GACP,UAAU;GACX;EACD,MAAM;GACJ,OAAO;GACP,SAAS;GACT,WAAW;GACZ;EACD,OAAO;GACL,SAAS;GACT,OAAO;GACP,SAAS;GACV;EACF;CACD,iBAAiB;EACf,SAAS;EACT,OAAO;EACP,MAAM;EACP;CACF,CACF;AAgBD,MAAa,UAAU,EACrB,UAAU,OACV,UACA,WACA,YACA,UACA,SACA,cACA,IACA,OACA,SACA,KACA,MACA,WACA,OACA,OAAO,UACP,UAAU,WACV,GAAG,YAGC;CACJ,MAAM,cAAc;EAClB,OAAO;GACL,SAAS;GACT,WAAW;GACX,SAAS;GACT,OAAO;GACP,UAAU;GACX;EACD,SAAS;GACP,SAAS;GACT,WAAW;GACX,SAAS;GACT,OAAO;GACP,UAAU;GACX;EACF;CAED,MAAM,UAAU,GAAG,eAAe;EAAE;EAAS;EAAO;EAAM,CAAC,EAAE,UAAU,WAAW,YAAY,MAAM,UAA4C,UAAU,aAAa,YAAY,QAAQ,UAA8C,UAAU;CACnP,MAAM,cAAc,OAAO;CAC3B,MAAM,iBAAiB,KAAK,GAAG,GAAG,UAAU,GAAG,YAAY;CAE3D,MAAM,gBAAgB,WAAW,eAAe,SAAS,IAAI,SAAS,MAAM,SAAS,KAAK;CAC1F,MAAM,UAAU,gBAAgB,SAAa,YAAY;CACzD,MAAM,oBAAoB,UAAU,WAAW,OAAO,iBAAiB,WAAW,eAAe;CACjG,MAAM,yBAAyB,UAAU,WAAW,eAAe,aAAa,GAAG,eAAe;AAElG,KAAI,CAAC,iBAAiB,CAAC,QACrB,OAAM,IAAI,MAAM,+CAA+C;CAGjE,MAAM,8BAA8B,UAAmC;AACrE,MAAI,CAAC,SAAU;AACf,QAAM,gBAAgB;AACtB,QAAM,iBAAiB;;CAGzB,MAAM,OAAO,gBAAgB,OAAO;AAEpC,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,sCAAsC,KAAK;YAA9D,CACE,oBAAC,MAAD;GACE,iBAAe;GACf,WAAW,GAAG,SAAS,iBAAiB,wDAAwD,iBAAiB,YAAY,iCAAiC;GAC9J,iBAAe,gBAAgB,SAAS;GACxC,cAAY;GACZ,eAAa,cAAc,mBAAmB;GAC9C,gBAAc;GACd,UAAU,gBAAgB,SAAY;GAClC;GACK;GACT,gBAAgB,gBAAgB,6BAA6B;GACxD;GACL,UAAU,iBAAiB,WAAW,KAAK;GAC3C,MAAM,gBAAgB,SAAY;GAClC,GAAI;aAEH,gBACC,WAEA;IACG,aACC,oBAAC,QAAD;KAAM,WAAW,GAAG,QAAQ,YAAY,cAAc,OAAO;KAAE;KAAY,eAAY;eACpF;KACI;IAER,UAAU,aAAa,oBAAC,YAAD;KAAY,WAAU;KAAgC,MAAM;KAAM;IACzF;IACA,WAAW,UAAU,aACpB,oBAAC,QAAD;KAAM,WAAW,GAAG,QAAQ,YAAY,cAAc,OAAO;KAAE;KAAY,eAAY;eACpF;KACI;IAER;GAEA,GACN,0BAA0B,oBAAC,cAAD;GAAc,YAAW;GAAgC,IAAI;GAAgB,SAAS;GAAqB,EAClI;;;AAGV,OAAO,cAAc"}
1
+ {"version":3,"file":"Button.js","names":[],"sources":["../src/components/Button/Button.tsx"],"sourcesContent":["import { LoaderIcon } from '@components/Icons'\nimport { Slot, type AsChildProp } from '@primitives/slot'\nimport { ErrorMessage } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { Children, isValidElement, useId, type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from 'react'\n\nconst buttonVariants = cva(\n `gap-2 rounded-lg font-semibold [&_svg]:size-4 relative flex cursor-pointer items-center justify-center border font-sans! whitespace-nowrap transition-colors focus:outline-none focus-visible:outline-1 focus-visible:outline-offset-2 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0`,\n {\n variants: {\n variant: {\n primary: `border-button-primary-border bg-button-primary-bg text-button-primary-text hover:border-button-primary-border--hover hover:bg-button-primary-bg--hover hover:text-button-primary-text--hover focus-visible:outline-button-primary-border disabled:border-button-primary-border--disabled disabled:bg-button-primary-bg--disabled disabled:text-button-primary-text--disabled`,\n secondary: `border-button-secondary-border bg-button-secondary-bg text-button-secondary-text hover:bg-button-secondary-bg--hover hover:text-button-secondary-text--hover focus-visible:outline-button-secondary-border disabled:border-button-secondary-border--disabled disabled:bg-button-secondary-bg--disabled disabled:text-button-secondary-text--disabled`,\n outline: `border-button-outline-border bg-button-outline-bg text-button-outline-text hover:bg-button-outline-bg--hover hover:text-button-outline-text--hover focus-visible:outline-button-outline-border disabled:border-button-outline-border--disabled disabled:text-button-outline-text--disabled`,\n ghost: 'border-none bg-button-ghost-bg text-button-ghost-text hover:bg-button-ghost-bg--hover hover:text-button-ghost-text--hover focus-visible:outline-none disabled:text-button-ghost-text--disabled',\n unstyled: 'font-inherit! font-normal gap-0 p-0! inline-flex w-auto! justify-start rounded-none! border-none! bg-transparent text-inherit shadow-none! hover:cursor-pointer hover:bg-transparent focus-visible:outline-button-outline-border disabled:opacity-50',\n },\n size: {\n small: 'py-1 px-4 text-sm! w-fit',\n default: 'py-2 px-6 text-base! w-fit',\n fullWidth: 'py-2 px-6 text-base! w-full',\n },\n state: {\n default: '',\n error: 'bg-button-danger text-text-primary transition focus-visible:outline-button-danger',\n loading: 'pointer-events-none cursor-wait',\n },\n },\n defaultVariants: {\n variant: 'primary',\n state: 'default',\n size: 'default',\n },\n },\n)\n\nexport interface ButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'>, VariantProps<typeof buttonVariants> {\n dataTestId?: string\n disabled?: boolean\n endIcon?: ReactNode\n errorMessage?: string | ReactNode\n label?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n onClick?: (event: MouseEvent<HTMLElement>) => void\n size?: 'small' | 'default' | 'fullWidth'\n startIcon?: ReactNode\n state?: 'default' | 'error' | 'loading'\n type?: 'button' | 'submit' | 'reset'\n variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'unstyled'\n}\n\nexport const Button = ({\n asChild = false,\n children,\n className,\n dataTestId,\n disabled,\n endIcon,\n errorMessage,\n id,\n label,\n messageReserveLines = 1,\n messageReserveSpace = true,\n onClick,\n ref,\n size,\n startIcon,\n state,\n type = 'button',\n variant = 'primary',\n ...props\n}: ButtonProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const stateStyles = {\n error: {\n primary: 'bg-button-danger border-button-danger text-text-primary hover:bg-button-danger--hover hover:text-text-primary',\n secondary: 'bg-button-danger border-button-danger text-text-primary hover:bg-button-danger--hover hover:text-text-primary',\n outline: 'bg-transparent border-button-danger text-button-danger hover:border-button-danger--hover hover:text-button-danger/80',\n ghost: 'bg-transparent text-button-danger hover:text-button-danger/80',\n unstyled: 'bg-transparent text-button-danger',\n },\n loading: {\n primary: 'bg-button-primary-bg--disabled border-button-primary-border--disabled text-button-primary-text--disabled pointer-events-none',\n secondary: 'bg-button-secondary-bg--disabled border-button-secondary-border--disabled text-button-secondary-text--disabled pointer-events-none',\n outline: 'bg-button-outline-bg--disabled border-button-outline-border--disabled text-button-outline-text--disabled pointer-events-none',\n ghost: 'bg-transparent text-button-ghost-text--disabled pointer-events-none',\n unstyled: 'bg-transparent opacity-50 pointer-events-none',\n },\n }\n\n const classes = cn(buttonVariants({ variant, state, size }), state === 'error' && stateStyles.error[variant as keyof typeof stateStyles.error], state === 'loading' && stateStyles.loading[variant as keyof typeof stateStyles.loading], className)\n const generatedId = useId()\n const errorMessageId = id ? `${id}-error` : `${generatedId}-error`\n\n const canUseAsChild = asChild && isValidElement(children) && Children.count(children) === 1\n const content = canUseAsChild ? undefined : (children ?? label)\n const errorMessageValue = state === 'error' && typeof errorMessage === 'string' ? errorMessage : null\n const customErrorMessageNode = state === 'error' && isValidElement(errorMessage) ? errorMessage : null\n\n if (!canUseAsChild && !content) {\n throw new Error('Button requires either `label` or `children`')\n }\n\n const handleDisabledClickCapture = (event: MouseEvent<HTMLElement>) => {\n if (!disabled) return\n event.preventDefault()\n event.stopPropagation()\n }\n\n const Comp = canUseAsChild ? Slot : 'button'\n\n return (\n <div className={cn('flex flex-col justify-items-center', size)}>\n <Comp\n aria-disabled={disabled}\n className={cn(classes, canUseAsChild && 'no-underline! before:content-none after:content-none', canUseAsChild && disabled && 'pointer-events-none opacity-50')}\n data-as-child={canUseAsChild ? 'true' : undefined}\n data-state={state}\n data-testid={dataTestId ?? `spectral-button-${variant}`}\n data-variant={variant}\n disabled={canUseAsChild ? undefined : disabled}\n id={id}\n onClick={onClick}\n onClickCapture={canUseAsChild ? handleDisabledClickCapture : undefined}\n ref={ref}\n tabIndex={canUseAsChild && disabled ? -1 : undefined}\n type={canUseAsChild ? undefined : type}\n {...props}\n >\n {canUseAsChild ? (\n children\n ) : (\n <>\n {startIcon && (\n <span className={cn('flex', variant !== 'unstyled' && 'pr-1')} aria-hidden data-testid='spectral-button-start-icon'>\n {startIcon}\n </span>\n )}\n {state === 'loading' && <LoaderIcon className='ml-2 motion-safe:animate-spin' size={16} />}\n {content}\n {endIcon && state !== 'loading' && (\n <span className={cn('flex', variant !== 'unstyled' && 'pl-2')} aria-hidden data-testid='spectral-button-end-icon'>\n {endIcon}\n </span>\n )}\n </>\n )}\n </Comp>\n {customErrorMessageNode ?? <ErrorMessage dataTestId='spectral-button-error-message' id={errorMessageId} message={errorMessageValue} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace} />}\n </div>\n )\n}\nButton.displayName = 'Button'\n"],"mappings":";;;;;;;;;;AAOA,MAAM,iBAAiB,IACrB,oTACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,WAAW;GACX,SAAS;GACT,OAAO;GACP,UAAU;GACX;EACD,MAAM;GACJ,OAAO;GACP,SAAS;GACT,WAAW;GACZ;EACD,OAAO;GACL,SAAS;GACT,OAAO;GACP,SAAS;GACV;EACF;CACD,iBAAiB;EACf,SAAS;EACT,OAAO;EACP,MAAM;EACP;CACF,CACF;AAkBD,MAAa,UAAU,EACrB,UAAU,OACV,UACA,WACA,YACA,UACA,SACA,cACA,IACA,OACA,sBAAsB,GACtB,sBAAsB,MACtB,SACA,KACA,MACA,WACA,OACA,OAAO,UACP,UAAU,WACV,GAAG,YAGC;CACJ,MAAM,cAAc;EAClB,OAAO;GACL,SAAS;GACT,WAAW;GACX,SAAS;GACT,OAAO;GACP,UAAU;GACX;EACD,SAAS;GACP,SAAS;GACT,WAAW;GACX,SAAS;GACT,OAAO;GACP,UAAU;GACX;EACF;CAED,MAAM,UAAU,GAAG,eAAe;EAAE;EAAS;EAAO;EAAM,CAAC,EAAE,UAAU,WAAW,YAAY,MAAM,UAA4C,UAAU,aAAa,YAAY,QAAQ,UAA8C,UAAU;CACnP,MAAM,cAAc,OAAO;CAC3B,MAAM,iBAAiB,KAAK,GAAG,GAAG,UAAU,GAAG,YAAY;CAE3D,MAAM,gBAAgB,WAAW,eAAe,SAAS,IAAI,SAAS,MAAM,SAAS,KAAK;CAC1F,MAAM,UAAU,gBAAgB,SAAa,YAAY;CACzD,MAAM,oBAAoB,UAAU,WAAW,OAAO,iBAAiB,WAAW,eAAe;CACjG,MAAM,yBAAyB,UAAU,WAAW,eAAe,aAAa,GAAG,eAAe;AAElG,KAAI,CAAC,iBAAiB,CAAC,QACrB,OAAM,IAAI,MAAM,+CAA+C;CAGjE,MAAM,8BAA8B,UAAmC;AACrE,MAAI,CAAC,SAAU;AACf,QAAM,gBAAgB;AACtB,QAAM,iBAAiB;;CAGzB,MAAM,OAAO,gBAAgB,OAAO;AAEpC,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,sCAAsC,KAAK;YAA9D,CACE,oBAAC,MAAD;GACE,iBAAe;GACf,WAAW,GAAG,SAAS,iBAAiB,wDAAwD,iBAAiB,YAAY,iCAAiC;GAC9J,iBAAe,gBAAgB,SAAS;GACxC,cAAY;GACZ,eAAa,cAAc,mBAAmB;GAC9C,gBAAc;GACd,UAAU,gBAAgB,SAAY;GAClC;GACK;GACT,gBAAgB,gBAAgB,6BAA6B;GACxD;GACL,UAAU,iBAAiB,WAAW,KAAK;GAC3C,MAAM,gBAAgB,SAAY;GAClC,GAAI;aAEH,gBACC,WAEA;IACG,aACC,oBAAC,QAAD;KAAM,WAAW,GAAG,QAAQ,YAAY,cAAc,OAAO;KAAE;KAAY,eAAY;eACpF;KACI;IAER,UAAU,aAAa,oBAAC,YAAD;KAAY,WAAU;KAAgC,MAAM;KAAM;IACzF;IACA,WAAW,UAAU,aACpB,oBAAC,QAAD;KAAM,WAAW,GAAG,QAAQ,YAAY,cAAc,OAAO;KAAE;KAAY,eAAY;eACpF;KACI;IAER;GAEA,GACN,0BAA0B,oBAAC,cAAD;GAAc,YAAW;GAAgC,IAAI;GAAgB,SAAS;GAAwC;GAA0C;GAAuB,EACtN;;;AAGV,OAAO,cAAc"}
@@ -14,6 +14,8 @@ interface CheckboxProps extends Omit<ComponentPropsWithoutRef<typeof CheckboxBas
14
14
  id?: string;
15
15
  label?: string;
16
16
  labelText?: string;
17
+ messageReserveLines?: number;
18
+ messageReserveSpace?: boolean;
17
19
  name?: string;
18
20
  onCheckedChange: (value: boolean | 'indeterminate') => void;
19
21
  required?: boolean;
@@ -28,6 +30,8 @@ declare function Checkbox({
28
30
  id,
29
31
  label,
30
32
  labelText,
33
+ messageReserveLines,
34
+ messageReserveSpace,
31
35
  onCheckedChange,
32
36
  ref,
33
37
  required,
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.d.ts","names":[],"sources":["../src/components/Checkbox/Checkbox.tsx"],"mappings":";;;;;;;UAMiB,aAAA,SAAsB,IAAA,CAAK,wBAAA,QAAgC,YAAA;EAC1E,kBAAA;EACA,YAAA;EACA,OAAA;EACA,QAAA;EACA,YAAA,GAAe,kBAAA;EACf,EAAA;EACA,KAAA;EACA,SAAA;EACA,IAAA;EACA,eAAA,GAAkB,KAAA;EAClB,QAAA;EACA,KAAA,GAAQ,cAAA;EACR,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA;EAIjB,OAAA;EACA,SAAA;EACA,YAAA;EACA,EAAA;EACA,KAAA;EACA,SAAA;EACA,eAAA;EACA,GAAA;EACA,QAAA;EACA,KAAA;EACA,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN,SAAA;EAAA,GACX;AAAA,GACF,aAAA;EACD,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,YAAA;AAAA,IAC/B,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"Checkbox.d.ts","names":[],"sources":["../src/components/Checkbox/Checkbox.tsx"],"mappings":";;;;;;;UAMiB,aAAA,SAAsB,IAAA,CAAK,wBAAA,QAAgC,YAAA;EAC1E,kBAAA;EACA,YAAA;EACA,OAAA;EACA,QAAA;EACA,YAAA,GAAe,kBAAA;EACf,EAAA;EACA,KAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,eAAA,GAAkB,KAAA;EAClB,QAAA;EACA,KAAA,GAAQ,cAAA;EACR,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA;EAIjB,OAAA;EACA,SAAA;EACA,YAAA;EACA,EAAA;EACA,KAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EACA,eAAA;EACA,GAAA;EACA,QAAA;EACA,KAAA;EACA,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN,SAAA;EAAA,GACX;AAAA,GACF,aAAA;EACD,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,YAAA;AAAA,IAC/B,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
package/dist/Checkbox.js CHANGED
@@ -9,7 +9,7 @@ import "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
10
 
11
11
  //#region src/components/Checkbox/Checkbox.tsx
12
- const Checkbox = ({ checked, className, errorMessage, id, label, labelText, onCheckedChange, ref, required, state = "default", warningMessage, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, ...props }) => {
12
+ const Checkbox = ({ checked, className, errorMessage, id, label, labelText, messageReserveLines = 1, messageReserveSpace = true, onCheckedChange, ref, required, state = "default", warningMessage, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, ...props }) => {
13
13
  const inputId = useFormFieldId(id, props.name);
14
14
  const resolvedLabelText = labelText ?? label;
15
15
  const errorMessageId = `${inputId}-error`;
@@ -53,12 +53,16 @@ const Checkbox = ({ checked, className, errorMessage, id, label, labelText, onCh
53
53
  /* @__PURE__ */ jsx(ErrorMessage, {
54
54
  dataTestId: "spectral-checkbox-error-message",
55
55
  id: errorMessageId,
56
- message: state === "error" ? errorMessage : null
56
+ message: state === "error" ? errorMessage : null,
57
+ messageReserveLines,
58
+ messageReserveSpace: messageReserveSpace && state === "error"
57
59
  }),
58
60
  /* @__PURE__ */ jsx(WarningMessage, {
59
61
  dataTestId: "spectral-checkbox-warning-message",
60
62
  id: warningMessageId,
61
- message: state === "warning" ? warningMessage : null
63
+ message: state === "warning" ? warningMessage : null,
64
+ messageReserveLines,
65
+ messageReserveSpace: messageReserveSpace && state === "warning"
62
66
  })
63
67
  ] });
64
68
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.js","names":[],"sources":["../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { CheckmarkIcon, MinusIcon } from '@components/Icons'\nimport { ErrorMessage, WarningMessage, useFormFieldId, type BaseFormFieldProps, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentPropsWithoutRef, type ComponentRef, type Ref } from 'react'\nimport { CheckboxBase, CheckboxIndicator } from './CheckboxBase'\n\nexport interface CheckboxProps extends Omit<ComponentPropsWithoutRef<typeof CheckboxBase>, 'onCheckedChange'> {\n 'aria-describedby'?: string\n 'aria-label'?: string\n checked?: boolean | 'indeterminate'\n disabled?: boolean\n errorMessage?: BaseFormFieldProps['errorMessage']\n id?: string\n label?: string\n labelText?: string\n name?: string\n onCheckedChange: (value: boolean | 'indeterminate') => void\n required?: boolean\n state?: FormFieldState\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport const Checkbox = ({\n checked,\n className,\n errorMessage,\n id,\n label,\n labelText,\n onCheckedChange,\n ref,\n required,\n state = 'default',\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n ...props\n}: CheckboxProps & {\n ref?: Ref<ComponentRef<typeof CheckboxBase>>\n}) => {\n const inputId = useFormFieldId(id, props.name)\n const resolvedLabelText = labelText ?? label\n const errorMessageId = `${inputId}-error`\n const warningMessageId = `${inputId}-warning`\n const messageId = state === 'error' && errorMessage && errorMessageId ? errorMessageId : state === 'warning' && warningMessage && warningMessageId ? warningMessageId : undefined\n\n return (\n <div>\n <div className='gap-2 flex items-center'>\n <CheckboxBase\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n aria-invalid={state === 'error' ? true : undefined}\n aria-label={ariaLabel ?? (!resolvedLabelText ? 'Checkbox' : undefined)}\n aria-required={required ?? undefined}\n checked={checked}\n className={cn(\n 'checkbox peer h-5 w-5 rounded-sm shrink-0 border border-checkbox-border hover:opacity-80 focus:outline-none focus-visible:outline-1 focus-visible:outline-checkbox-border--focus',\n 'focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-checkbox-border--checked',\n className,\n )}\n data-field-state={state}\n data-testid='spectral-checkbox'\n id={inputId}\n onCheckedChange={onCheckedChange}\n ref={ref}\n {...props}\n >\n <CheckboxIndicator data-testid='spectral-checkbox-indicator' className={cn('checkbox-indicator flex items-center justify-center text-checkbox-indicator--checked')}>\n {checked === 'indeterminate' ? <MinusIcon className='checkbox-indeterminate' size={16} strokeWidth={4} /> : <CheckmarkIcon className='checkbox-check' size={16} strokeWidth={4} />}\n </CheckboxIndicator>\n </CheckboxBase>\n {resolvedLabelText && (\n <label className='text-md peer-disabled:text-neutral-400 text-text-primary' data-testid='spectral-checkbox-label' htmlFor={inputId}>\n {resolvedLabelText}\n </label>\n )}\n </div>\n <ErrorMessage dataTestId='spectral-checkbox-error-message' id={errorMessageId} message={state === 'error' ? errorMessage : null} />\n <WarningMessage dataTestId='spectral-checkbox-warning-message' id={warningMessageId} message={state === 'warning' ? warningMessage : null} />\n </div>\n )\n}\nCheckbox.displayName = 'Checkbox'\n"],"mappings":";;;;;;;;;;;AAuBA,MAAa,YAAY,EACvB,SACA,WACA,cACA,IACA,OACA,WACA,iBACA,KACA,UACA,QAAQ,WACR,gBACA,oBAAoB,iBACpB,cAAc,WACd,GAAG,YAGC;CACJ,MAAM,UAAU,eAAe,IAAI,MAAM,KAAK;CAC9C,MAAM,oBAAoB,aAAa;CACvC,MAAM,iBAAiB,GAAG,QAAQ;CAClC,MAAM,mBAAmB,GAAG,QAAQ;AAGpC,QACE,qBAAC,OAAD;EACE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,cAAD;IACE,oBAAkB,CANR,UAAU,WAAW,gBAAgB,iBAAiB,iBAAiB,UAAU,aAAa,kBAAkB,mBAAmB,mBAAmB,QAMlI,gBAAgB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;IAC5E,gBAAc,UAAU,UAAU,OAAO;IACzC,cAAY,cAAc,CAAC,oBAAoB,aAAa;IAC5D,iBAAe,YAAY;IAClB;IACT,WAAW,GACT,oLACA,uIACA,UACD;IACD,oBAAkB;IAClB,eAAY;IACZ,IAAI;IACa;IACZ;IACL,GAAI;cAEJ,oBAAC,mBAAD;KAAmB,eAAY;KAA8B,WAAW,GAAG,uFAAuF;eAC/J,YAAY,kBAAkB,oBAAC,WAAD;MAAW,WAAU;MAAyB,MAAM;MAAI,aAAa;MAAK,IAAG,oBAAC,eAAD;MAAe,WAAU;MAAiB,MAAM;MAAI,aAAa;MAAK;KAChK;IACP,GACd,qBACC,oBAAC,SAAD;IAAO,WAAU;IAA2D,eAAY;IAA0B,SAAS;cACxH;IACK,EAEN;;EACN,oBAAC,cAAD;GAAc,YAAW;GAAkC,IAAI;GAAgB,SAAS,UAAU,UAAU,eAAe;GAAQ;EACnI,oBAAC,gBAAD;GAAgB,YAAW;GAAoC,IAAI;GAAkB,SAAS,UAAU,YAAY,iBAAiB;GAAQ;EACzI;;AAGV,SAAS,cAAc"}
1
+ {"version":3,"file":"Checkbox.js","names":[],"sources":["../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { CheckmarkIcon, MinusIcon } from '@components/Icons'\nimport { ErrorMessage, WarningMessage, useFormFieldId, type BaseFormFieldProps, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentPropsWithoutRef, type ComponentRef, type Ref } from 'react'\nimport { CheckboxBase, CheckboxIndicator } from './CheckboxBase'\n\nexport interface CheckboxProps extends Omit<ComponentPropsWithoutRef<typeof CheckboxBase>, 'onCheckedChange'> {\n 'aria-describedby'?: string\n 'aria-label'?: string\n checked?: boolean | 'indeterminate'\n disabled?: boolean\n errorMessage?: BaseFormFieldProps['errorMessage']\n id?: string\n label?: string\n labelText?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n name?: string\n onCheckedChange: (value: boolean | 'indeterminate') => void\n required?: boolean\n state?: FormFieldState\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport const Checkbox = ({\n checked,\n className,\n errorMessage,\n id,\n label,\n labelText,\n messageReserveLines = 1,\n messageReserveSpace = true,\n onCheckedChange,\n ref,\n required,\n state = 'default',\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n ...props\n}: CheckboxProps & {\n ref?: Ref<ComponentRef<typeof CheckboxBase>>\n}) => {\n const inputId = useFormFieldId(id, props.name)\n const resolvedLabelText = labelText ?? label\n const errorMessageId = `${inputId}-error`\n const warningMessageId = `${inputId}-warning`\n const messageId = state === 'error' && errorMessage && errorMessageId ? errorMessageId : state === 'warning' && warningMessage && warningMessageId ? warningMessageId : undefined\n\n return (\n <div>\n <div className='gap-2 flex items-center'>\n <CheckboxBase\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n aria-invalid={state === 'error' ? true : undefined}\n aria-label={ariaLabel ?? (!resolvedLabelText ? 'Checkbox' : undefined)}\n aria-required={required ?? undefined}\n checked={checked}\n className={cn(\n 'checkbox peer h-5 w-5 rounded-sm shrink-0 border border-checkbox-border hover:opacity-80 focus:outline-none focus-visible:outline-1 focus-visible:outline-checkbox-border--focus',\n 'focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-checkbox-border--checked',\n className,\n )}\n data-field-state={state}\n data-testid='spectral-checkbox'\n id={inputId}\n onCheckedChange={onCheckedChange}\n ref={ref}\n {...props}\n >\n <CheckboxIndicator data-testid='spectral-checkbox-indicator' className={cn('checkbox-indicator flex items-center justify-center text-checkbox-indicator--checked')}>\n {checked === 'indeterminate' ? <MinusIcon className='checkbox-indeterminate' size={16} strokeWidth={4} /> : <CheckmarkIcon className='checkbox-check' size={16} strokeWidth={4} />}\n </CheckboxIndicator>\n </CheckboxBase>\n {resolvedLabelText && (\n <label className='text-md peer-disabled:text-neutral-400 text-text-primary' data-testid='spectral-checkbox-label' htmlFor={inputId}>\n {resolvedLabelText}\n </label>\n )}\n </div>\n <ErrorMessage dataTestId='spectral-checkbox-error-message' id={errorMessageId} message={state === 'error' ? errorMessage : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'error'} />\n <WarningMessage dataTestId='spectral-checkbox-warning-message' id={warningMessageId} message={state === 'warning' ? warningMessage : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'warning'} />\n </div>\n )\n}\nCheckbox.displayName = 'Checkbox'\n"],"mappings":";;;;;;;;;;;AAyBA,MAAa,YAAY,EACvB,SACA,WACA,cACA,IACA,OACA,WACA,sBAAsB,GACtB,sBAAsB,MACtB,iBACA,KACA,UACA,QAAQ,WACR,gBACA,oBAAoB,iBACpB,cAAc,WACd,GAAG,YAGC;CACJ,MAAM,UAAU,eAAe,IAAI,MAAM,KAAK;CAC9C,MAAM,oBAAoB,aAAa;CACvC,MAAM,iBAAiB,GAAG,QAAQ;CAClC,MAAM,mBAAmB,GAAG,QAAQ;AAGpC,QACE,qBAAC,OAAD;EACE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,cAAD;IACE,oBAAkB,CANR,UAAU,WAAW,gBAAgB,iBAAiB,iBAAiB,UAAU,aAAa,kBAAkB,mBAAmB,mBAAmB,QAMlI,gBAAgB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;IAC5E,gBAAc,UAAU,UAAU,OAAO;IACzC,cAAY,cAAc,CAAC,oBAAoB,aAAa;IAC5D,iBAAe,YAAY;IAClB;IACT,WAAW,GACT,oLACA,uIACA,UACD;IACD,oBAAkB;IAClB,eAAY;IACZ,IAAI;IACa;IACZ;IACL,GAAI;cAEJ,oBAAC,mBAAD;KAAmB,eAAY;KAA8B,WAAW,GAAG,uFAAuF;eAC/J,YAAY,kBAAkB,oBAAC,WAAD;MAAW,WAAU;MAAyB,MAAM;MAAI,aAAa;MAAK,IAAG,oBAAC,eAAD;MAAe,WAAU;MAAiB,MAAM;MAAI,aAAa;MAAK;KAChK;IACP,GACd,qBACC,oBAAC,SAAD;IAAO,WAAU;IAA2D,eAAY;IAA0B,SAAS;cACxH;IACK,EAEN;;EACN,oBAAC,cAAD;GAAc,YAAW;GAAkC,IAAI;GAAgB,SAAS,UAAU,UAAU,eAAe;GAA2B;GAAqB,qBAAqB,uBAAuB,UAAU;GAAW;EAC5O,oBAAC,gBAAD;GAAgB,YAAW;GAAoC,IAAI;GAAkB,SAAS,UAAU,YAAY,iBAAiB;GAA2B;GAAqB,qBAAqB,uBAAuB,UAAU;GAAa;EACpP;;AAGV,SAAS,cAAc"}
@@ -31,6 +31,8 @@ declare function Combobox({
31
31
  label,
32
32
  labelClassName,
33
33
  loadingMessage,
34
+ messageReserveLines,
35
+ messageReserveSpace,
34
36
  name,
35
37
  onChange,
36
38
  onValueChange,
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.d.ts","names":[],"sources":["../src/components/Combobox/Combobox.tsx"],"mappings":";;;;;;KA0BY,cAAA,GAAiB,UAAA;AAAA,UAEZ,aAAA,SAAsB,IAAA,CAAK,kBAAA;EAC1C,SAAA;EACA,YAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA;EACA,cAAA;EACA,cAAA;EACA,QAAA,IAAY,KAAA;EACZ,aAAA,IAAiB,KAAA;EACjB,OAAA,EAAS,cAAA;EACT,WAAA;EACA,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA;EAIjB,SAAA;EACA,QAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,YAAA;EACA,EAAA;EACA,KAAA;EACA,cAAA;EACA,cAAA;EACA,IAAA;EACA,QAAA;EACA,aAAA;EACA,OAAA;EACA,WAAA;EACA,GAAA;EACA,QAAA;EACA,KAAA;EACA,KAAA,EAAO,SAAA;EACP,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN;AAAA,GACb,aAAA;EAAkB,GAAA,GAAM,GAAA,CAAI,cAAA;AAAA,IAAiB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"Combobox.d.ts","names":[],"sources":["../src/components/Combobox/Combobox.tsx"],"mappings":";;;;;;KA0BY,cAAA,GAAiB,UAAA;AAAA,UAEZ,aAAA,SAAsB,IAAA,CAAK,kBAAA;EAC1C,SAAA;EACA,YAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA;EACA,cAAA;EACA,cAAA;EACA,QAAA,IAAY,KAAA;EACZ,aAAA,IAAiB,KAAA;EACjB,OAAA,EAAS,cAAA;EACT,WAAA;EACA,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA;EAIjB,SAAA;EACA,QAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,YAAA;EACA,EAAA;EACA,KAAA;EACA,cAAA;EACA,cAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA;EACA,aAAA;EACA,OAAA;EACA,WAAA;EACA,GAAA;EACA,QAAA;EACA,KAAA;EACA,KAAA,EAAO,SAAA;EACP,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN;AAAA,GACb,aAAA;EAAkB,GAAA,GAAM,GAAA,CAAI,cAAA;AAAA,IAAiB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
package/dist/Combobox.js CHANGED
@@ -13,7 +13,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
13
13
  import { Combobox as Combobox$1 } from "@base-ui/react";
14
14
 
15
15
  //#region src/components/Combobox/Combobox.tsx
16
- const Combobox = ({ className, disabled, defaultValue = "", dropdownWidth = "trigger", emptyMessage = "No options found.", errorMessage, id, label, labelClassName, loadingMessage = "Loading…", name, onChange, onValueChange, options, placeholder = "Search…", ref, required, state = "default", value: valueProp, warningMessage, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel }) => {
16
+ const Combobox = ({ className, disabled, defaultValue = "", dropdownWidth = "trigger", emptyMessage = "No options found.", errorMessage, id, label, labelClassName, loadingMessage = "Loading…", messageReserveLines = 1, messageReserveSpace = true, name, onChange, onValueChange, options, placeholder = "Search…", ref, required, state = "default", value: valueProp, warningMessage, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel }) => {
17
17
  if (!label && !ariaLabel) console.warn("Combobox: provide either `label` or `aria-label` for an accessible name.");
18
18
  const comboboxId = useFormFieldId(id, name);
19
19
  const listboxId = `${comboboxId}-listbox`;
@@ -182,12 +182,16 @@ const Combobox = ({ className, disabled, defaultValue = "", dropdownWidth = "tri
182
182
  /* @__PURE__ */ jsx(ErrorMessage, {
183
183
  dataTestId: "spectral-combobox-error-message",
184
184
  id: errorMessageId,
185
- message: isInvalid ? errorMessage ?? null : null
185
+ message: isInvalid ? errorMessage ?? null : null,
186
+ messageReserveLines,
187
+ messageReserveSpace: messageReserveSpace && state === "error"
186
188
  }),
187
189
  /* @__PURE__ */ jsx(WarningMessage, {
188
190
  dataTestId: "spectral-combobox-warning-message",
189
191
  id: warningMessageId,
190
- message: state === "warning" ? warningMessage ?? null : null
192
+ message: state === "warning" ? warningMessage ?? null : null,
193
+ messageReserveLines,
194
+ messageReserveSpace: messageReserveSpace && state === "warning"
191
195
  })
192
196
  ]
193
197
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","names":["ComboboxPrimitive"],"sources":["../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { Combobox as ComboboxPrimitive } from '@base-ui/react'\nimport { CheckmarkIcon, ChevronDownIcon, LoaderIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { InputGroup, InputGroupAddon } from '@primitives/input-group'\nimport {\n EmptyState,\n ErrorMessage,\n getAriaProps,\n getDropdownWidthStyles,\n getDropdownSurfaceClasses,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getOptionClasses,\n getTriggerClasses,\n LoadingState,\n WarningMessage,\n useFormFieldId,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useMemo, useRef, useState, type CSSProperties, type MouseEvent, type Ref } from 'react'\n\nexport type ComboboxOption = BaseOption\n\nexport interface ComboboxProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n labelClassName?: string\n loadingMessage?: string\n onChange?: (value: string) => void\n onValueChange?: (value: string) => void\n options: ComboboxOption[]\n placeholder?: string\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport const Combobox = ({\n className,\n disabled,\n defaultValue = '',\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found.',\n errorMessage,\n id,\n label,\n labelClassName,\n loadingMessage = 'Loading…',\n name,\n onChange,\n onValueChange,\n options,\n placeholder = 'Search…',\n ref,\n required,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: ComboboxProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n console.warn('Combobox: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const comboboxId = useFormFieldId(id, name)\n const listboxId = `${comboboxId}-listbox`\n const errorMessageId = getErrorMessageId(comboboxId)\n const warningMessageId = `${comboboxId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const isInvalid = state === 'error'\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [open, setOpen] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const inputRef = useRef<HTMLInputElement>(null)\n const suppressNextInputFocusOpenRef = useRef(false)\n const clearFocusSuppression = () => {\n suppressNextInputFocusOpenRef.current = false\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 selectedOption = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value])\n const filteredOptions = useMemo(() => {\n const query = inputValue.trim().toLowerCase()\n if (!query) return options\n return options.filter((option) => option.label.toLowerCase().includes(query))\n }, [inputValue, options])\n const showsSelectedValueAsPlaceholder = Boolean(selectedOption && inputValue.length === 0)\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--anchor-width)',\n })\n\n const handleValueChange = (nextOption: ComboboxOption | null, eventDetails: { reason?: string }) => {\n const nextValue = nextOption?.value ?? ''\n const shouldClearSelection = eventDetails.reason === 'item-press' && nextValue === value\n setValue(shouldClearSelection ? '' : nextValue)\n setInputValue('')\n setOpen(false)\n }\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (isDisabled || isLoading) {\n setOpen(false)\n return\n }\n\n if (!nextOpen) {\n setInputValue('')\n }\n\n setOpen(nextOpen)\n }\n\n const handleTriggerContainerClick = (event: MouseEvent<HTMLDivElement>) => {\n if (isDisabled || isLoading) return\n\n const target = event.target as HTMLElement\n if (target === inputRef.current) return\n if (target.closest('[data-slot=combobox-trigger-button]')) return\n inputRef.current?.focus()\n setOpen(true)\n }\n\n return (\n <div className='w-full' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', labelClassName, isDisabled && 'text-text-secondary')} data-testid='spectral-combobox-label' htmlFor={comboboxId}>\n {label}\n </Label>\n )}\n\n <ComboboxPrimitive.Root\n autoHighlight\n disabled={isDisabled || isLoading}\n filter={null}\n inputValue={inputValue}\n itemToStringLabel={(option: ComboboxOption) => option.label}\n itemToStringValue={(option: ComboboxOption) => option.value}\n openOnInputClick\n onInputValueChange={(nextInputValue, eventDetails) => {\n if (eventDetails.reason !== 'input-change' && eventDetails.reason !== 'input-clear') return\n setInputValue(nextInputValue)\n }}\n name={name}\n onOpenChange={handleOpenChange}\n onValueChange={handleValueChange}\n open={open}\n required={required}\n value={selectedOption}\n >\n <ComboboxPrimitive.InputGroup\n render={\n <InputGroup\n data-slot='combobox-content'\n data-state={state}\n data-testid='spectral-combobox-trigger'\n className={cn(getTriggerClasses(open, state), 'ring-0! outline-none focus-within:outline-none has-[[data-slot=input-group-control]:focus-visible]:outline-0', isDisabled && 'pointer-events-none cursor-not-allowed', className)}\n onClick={handleTriggerContainerClick}\n style={getFormFieldCSSProperties() as CSSProperties}\n />\n }\n >\n <ComboboxPrimitive.Input\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={cn(\n 'min-w-0 text-base flex-1 truncate overflow-hidden border-0 bg-transparent whitespace-nowrap text-input-text outline-hidden outline-0 placeholder:opacity-100 focus-visible:ring-0 focus-visible:outline-none',\n showsSelectedValueAsPlaceholder ? 'placeholder:text-input-text!' : 'placeholder:text-input-text-placeholder!',\n )}\n data-slot='input-group-control'\n id={comboboxId}\n onFocus={() => {\n if (suppressNextInputFocusOpenRef.current) {\n suppressNextInputFocusOpenRef.current = false\n return\n }\n\n if (!isDisabled && !isLoading) {\n setOpen(true)\n }\n }}\n ref={inputRef}\n placeholder={selectedOption?.label ?? placeholder}\n role='combobox'\n {...ariaProps}\n />\n <InputGroupAddon align='inline-end' className='cursor-pointer'>\n <ComboboxPrimitive.Trigger\n aria-label='Toggle options'\n className='cursor-pointer'\n data-slot='combobox-trigger-button'\n onPointerDownCapture={() => {\n const inputElement = inputRef.current\n if (!inputElement) {\n clearFocusSuppression()\n return\n }\n\n const activeElement = inputElement.ownerDocument.activeElement\n suppressNextInputFocusOpenRef.current = activeElement !== inputElement\n }}\n onPointerUpCapture={clearFocusSuppression}\n onPointerCancelCapture={clearFocusSuppression}\n onTouchEndCapture={clearFocusSuppression}\n >\n {isLoading ? <LoaderIcon className='size-5 motion-safe:animate-spin' /> : <ChevronDownIcon className={cn('size-5 shrink-0 transition-transform duration-200', open && 'rotate-180')} />}\n </ComboboxPrimitive.Trigger>\n </InputGroupAddon>\n </ComboboxPrimitive.InputGroup>\n\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Positioner align='start' className='isolate z-50' sideOffset={4}>\n <ComboboxPrimitive.Popup\n className={cn(\n 'p-1 z-50 motion-safe:data-closed:animate-out motion-safe:data-open:animate-in',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-closed:fade-out-0 motion-safe:data-closed:zoom-out-95 motion-safe:data-open:fade-in-0 motion-safe:data-open:zoom-in-95',\n 'max-h-[min(var(--available-height),18rem)] motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n // Keep a single scroll container (the list) to avoid dual scrollbar styles.\n 'min-w-32 origin-(--transform-origin) overflow-hidden',\n )}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-combobox-content'\n style={dropdownWidthStyle}\n >\n {isLoading ? (\n <LoadingState message={loadingMessage} />\n ) : filteredOptions.length === 0 ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <ComboboxPrimitive.List className='max-h-[min(var(--available-height),18rem)] overflow-y-auto' id={listboxId}>\n {filteredOptions.map((option) => (\n <ComboboxPrimitive.Item\n className={cn(getOptionClasses(!!option.disabled, false, value === option.value), 'group/command-item relative flex w-full items-center data-highlighted:bg-input-bg--hover')}\n data-testid='spectral-combobox-item'\n disabled={option.disabled}\n key={option.value}\n value={option}\n >\n <span className='min-w-0 flex-1 truncate whitespace-nowrap'>{option.label}</span>\n <ComboboxPrimitive.ItemIndicator\n render={\n <span className='right-2 h-4 w-4 absolute flex items-center justify-center'>\n <CheckmarkIcon size={16} />\n </span>\n }\n />\n </ComboboxPrimitive.Item>\n ))}\n </ComboboxPrimitive.List>\n )}\n </ComboboxPrimitive.Popup>\n </ComboboxPrimitive.Positioner>\n </ComboboxPrimitive.Portal>\n </ComboboxPrimitive.Root>\n\n <ErrorMessage dataTestId='spectral-combobox-error-message' id={errorMessageId} message={isInvalid ? (errorMessage ?? null) : null} />\n <WarningMessage dataTestId='spectral-combobox-warning-message' id={warningMessageId} message={state === 'warning' ? (warningMessage ?? null) : null} />\n </div>\n )\n}\nCombobox.displayName = 'Combobox'\n"],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.js","names":["ComboboxPrimitive"],"sources":["../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { Combobox as ComboboxPrimitive } from '@base-ui/react'\nimport { CheckmarkIcon, ChevronDownIcon, LoaderIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { InputGroup, InputGroupAddon } from '@primitives/input-group'\nimport {\n EmptyState,\n ErrorMessage,\n getAriaProps,\n getDropdownWidthStyles,\n getDropdownSurfaceClasses,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getOptionClasses,\n getTriggerClasses,\n LoadingState,\n WarningMessage,\n useFormFieldId,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useMemo, useRef, useState, type CSSProperties, type MouseEvent, type Ref } from 'react'\n\nexport type ComboboxOption = BaseOption\n\nexport interface ComboboxProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n labelClassName?: string\n loadingMessage?: string\n onChange?: (value: string) => void\n onValueChange?: (value: string) => void\n options: ComboboxOption[]\n placeholder?: string\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport const Combobox = ({\n className,\n disabled,\n defaultValue = '',\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found.',\n errorMessage,\n id,\n label,\n labelClassName,\n loadingMessage = 'Loading…',\n messageReserveLines = 1,\n messageReserveSpace = true,\n name,\n onChange,\n onValueChange,\n options,\n placeholder = 'Search…',\n ref,\n required,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: ComboboxProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n console.warn('Combobox: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const comboboxId = useFormFieldId(id, name)\n const listboxId = `${comboboxId}-listbox`\n const errorMessageId = getErrorMessageId(comboboxId)\n const warningMessageId = `${comboboxId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const isInvalid = state === 'error'\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [open, setOpen] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const inputRef = useRef<HTMLInputElement>(null)\n const suppressNextInputFocusOpenRef = useRef(false)\n const clearFocusSuppression = () => {\n suppressNextInputFocusOpenRef.current = false\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 selectedOption = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value])\n const filteredOptions = useMemo(() => {\n const query = inputValue.trim().toLowerCase()\n if (!query) return options\n return options.filter((option) => option.label.toLowerCase().includes(query))\n }, [inputValue, options])\n const showsSelectedValueAsPlaceholder = Boolean(selectedOption && inputValue.length === 0)\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--anchor-width)',\n })\n\n const handleValueChange = (nextOption: ComboboxOption | null, eventDetails: { reason?: string }) => {\n const nextValue = nextOption?.value ?? ''\n const shouldClearSelection = eventDetails.reason === 'item-press' && nextValue === value\n setValue(shouldClearSelection ? '' : nextValue)\n setInputValue('')\n setOpen(false)\n }\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (isDisabled || isLoading) {\n setOpen(false)\n return\n }\n\n if (!nextOpen) {\n setInputValue('')\n }\n\n setOpen(nextOpen)\n }\n\n const handleTriggerContainerClick = (event: MouseEvent<HTMLDivElement>) => {\n if (isDisabled || isLoading) return\n\n const target = event.target as HTMLElement\n if (target === inputRef.current) return\n if (target.closest('[data-slot=combobox-trigger-button]')) return\n inputRef.current?.focus()\n setOpen(true)\n }\n\n return (\n <div className='w-full' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', labelClassName, isDisabled && 'text-text-secondary')} data-testid='spectral-combobox-label' htmlFor={comboboxId}>\n {label}\n </Label>\n )}\n\n <ComboboxPrimitive.Root\n autoHighlight\n disabled={isDisabled || isLoading}\n filter={null}\n inputValue={inputValue}\n itemToStringLabel={(option: ComboboxOption) => option.label}\n itemToStringValue={(option: ComboboxOption) => option.value}\n openOnInputClick\n onInputValueChange={(nextInputValue, eventDetails) => {\n if (eventDetails.reason !== 'input-change' && eventDetails.reason !== 'input-clear') return\n setInputValue(nextInputValue)\n }}\n name={name}\n onOpenChange={handleOpenChange}\n onValueChange={handleValueChange}\n open={open}\n required={required}\n value={selectedOption}\n >\n <ComboboxPrimitive.InputGroup\n render={\n <InputGroup\n data-slot='combobox-content'\n data-state={state}\n data-testid='spectral-combobox-trigger'\n className={cn(getTriggerClasses(open, state), 'ring-0! outline-none focus-within:outline-none has-[[data-slot=input-group-control]:focus-visible]:outline-0', isDisabled && 'pointer-events-none cursor-not-allowed', className)}\n onClick={handleTriggerContainerClick}\n style={getFormFieldCSSProperties() as CSSProperties}\n />\n }\n >\n <ComboboxPrimitive.Input\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={cn(\n 'min-w-0 text-base flex-1 truncate overflow-hidden border-0 bg-transparent whitespace-nowrap text-input-text outline-hidden outline-0 placeholder:opacity-100 focus-visible:ring-0 focus-visible:outline-none',\n showsSelectedValueAsPlaceholder ? 'placeholder:text-input-text!' : 'placeholder:text-input-text-placeholder!',\n )}\n data-slot='input-group-control'\n id={comboboxId}\n onFocus={() => {\n if (suppressNextInputFocusOpenRef.current) {\n suppressNextInputFocusOpenRef.current = false\n return\n }\n\n if (!isDisabled && !isLoading) {\n setOpen(true)\n }\n }}\n ref={inputRef}\n placeholder={selectedOption?.label ?? placeholder}\n role='combobox'\n {...ariaProps}\n />\n <InputGroupAddon align='inline-end' className='cursor-pointer'>\n <ComboboxPrimitive.Trigger\n aria-label='Toggle options'\n className='cursor-pointer'\n data-slot='combobox-trigger-button'\n onPointerDownCapture={() => {\n const inputElement = inputRef.current\n if (!inputElement) {\n clearFocusSuppression()\n return\n }\n\n const activeElement = inputElement.ownerDocument.activeElement\n suppressNextInputFocusOpenRef.current = activeElement !== inputElement\n }}\n onPointerUpCapture={clearFocusSuppression}\n onPointerCancelCapture={clearFocusSuppression}\n onTouchEndCapture={clearFocusSuppression}\n >\n {isLoading ? <LoaderIcon className='size-5 motion-safe:animate-spin' /> : <ChevronDownIcon className={cn('size-5 shrink-0 transition-transform duration-200', open && 'rotate-180')} />}\n </ComboboxPrimitive.Trigger>\n </InputGroupAddon>\n </ComboboxPrimitive.InputGroup>\n\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Positioner align='start' className='isolate z-50' sideOffset={4}>\n <ComboboxPrimitive.Popup\n className={cn(\n 'p-1 z-50 motion-safe:data-closed:animate-out motion-safe:data-open:animate-in',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-closed:fade-out-0 motion-safe:data-closed:zoom-out-95 motion-safe:data-open:fade-in-0 motion-safe:data-open:zoom-in-95',\n 'max-h-[min(var(--available-height),18rem)] motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n // Keep a single scroll container (the list) to avoid dual scrollbar styles.\n 'min-w-32 origin-(--transform-origin) overflow-hidden',\n )}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-combobox-content'\n style={dropdownWidthStyle}\n >\n {isLoading ? (\n <LoadingState message={loadingMessage} />\n ) : filteredOptions.length === 0 ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <ComboboxPrimitive.List className='max-h-[min(var(--available-height),18rem)] overflow-y-auto' id={listboxId}>\n {filteredOptions.map((option) => (\n <ComboboxPrimitive.Item\n className={cn(getOptionClasses(!!option.disabled, false, value === option.value), 'group/command-item relative flex w-full items-center data-highlighted:bg-input-bg--hover')}\n data-testid='spectral-combobox-item'\n disabled={option.disabled}\n key={option.value}\n value={option}\n >\n <span className='min-w-0 flex-1 truncate whitespace-nowrap'>{option.label}</span>\n <ComboboxPrimitive.ItemIndicator\n render={\n <span className='right-2 h-4 w-4 absolute flex items-center justify-center'>\n <CheckmarkIcon size={16} />\n </span>\n }\n />\n </ComboboxPrimitive.Item>\n ))}\n </ComboboxPrimitive.List>\n )}\n </ComboboxPrimitive.Popup>\n </ComboboxPrimitive.Positioner>\n </ComboboxPrimitive.Portal>\n </ComboboxPrimitive.Root>\n\n <ErrorMessage dataTestId='spectral-combobox-error-message' id={errorMessageId} message={isInvalid ? (errorMessage ?? null) : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'error'} />\n <WarningMessage dataTestId='spectral-combobox-warning-message' id={warningMessageId} message={state === 'warning' ? (warningMessage ?? null) : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'warning'} />\n </div>\n )\n}\nCombobox.displayName = 'Combobox'\n"],"mappings":"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"}
@@ -34,7 +34,9 @@ type ControlGroupSelectBaseProps = ComponentProps<typeof Select> & Pick<ControlG
34
34
  inputItemClassName?: string; /** When `captionLayout` is `inline`, overrides the input's accessible name (defaults to `inputPlaceholder` when set). */
35
35
  inputAriaLabel?: string;
36
36
  inputPlaceholder?: string;
37
- maxAmount?: number;
37
+ maxAmount?: number; /** Number of message lines to reserve (default: 1). */
38
+ messageReserveLines?: number; /** Whether to keep message space reserved when hidden (default: true). */
39
+ messageReserveSpace?: boolean;
38
40
  minAmount?: number;
39
41
  selectPlaceholder?: string; /** When `captionLayout` is `inline` and `selectPlaceholder` is empty, sets the select trigger's accessible name. */
40
42
  selectAriaLabel?: string; /** Class applied to the select `ControlGroupItem` segment for width/split composition. */
@@ -59,6 +61,8 @@ declare const ControlGroupSelect: ({
59
61
  inputPlaceholder,
60
62
  inputItemClassName,
61
63
  inputValue,
64
+ messageReserveLines,
65
+ messageReserveSpace,
62
66
  maxAmount,
63
67
  minAmount,
64
68
  onInputChange,
@@ -1 +1 @@
1
- {"version":3,"file":"ControlGroupSelect.d.ts","names":[],"sources":["../../src/components/ControlGroup/ControlGroupSelect.tsx"],"mappings":";;;;;;;;;UAYiB,gBAAA;EACf,QAAA;EACA,KAAA;EACA,KAAA;AAAA;;KAIU,+BAAA;AAAA,KAEP,mCAAA;EAEC,UAAA,EAAY,cAAA,QAAsB,KAAA;EAClC,aAAA,EAAe,cAAA,QAAsB,KAAA;AAAA;EAGrC,UAAA;EACA,aAAA;AAAA;AAAA,KAGD,2BAAA,GAA8B,cAAA,QAAsB,MAAA,IACvD,IAAA,CAAK,iBAAA;EACH,UAAA,WAduC;EAgBvC,SAAA;EACA,aAAA,GAAgB,+BAAA;EAChB,SAAA;EACA,UAAA;EACA,QAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA,uBAAmC,MAAA;EACnC,EAAA,WAlB+B;EAoB/B,kBAAA,WArBc;EAuBd,cAAA;EACA,gBAAA;EACA,SAAA;EACA,SAAA;EACA,iBAAA,WAtBE;EAwBF,eAAA,WAxBe;EA0Bf,mBAAA;EACA,aAAA,EAAe,gBAAA;EACf,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,IAAA;EACA,cAAA,uBAAqC,MAAA;AAAA;AAAA,KAG7B,uBAAA,GAA0B,2BAAA,GAA8B,mCAAA;AAAA,cAiBvD,kBAAA;EAAkB,UAAA;EAAA,SAAA;EAAA,aAAA;EAAA,SAAA;EAAA,UAAA;EAAA,QAAA;EAAA,YAAA;EAAA,EAAA;EAAA,aAAA;EAAA,cAAA;EAAA,gBAAA;EAAA,kBAAA;EAAA,UAAA;EAAA,SAAA;EAAA,SAAA;EAAA,aAAA;EAAA,WAAA;EAAA,eAAA;EAAA,mBAAA;EAAA,aAAA;EAAA,iBAAA;EAAA,KAAA;EAAA,IAAA;EAAA,cAAA;EAAA,GAAA;AAAA,GA0B5B,uBAAA,KAAuB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"ControlGroupSelect.d.ts","names":[],"sources":["../../src/components/ControlGroup/ControlGroupSelect.tsx"],"mappings":";;;;;;;;;UAYiB,gBAAA;EACf,QAAA;EACA,KAAA;EACA,KAAA;AAAA;;KAIU,+BAAA;AAAA,KAEP,mCAAA;EAEC,UAAA,EAAY,cAAA,QAAsB,KAAA;EAClC,aAAA,EAAe,cAAA,QAAsB,KAAA;AAAA;EAGrC,UAAA;EACA,aAAA;AAAA;AAAA,KAGD,2BAAA,GAA8B,cAAA,QAAsB,MAAA,IACvD,IAAA,CAAK,iBAAA;EACH,UAAA,WAduC;EAgBvC,SAAA;EACA,aAAA,GAAgB,+BAAA;EAChB,SAAA;EACA,UAAA;EACA,QAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA,uBAAmC,MAAA;EACnC,EAAA,WAlB+B;EAoB/B,kBAAA,WArBc;EAuBd,cAAA;EACA,gBAAA;EACA,SAAA,WAxBuC;EA0BvC,mBAAA,WAtBE;EAwBF,mBAAA;EACA,SAAA;EACA,iBAAA,WAvB4B;EAyB5B,eAAA,WAzBqD;EA2BrD,mBAAA;EACA,aAAA,EAAe,gBAAA;EACf,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,IAAA;EACA,cAAA,uBAAqC,MAAA;AAAA;AAAA,KAG7B,uBAAA,GAA0B,2BAAA,GAA8B,mCAAA;AAAA,cAiBvD,kBAAA;EAAkB,UAAA;EAAA,SAAA;EAAA,aAAA;EAAA,SAAA;EAAA,UAAA;EAAA,QAAA;EAAA,YAAA;EAAA,EAAA;EAAA,aAAA;EAAA,cAAA;EAAA,gBAAA;EAAA,kBAAA;EAAA,UAAA;EAAA,mBAAA;EAAA,mBAAA;EAAA,SAAA;EAAA,SAAA;EAAA,aAAA;EAAA,WAAA;EAAA,eAAA;EAAA,mBAAA;EAAA,aAAA;EAAA,iBAAA;EAAA,KAAA;EAAA,IAAA;EAAA,cAAA;EAAA,GAAA;AAAA,GA4B5B,uBAAA,KAAuB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -23,7 +23,7 @@ const getReadableLabel = (label, fallback) => {
23
23
  const normalizedLabel = label?.trim();
24
24
  return normalizedLabel && normalizedLabel.length > 0 ? normalizedLabel : fallback;
25
25
  };
26
- const ControlGroupSelect = ({ amountStep, ariaLabel, captionLayout = "inline", className, dataTestId = "spectral-control-group-select", disabled, errorMessage, id, dropdownWidth = "trigger", inputAriaLabel, inputPlaceholder, inputItemClassName, inputValue, maxAmount = 1e6, minAmount = 0, onInputChange, orientation = "horizontal", selectAriaLabel, selectItemClassName, selectOptions, selectPlaceholder, state = "default", type = "number", warningMessage, ...selectProps }) => {
26
+ const ControlGroupSelect = ({ amountStep, ariaLabel, captionLayout = "inline", className, dataTestId = "spectral-control-group-select", disabled, errorMessage, id, dropdownWidth = "trigger", inputAriaLabel, inputPlaceholder, inputItemClassName, inputValue, messageReserveLines = 1, messageReserveSpace = true, maxAmount = 1e6, minAmount = 0, onInputChange, orientation = "horizontal", selectAriaLabel, selectItemClassName, selectOptions, selectPlaceholder, state = "default", type = "number", warningMessage, ...selectProps }) => {
27
27
  const baseId = useId();
28
28
  const idPrefix = id ?? baseId;
29
29
  const inputId = `${idPrefix}-amount`;
@@ -49,6 +49,8 @@ const ControlGroupSelect = ({ amountStep, ariaLabel, captionLayout = "inline", c
49
49
  disabled,
50
50
  errorMessage,
51
51
  id: idPrefix,
52
+ messageReserveLines,
53
+ messageReserveSpace,
52
54
  orientation,
53
55
  state,
54
56
  warningMessage,
@@ -1 +1 @@
1
- {"version":3,"file":"ControlGroupSelect.js","names":[],"sources":["../../src/components/ControlGroup/ControlGroupSelect.tsx"],"sourcesContent":["import { Label } from '@components/Label/Label'\nimport { Input } from '@primitives/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@primitives/select'\nimport { getStateClasses, type DropdownWidth, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useId, type ComponentProps } from 'react'\nimport { ControlGroup, ControlGroupItem, useControlGroup, type ControlGroupProps } from './ControlGroup'\n\nconst numberInputNoSpinner = '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none'\nconst defaultInputLabel = 'Amount'\nconst defaultSelectLabel = 'Select an option'\n\nexport interface SelectOptionType {\n disabled?: boolean\n label: string\n value: string\n}\n\n/** `inline`: `inputPlaceholder` / `selectPlaceholder` show inside the fields. `above`: same strings render as labels above each control (no inner placeholders). */\nexport type ControlGroupSelectCaptionLayout = 'above' | 'inline'\n\ntype ControlGroupSelectInputControlProps =\n | {\n inputValue: ComponentProps<typeof Input>['value']\n onInputChange: ComponentProps<typeof Input>['onChange']\n }\n | {\n inputValue?: undefined\n onInputChange?: undefined\n }\n\ntype ControlGroupSelectBaseProps = ComponentProps<typeof Select> &\n Pick<ControlGroupProps, 'orientation'> & {\n amountStep?: number\n /** Accessible name for the group wrapper (use when there is no visible group heading). */\n ariaLabel?: string\n captionLayout?: ControlGroupSelectCaptionLayout\n className?: string\n dataTestId?: string\n disabled?: boolean\n dropdownWidth?: DropdownWidth\n errorMessage?: string | string[] | Record<string, unknown> | null\n id?: string\n /** Class applied to the input `ControlGroupItem` segment for width/split composition. */\n inputItemClassName?: string\n /** When `captionLayout` is `inline`, overrides the input's accessible name (defaults to `inputPlaceholder` when set). */\n inputAriaLabel?: string\n inputPlaceholder?: string\n maxAmount?: number\n minAmount?: number\n selectPlaceholder?: string\n /** When `captionLayout` is `inline` and `selectPlaceholder` is empty, sets the select trigger's accessible name. */\n selectAriaLabel?: string\n /** Class applied to the select `ControlGroupItem` segment for width/split composition. */\n selectItemClassName?: string\n selectOptions: SelectOptionType[]\n state?: Exclude<FormFieldState, 'disabled'>\n type?: 'number' | 'text'\n warningMessage?: string | string[] | Record<string, unknown> | null\n }\n\nexport type ControlGroupSelectProps = ControlGroupSelectBaseProps & ControlGroupSelectInputControlProps\n\nconst fieldStackClass = 'flex w-full min-w-0 flex-col gap-1.5'\nconst groupedFieldBaseClasses =\n 'h-12! min-h-12! border-2! border-input-border bg-input-bg text-base text-input-text transition duration-200 placeholder:text-input-text-placeholder hover:border-input-border--hover focus-visible:border-input-border--focus focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus disabled:pointer-events-none disabled:border-input-border--disabled disabled:bg-input-bg--disabled disabled:text-input-text--disabled'\n\nconst getFieldMinWidth = (caption?: string, extraCharacters: number = 0): string | undefined => {\n const normalizedCaption = caption?.trim()\n if (!normalizedCaption) return undefined\n return `${Math.max(normalizedCaption.length + extraCharacters, 8)}ch`\n}\n\nconst getReadableLabel = (label: string | undefined, fallback: string): string => {\n const normalizedLabel = label?.trim()\n return normalizedLabel && normalizedLabel.length > 0 ? normalizedLabel : fallback\n}\n\nexport const ControlGroupSelect = ({\n amountStep,\n ariaLabel,\n captionLayout = 'inline',\n className,\n dataTestId = 'spectral-control-group-select',\n disabled,\n errorMessage,\n id,\n dropdownWidth = 'trigger',\n inputAriaLabel,\n inputPlaceholder,\n inputItemClassName,\n inputValue,\n maxAmount = 1000000,\n minAmount = 0,\n onInputChange,\n orientation = 'horizontal',\n selectAriaLabel,\n selectItemClassName,\n selectOptions,\n selectPlaceholder,\n state = 'default',\n type = 'number',\n warningMessage,\n ...selectProps\n}: ControlGroupSelectProps) => {\n const baseId = useId()\n const idPrefix = id ?? baseId\n const inputId = `${idPrefix}-amount`\n const selectTriggerId = `${idPrefix}-select`\n const inputLabelId = `${inputId}-label`\n const selectLabelId = `${selectTriggerId}-label`\n\n const useAboveLabels = captionLayout === 'above'\n const inputCaption = useAboveLabels ? undefined : inputPlaceholder\n const selectCaption = useAboveLabels ? undefined : selectPlaceholder\n const inputLabelText = getReadableLabel(inputPlaceholder, defaultInputLabel)\n const selectLabelText = getReadableLabel(selectPlaceholder, defaultSelectLabel)\n\n const inputAccessibleName = inputAriaLabel ?? (useAboveLabels ? undefined : inputLabelText)\n const selectTriggerAriaLabel = selectAriaLabel ?? (useAboveLabels ? undefined : selectLabelText)\n const inputMinWidth = getFieldMinWidth(inputPlaceholder, 3)\n const selectTriggerMinWidth = getFieldMinWidth(selectPlaceholder, 5)\n const groupAriaLabel = ariaLabel ?? `${inputLabelText} and ${selectLabelText}`\n\n const isDisabled = !!disabled\n const isInputControlled = inputValue !== undefined && onInputChange !== undefined\n\n return (\n <ControlGroup aria-label={groupAriaLabel} className={className} data-testid={dataTestId} disabled={disabled} errorMessage={errorMessage} id={idPrefix} orientation={orientation} state={state} warningMessage={warningMessage}>\n <ControlGroupSelectInner\n amountStep={amountStep}\n dataTestId={dataTestId}\n inputAccessibleName={inputAccessibleName}\n inputCaption={inputCaption}\n inputId={inputId}\n inputLabelId={inputLabelId}\n inputLabelText={inputLabelText}\n inputItemClassName={inputItemClassName}\n inputMinWidth={inputMinWidth}\n inputValue={isInputControlled ? inputValue : undefined}\n isDisabled={isDisabled}\n maxAmount={maxAmount}\n minAmount={minAmount}\n onInputChange={isInputControlled ? onInputChange : undefined}\n dropdownWidth={dropdownWidth}\n selectCaption={selectCaption}\n selectOptions={selectOptions}\n selectLabelId={selectLabelId}\n selectLabelText={selectLabelText}\n selectItemClassName={selectItemClassName}\n selectProps={selectProps}\n selectTriggerAriaLabel={selectTriggerAriaLabel}\n selectTriggerId={selectTriggerId}\n selectTriggerMinWidth={selectTriggerMinWidth}\n type={type}\n useAboveLabels={useAboveLabels}\n />\n </ControlGroup>\n )\n}\n\ninterface ControlGroupSelectInnerProps {\n amountStep?: number\n dataTestId: string\n dropdownWidth: DropdownWidth\n inputAccessibleName?: string\n inputCaption?: string\n inputId: string\n inputLabelId: string\n inputLabelText: string\n inputItemClassName?: string\n inputMinWidth?: string\n inputValue?: ComponentProps<typeof Input>['value']\n isDisabled: boolean\n maxAmount: number\n minAmount: number\n onInputChange?: ComponentProps<typeof Input>['onChange']\n selectCaption?: string\n selectLabelId: string\n selectLabelText: string\n selectItemClassName?: string\n selectOptions: SelectOptionType[]\n selectProps: Omit<ComponentProps<typeof Select>, 'children'>\n selectTriggerAriaLabel?: string\n selectTriggerId: string\n selectTriggerMinWidth?: string\n type: 'number' | 'text'\n useAboveLabels: boolean\n}\n\nconst ControlGroupSelectInner = ({\n amountStep,\n dataTestId,\n dropdownWidth,\n inputAccessibleName,\n inputCaption,\n inputId,\n inputLabelId,\n inputLabelText,\n inputItemClassName,\n inputMinWidth,\n inputValue,\n isDisabled,\n maxAmount,\n minAmount,\n onInputChange,\n selectCaption,\n selectLabelId,\n selectLabelText,\n selectItemClassName,\n selectOptions,\n selectProps,\n selectTriggerAriaLabel,\n selectTriggerId,\n selectTriggerMinWidth,\n type,\n useAboveLabels,\n}: ControlGroupSelectInnerProps) => {\n const { messageId, orientation, state } = useControlGroup()\n const isSelectOpenControlled = selectProps.open !== undefined\n const handleSelectOpenChange: NonNullable<ComponentProps<typeof Select>['onOpenChange']> = (nextOpen) => {\n selectProps.onOpenChange?.(nextOpen)\n }\n\n const inputRadiusClasses = orientation === 'horizontal' ? 'rounded-s-md rounded-e-none' : 'rounded-ss-md rounded-se-md rounded-es-none rounded-ee-none'\n const selectRadiusClasses = orientation === 'horizontal' ? 'rounded-s-none rounded-e-md' : 'rounded-ss-none rounded-se-none rounded-es-md rounded-ee-md'\n const firstItemAboveLabelClasses = orientation === 'horizontal' ? 'me-0' : 'mbe-0'\n const secondItemAboveLabelClasses = orientation === 'horizontal' ? 'me-0 [&>*:last-child]:-ms-0.5' : 'mbe-0 [&>*:last-child]:-mt-0.5'\n\n const inputElement = (\n <Input\n aria-describedby={messageId}\n aria-label={inputAccessibleName}\n aria-labelledby={useAboveLabels ? inputLabelId : undefined}\n className={cn(groupedFieldBaseClasses, inputRadiusClasses, getStateClasses(state), type === 'number' && numberInputNoSpinner, type === 'number' && 'tabular-nums')}\n data-testid={`${dataTestId}-input`}\n disabled={isDisabled}\n id={useAboveLabels ? inputId : undefined}\n type={type === 'number' ? 'number' : 'text'}\n placeholder={inputCaption}\n style={inputMinWidth ? { minWidth: inputMinWidth } : undefined}\n min={minAmount ?? 0}\n max={maxAmount ?? 1000000}\n {...(inputValue !== undefined && onInputChange !== undefined ? { value: inputValue, onChange: onInputChange } : {})}\n step={amountStep ?? 1}\n />\n )\n\n const selectTriggerElement = (\n <SelectTrigger\n aria-describedby={messageId}\n aria-label={selectTriggerAriaLabel}\n aria-labelledby={useAboveLabels ? selectLabelId : undefined}\n className={cn(\n 'text-input-text data-placeholder:text-input-text-placeholder!',\n selectRadiusClasses,\n 'px-4 w-full justify-between focus-visible:border-input-border--focus focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus',\n getStateClasses(state),\n )}\n data-testid={`${dataTestId}-select-trigger`}\n disabled={isDisabled}\n id={useAboveLabels ? selectTriggerId : undefined}\n style={selectTriggerMinWidth ? { minWidth: selectTriggerMinWidth } : undefined}\n >\n <SelectValue className='min-w-0 block max-w-full truncate text-left whitespace-nowrap text-input-text! data-placeholder:text-input-text-placeholder!' placeholder={selectCaption} />\n </SelectTrigger>\n )\n\n return (\n <>\n <ControlGroupItem className={cn(useAboveLabels ? firstItemAboveLabelClasses : undefined, inputItemClassName)}>\n {useAboveLabels ? (\n <div className={fieldStackClass}>\n <Label className='text-text-primary' htmlFor={inputId} id={inputLabelId}>\n {inputLabelText}\n </Label>\n {inputElement}\n </div>\n ) : (\n inputElement\n )}\n </ControlGroupItem>\n <Select {...selectProps} data-testid={`${dataTestId}-select-root`} disabled={isDisabled} {...(isSelectOpenControlled ? { open: selectProps.open } : {})} onOpenChange={handleSelectOpenChange}>\n <ControlGroupItem className={cn(useAboveLabels ? secondItemAboveLabelClasses : undefined, selectItemClassName)}>\n {useAboveLabels ? (\n <div className={fieldStackClass}>\n <Label className='text-text-primary' htmlFor={selectTriggerId} id={selectLabelId}>\n {selectLabelText}\n </Label>\n {selectTriggerElement}\n </div>\n ) : (\n selectTriggerElement\n )}\n </ControlGroupItem>\n <SelectContent data-testid={`${dataTestId}-select-content`} dropdownWidth={dropdownWidth}>\n {selectOptions.map((option) => (\n <SelectItem disabled={option.disabled} key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ControlGroupSelect.js","names":[],"sources":["../../src/components/ControlGroup/ControlGroupSelect.tsx"],"sourcesContent":["import { Label } from '@components/Label/Label'\nimport { Input } from '@primitives/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@primitives/select'\nimport { getStateClasses, type DropdownWidth, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useId, type ComponentProps } from 'react'\nimport { ControlGroup, ControlGroupItem, useControlGroup, type ControlGroupProps } from './ControlGroup'\n\nconst numberInputNoSpinner = '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none'\nconst defaultInputLabel = 'Amount'\nconst defaultSelectLabel = 'Select an option'\n\nexport interface SelectOptionType {\n disabled?: boolean\n label: string\n value: string\n}\n\n/** `inline`: `inputPlaceholder` / `selectPlaceholder` show inside the fields. `above`: same strings render as labels above each control (no inner placeholders). */\nexport type ControlGroupSelectCaptionLayout = 'above' | 'inline'\n\ntype ControlGroupSelectInputControlProps =\n | {\n inputValue: ComponentProps<typeof Input>['value']\n onInputChange: ComponentProps<typeof Input>['onChange']\n }\n | {\n inputValue?: undefined\n onInputChange?: undefined\n }\n\ntype ControlGroupSelectBaseProps = ComponentProps<typeof Select> &\n Pick<ControlGroupProps, 'orientation'> & {\n amountStep?: number\n /** Accessible name for the group wrapper (use when there is no visible group heading). */\n ariaLabel?: string\n captionLayout?: ControlGroupSelectCaptionLayout\n className?: string\n dataTestId?: string\n disabled?: boolean\n dropdownWidth?: DropdownWidth\n errorMessage?: string | string[] | Record<string, unknown> | null\n id?: string\n /** Class applied to the input `ControlGroupItem` segment for width/split composition. */\n inputItemClassName?: string\n /** When `captionLayout` is `inline`, overrides the input's accessible name (defaults to `inputPlaceholder` when set). */\n inputAriaLabel?: string\n inputPlaceholder?: string\n maxAmount?: number\n /** Number of message lines to reserve (default: 1). */\n messageReserveLines?: number\n /** Whether to keep message space reserved when hidden (default: true). */\n messageReserveSpace?: boolean\n minAmount?: number\n selectPlaceholder?: string\n /** When `captionLayout` is `inline` and `selectPlaceholder` is empty, sets the select trigger's accessible name. */\n selectAriaLabel?: string\n /** Class applied to the select `ControlGroupItem` segment for width/split composition. */\n selectItemClassName?: string\n selectOptions: SelectOptionType[]\n state?: Exclude<FormFieldState, 'disabled'>\n type?: 'number' | 'text'\n warningMessage?: string | string[] | Record<string, unknown> | null\n }\n\nexport type ControlGroupSelectProps = ControlGroupSelectBaseProps & ControlGroupSelectInputControlProps\n\nconst fieldStackClass = 'flex w-full min-w-0 flex-col gap-1.5'\nconst groupedFieldBaseClasses =\n 'h-12! min-h-12! border-2! border-input-border bg-input-bg text-base text-input-text transition duration-200 placeholder:text-input-text-placeholder hover:border-input-border--hover focus-visible:border-input-border--focus focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus disabled:pointer-events-none disabled:border-input-border--disabled disabled:bg-input-bg--disabled disabled:text-input-text--disabled'\n\nconst getFieldMinWidth = (caption?: string, extraCharacters: number = 0): string | undefined => {\n const normalizedCaption = caption?.trim()\n if (!normalizedCaption) return undefined\n return `${Math.max(normalizedCaption.length + extraCharacters, 8)}ch`\n}\n\nconst getReadableLabel = (label: string | undefined, fallback: string): string => {\n const normalizedLabel = label?.trim()\n return normalizedLabel && normalizedLabel.length > 0 ? normalizedLabel : fallback\n}\n\nexport const ControlGroupSelect = ({\n amountStep,\n ariaLabel,\n captionLayout = 'inline',\n className,\n dataTestId = 'spectral-control-group-select',\n disabled,\n errorMessage,\n id,\n dropdownWidth = 'trigger',\n inputAriaLabel,\n inputPlaceholder,\n inputItemClassName,\n inputValue,\n messageReserveLines = 1,\n messageReserveSpace = true,\n maxAmount = 1000000,\n minAmount = 0,\n onInputChange,\n orientation = 'horizontal',\n selectAriaLabel,\n selectItemClassName,\n selectOptions,\n selectPlaceholder,\n state = 'default',\n type = 'number',\n warningMessage,\n ...selectProps\n}: ControlGroupSelectProps) => {\n const baseId = useId()\n const idPrefix = id ?? baseId\n const inputId = `${idPrefix}-amount`\n const selectTriggerId = `${idPrefix}-select`\n const inputLabelId = `${inputId}-label`\n const selectLabelId = `${selectTriggerId}-label`\n\n const useAboveLabels = captionLayout === 'above'\n const inputCaption = useAboveLabels ? undefined : inputPlaceholder\n const selectCaption = useAboveLabels ? undefined : selectPlaceholder\n const inputLabelText = getReadableLabel(inputPlaceholder, defaultInputLabel)\n const selectLabelText = getReadableLabel(selectPlaceholder, defaultSelectLabel)\n\n const inputAccessibleName = inputAriaLabel ?? (useAboveLabels ? undefined : inputLabelText)\n const selectTriggerAriaLabel = selectAriaLabel ?? (useAboveLabels ? undefined : selectLabelText)\n const inputMinWidth = getFieldMinWidth(inputPlaceholder, 3)\n const selectTriggerMinWidth = getFieldMinWidth(selectPlaceholder, 5)\n const groupAriaLabel = ariaLabel ?? `${inputLabelText} and ${selectLabelText}`\n\n const isDisabled = !!disabled\n const isInputControlled = inputValue !== undefined && onInputChange !== undefined\n\n return (\n <ControlGroup\n aria-label={groupAriaLabel}\n className={className}\n data-testid={dataTestId}\n disabled={disabled}\n errorMessage={errorMessage}\n id={idPrefix}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n orientation={orientation}\n state={state}\n warningMessage={warningMessage}\n >\n <ControlGroupSelectInner\n amountStep={amountStep}\n dataTestId={dataTestId}\n inputAccessibleName={inputAccessibleName}\n inputCaption={inputCaption}\n inputId={inputId}\n inputLabelId={inputLabelId}\n inputLabelText={inputLabelText}\n inputItemClassName={inputItemClassName}\n inputMinWidth={inputMinWidth}\n inputValue={isInputControlled ? inputValue : undefined}\n isDisabled={isDisabled}\n maxAmount={maxAmount}\n minAmount={minAmount}\n onInputChange={isInputControlled ? onInputChange : undefined}\n dropdownWidth={dropdownWidth}\n selectCaption={selectCaption}\n selectOptions={selectOptions}\n selectLabelId={selectLabelId}\n selectLabelText={selectLabelText}\n selectItemClassName={selectItemClassName}\n selectProps={selectProps}\n selectTriggerAriaLabel={selectTriggerAriaLabel}\n selectTriggerId={selectTriggerId}\n selectTriggerMinWidth={selectTriggerMinWidth}\n type={type}\n useAboveLabels={useAboveLabels}\n />\n </ControlGroup>\n )\n}\n\ninterface ControlGroupSelectInnerProps {\n amountStep?: number\n dataTestId: string\n dropdownWidth: DropdownWidth\n inputAccessibleName?: string\n inputCaption?: string\n inputId: string\n inputLabelId: string\n inputLabelText: string\n inputItemClassName?: string\n inputMinWidth?: string\n inputValue?: ComponentProps<typeof Input>['value']\n isDisabled: boolean\n maxAmount: number\n minAmount: number\n onInputChange?: ComponentProps<typeof Input>['onChange']\n selectCaption?: string\n selectLabelId: string\n selectLabelText: string\n selectItemClassName?: string\n selectOptions: SelectOptionType[]\n selectProps: Omit<ComponentProps<typeof Select>, 'children'>\n selectTriggerAriaLabel?: string\n selectTriggerId: string\n selectTriggerMinWidth?: string\n type: 'number' | 'text'\n useAboveLabels: boolean\n}\n\nconst ControlGroupSelectInner = ({\n amountStep,\n dataTestId,\n dropdownWidth,\n inputAccessibleName,\n inputCaption,\n inputId,\n inputLabelId,\n inputLabelText,\n inputItemClassName,\n inputMinWidth,\n inputValue,\n isDisabled,\n maxAmount,\n minAmount,\n onInputChange,\n selectCaption,\n selectLabelId,\n selectLabelText,\n selectItemClassName,\n selectOptions,\n selectProps,\n selectTriggerAriaLabel,\n selectTriggerId,\n selectTriggerMinWidth,\n type,\n useAboveLabels,\n}: ControlGroupSelectInnerProps) => {\n const { messageId, orientation, state } = useControlGroup()\n const isSelectOpenControlled = selectProps.open !== undefined\n const handleSelectOpenChange: NonNullable<ComponentProps<typeof Select>['onOpenChange']> = (nextOpen) => {\n selectProps.onOpenChange?.(nextOpen)\n }\n\n const inputRadiusClasses = orientation === 'horizontal' ? 'rounded-s-md rounded-e-none' : 'rounded-ss-md rounded-se-md rounded-es-none rounded-ee-none'\n const selectRadiusClasses = orientation === 'horizontal' ? 'rounded-s-none rounded-e-md' : 'rounded-ss-none rounded-se-none rounded-es-md rounded-ee-md'\n const firstItemAboveLabelClasses = orientation === 'horizontal' ? 'me-0' : 'mbe-0'\n const secondItemAboveLabelClasses = orientation === 'horizontal' ? 'me-0 [&>*:last-child]:-ms-0.5' : 'mbe-0 [&>*:last-child]:-mt-0.5'\n\n const inputElement = (\n <Input\n aria-describedby={messageId}\n aria-label={inputAccessibleName}\n aria-labelledby={useAboveLabels ? inputLabelId : undefined}\n className={cn(groupedFieldBaseClasses, inputRadiusClasses, getStateClasses(state), type === 'number' && numberInputNoSpinner, type === 'number' && 'tabular-nums')}\n data-testid={`${dataTestId}-input`}\n disabled={isDisabled}\n id={useAboveLabels ? inputId : undefined}\n type={type === 'number' ? 'number' : 'text'}\n placeholder={inputCaption}\n style={inputMinWidth ? { minWidth: inputMinWidth } : undefined}\n min={minAmount ?? 0}\n max={maxAmount ?? 1000000}\n {...(inputValue !== undefined && onInputChange !== undefined ? { value: inputValue, onChange: onInputChange } : {})}\n step={amountStep ?? 1}\n />\n )\n\n const selectTriggerElement = (\n <SelectTrigger\n aria-describedby={messageId}\n aria-label={selectTriggerAriaLabel}\n aria-labelledby={useAboveLabels ? selectLabelId : undefined}\n className={cn(\n 'text-input-text data-placeholder:text-input-text-placeholder!',\n selectRadiusClasses,\n 'px-4 w-full justify-between focus-visible:border-input-border--focus focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus',\n getStateClasses(state),\n )}\n data-testid={`${dataTestId}-select-trigger`}\n disabled={isDisabled}\n id={useAboveLabels ? selectTriggerId : undefined}\n style={selectTriggerMinWidth ? { minWidth: selectTriggerMinWidth } : undefined}\n >\n <SelectValue className='min-w-0 block max-w-full truncate text-left whitespace-nowrap text-input-text! data-placeholder:text-input-text-placeholder!' placeholder={selectCaption} />\n </SelectTrigger>\n )\n\n return (\n <>\n <ControlGroupItem className={cn(useAboveLabels ? firstItemAboveLabelClasses : undefined, inputItemClassName)}>\n {useAboveLabels ? (\n <div className={fieldStackClass}>\n <Label className='text-text-primary' htmlFor={inputId} id={inputLabelId}>\n {inputLabelText}\n </Label>\n {inputElement}\n </div>\n ) : (\n inputElement\n )}\n </ControlGroupItem>\n <Select {...selectProps} data-testid={`${dataTestId}-select-root`} disabled={isDisabled} {...(isSelectOpenControlled ? { open: selectProps.open } : {})} onOpenChange={handleSelectOpenChange}>\n <ControlGroupItem className={cn(useAboveLabels ? secondItemAboveLabelClasses : undefined, selectItemClassName)}>\n {useAboveLabels ? (\n <div className={fieldStackClass}>\n <Label className='text-text-primary' htmlFor={selectTriggerId} id={selectLabelId}>\n {selectLabelText}\n </Label>\n {selectTriggerElement}\n </div>\n ) : (\n selectTriggerElement\n )}\n </ControlGroupItem>\n <SelectContent data-testid={`${dataTestId}-select-content`} dropdownWidth={dropdownWidth}>\n {selectOptions.map((option) => (\n <SelectItem disabled={option.disabled} key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </>\n )\n}\n"],"mappings":"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"}
@@ -18,6 +18,8 @@ interface ControlGroupProps extends ComponentProps<'div'> {
18
18
  disabled?: boolean;
19
19
  errorMessage?: string | string[] | Record<string, unknown> | null;
20
20
  id?: string;
21
+ messageReserveLines?: number;
22
+ messageReserveSpace?: boolean;
21
23
  name?: string;
22
24
  orientation?: 'horizontal' | 'vertical';
23
25
  state?: FormFieldState;
@@ -28,6 +30,8 @@ declare const ControlGroup: ({
28
30
  disabled,
29
31
  errorMessage,
30
32
  id,
33
+ messageReserveLines,
34
+ messageReserveSpace,
31
35
  name,
32
36
  orientation,
33
37
  state,
@@ -1 +1 @@
1
- {"version":3,"file":"ControlGroup.d.ts","names":[],"sources":["../src/components/ControlGroup/ControlGroup.tsx"],"mappings":";;;;;;;UAKU,wBAAA;EACR,SAAA;EACA,UAAA;EACA,SAAA;EACA,SAAA;EACA,WAAA;EACA,KAAA,EAAO,cAAA;AAAA;AAAA,cAKI,eAAA,QAAe,wBAAA;AAAA,UAQX,iBAAA,SAA0B,cAAA;EACzC,QAAA;EACA,YAAA,uBAAmC,MAAA;EACnC,EAAA;EACA,IAAA;EACA,WAAA;EACA,KAAA,GAAQ,cAAA;EACR,cAAA,uBAAqC,MAAA;AAAA;AAAA,cAG1B,YAAA;EAAY,SAAA;EAAA,QAAA;EAAA,YAAA;EAAA,EAAA;EAAA,IAAA;EAAA,WAAA;EAAA,KAAA;EAAA,cAAA;EAAA,GAAA;AAAA,GAA8H,iBAAA,KAAiB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cA2B3J,gBAAA;EAAgB,SAAA;EAAA,GAAA;AAAA,GAA6B,cAAA,QAAsB,IAAA,MAAK,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"ControlGroup.d.ts","names":[],"sources":["../src/components/ControlGroup/ControlGroup.tsx"],"mappings":";;;;;;;UAKU,wBAAA;EACR,SAAA;EACA,UAAA;EACA,SAAA;EACA,SAAA;EACA,WAAA;EACA,KAAA,EAAO,cAAA;AAAA;AAAA,cAKI,eAAA,QAAe,wBAAA;AAAA,UAQX,iBAAA,SAA0B,cAAA;EACzC,QAAA;EACA,YAAA,uBAAmC,MAAA;EACnC,EAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,WAAA;EACA,KAAA,GAAQ,cAAA;EACR,cAAA,uBAAqC,MAAA;AAAA;AAAA,cAG1B,YAAA;EAAY,SAAA;EAAA,QAAA;EAAA,YAAA;EAAA,EAAA;EAAA,mBAAA;EAAA,mBAAA;EAAA,IAAA;EAAA,WAAA;EAAA,KAAA;EAAA,cAAA;EAAA,GAAA;AAAA,GAAmL,iBAAA,KAAiB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cA2BhN,gBAAA;EAAgB,SAAA;EAAA,GAAA;AAAA,GAA6B,cAAA,QAAsB,IAAA,MAAK,oBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -13,7 +13,7 @@ const useControlGroup = () => {
13
13
  if (context === null) throw new Error("useControlGroup must be used within a ControlGroup");
14
14
  return context;
15
15
  };
16
- const ControlGroup = ({ className, disabled, errorMessage, id, name, orientation = "horizontal", state = "default", warningMessage, ...props }) => {
16
+ const ControlGroup = ({ className, disabled, errorMessage, id, messageReserveLines = 1, messageReserveSpace = true, name, orientation = "horizontal", state = "default", warningMessage, ...props }) => {
17
17
  const groupId = useFormFieldId(id, name);
18
18
  const errorMessageId = getErrorMessageId(groupId);
19
19
  const warningMessageId = `${groupId}-warning`;
@@ -45,12 +45,16 @@ const ControlGroup = ({ className, disabled, errorMessage, id, name, orientation
45
45
  /* @__PURE__ */ jsx(ErrorMessage, {
46
46
  dataTestId: "spectral-control-group-error-message",
47
47
  id: errorMessageId,
48
- message: isInvalid ? errorMessage : null
48
+ message: isInvalid ? errorMessage : null,
49
+ messageReserveLines,
50
+ messageReserveSpace: messageReserveSpace && state === "error"
49
51
  }),
50
52
  /* @__PURE__ */ jsx(WarningMessage, {
51
53
  dataTestId: "spectral-control-group-warning-message",
52
54
  id: warningMessageId,
53
- message: state === "warning" ? warningMessage : null
55
+ message: state === "warning" ? warningMessage : null,
56
+ messageReserveLines,
57
+ messageReserveSpace: messageReserveSpace && state === "warning"
54
58
  })
55
59
  ]
56
60
  })
@@ -1 +1 @@
1
- {"version":3,"file":"ControlGroup.js","names":[],"sources":["../src/components/ControlGroup/ControlGroup.tsx"],"sourcesContent":["import { Slot } from '@primitives/slot'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId, useFormFieldState, WarningMessage, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { createContext, useContext, type ComponentProps } from 'react'\n\ninterface ControlGroupContextValue {\n messageId?: string\n isDisabled: boolean\n isInvalid: boolean\n isLoading: boolean\n orientation: 'horizontal' | 'vertical'\n state: FormFieldState\n}\n\nconst ControlGroupContext = createContext<ControlGroupContextValue | null>(null)\n\nexport const useControlGroup = () => {\n const context = useContext(ControlGroupContext)\n if (context === null) {\n throw new Error('useControlGroup must be used within a ControlGroup')\n }\n return context\n}\n\nexport interface ControlGroupProps extends ComponentProps<'div'> {\n disabled?: boolean\n errorMessage?: string | string[] | Record<string, unknown> | null\n id?: string\n name?: string\n orientation?: 'horizontal' | 'vertical'\n state?: FormFieldState\n warningMessage?: string | string[] | Record<string, unknown> | null\n}\n\nexport const ControlGroup = ({ className, disabled, errorMessage, id, name, orientation = 'horizontal', state = 'default', warningMessage, ...props }: ControlGroupProps) => {\n const groupId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(groupId)\n const warningMessageId = `${groupId}-warning`\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const messageId = state === 'error' && errorMessage ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n\n return (\n <ControlGroupContext.Provider value={{ messageId, isDisabled, isInvalid, isLoading, orientation, state }}>\n <div data-slot='control-group-field' className='space-y-1.5 w-full'>\n <div\n data-slot='control-group'\n data-orientation={orientation}\n data-state={state}\n id={groupId}\n role='group'\n aria-describedby={messageId}\n className={cn('group flex w-full', orientation === 'vertical' && 'flex-col', isDisabled && 'pointer-events-none opacity-50', className)}\n {...props}\n />\n <ErrorMessage dataTestId='spectral-control-group-error-message' id={errorMessageId} message={isInvalid ? errorMessage : null} />\n <WarningMessage dataTestId='spectral-control-group-warning-message' id={warningMessageId} message={state === 'warning' ? warningMessage : null} />\n </div>\n </ControlGroupContext.Provider>\n )\n}\n\nexport const ControlGroupItem = ({ className, ...props }: ComponentProps<typeof Slot>) => {\n const { orientation } = useControlGroup()\n\n return (\n <Slot\n data-slot='control-group-item'\n className={cn(\n 'rounded-none focus-within:z-10 hover:z-10',\n orientation === 'horizontal' && 'first:rounded-s-md last:me-0 last:rounded-e-md -me-0.5 h-auto',\n orientation === 'vertical' && 'first:rounded-ss-md first:rounded-se-md last:rounded-ee-md last:rounded-es-md last:mbe-0 -mbe-0.5 w-auto',\n className,\n )}\n {...props}\n />\n )\n}\n"],"mappings":";;;;;;;;;AAcA,MAAM,sBAAsB,cAA+C,KAAK;AAEhF,MAAa,wBAAwB;CACnC,MAAM,UAAU,WAAW,oBAAoB;AAC/C,KAAI,YAAY,KACd,OAAM,IAAI,MAAM,qDAAqD;AAEvE,QAAO;;AAaT,MAAa,gBAAgB,EAAE,WAAW,UAAU,cAAc,IAAI,MAAM,cAAc,cAAc,QAAQ,WAAW,gBAAgB,GAAG,YAA+B;CAC3K,MAAM,UAAU,eAAe,IAAI,KAAK;CACxC,MAAM,iBAAiB,kBAAkB,QAAQ;CACjD,MAAM,mBAAmB,GAAG,QAAQ;CACpC,MAAM,EAAE,YAAY,WAAW,cAAc,kBAAkB,UAAU,MAAM;CAC/E,MAAM,YAAY,UAAU,WAAW,eAAe,iBAAiB,UAAU,aAAa,iBAAiB,mBAAmB;AAElI,QACE,oBAAC,oBAAoB,UAArB;EAA8B,OAAO;GAAE;GAAW;GAAY;GAAW;GAAW;GAAa;GAAO;YACtG,qBAAC,OAAD;GAAK,aAAU;GAAsB,WAAU;aAA/C;IACE,oBAAC,OAAD;KACE,aAAU;KACV,oBAAkB;KAClB,cAAY;KACZ,IAAI;KACJ,MAAK;KACL,oBAAkB;KAClB,WAAW,GAAG,qBAAqB,gBAAgB,cAAc,YAAY,cAAc,kCAAkC,UAAU;KACvI,GAAI;KACJ;IACF,oBAAC,cAAD;KAAc,YAAW;KAAuC,IAAI;KAAgB,SAAS,YAAY,eAAe;KAAQ;IAChI,oBAAC,gBAAD;KAAgB,YAAW;KAAyC,IAAI;KAAkB,SAAS,UAAU,YAAY,iBAAiB;KAAQ;IAC9I;;EACuB;;AAInC,MAAa,oBAAoB,EAAE,WAAW,GAAG,YAAyC;CACxF,MAAM,EAAE,gBAAgB,iBAAiB;AAEzC,QACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,6CACA,gBAAgB,gBAAgB,iEAChC,gBAAgB,cAAc,4GAC9B,UACD;EACD,GAAI;EACJ"}
1
+ {"version":3,"file":"ControlGroup.js","names":[],"sources":["../src/components/ControlGroup/ControlGroup.tsx"],"sourcesContent":["import { Slot } from '@primitives/slot'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId, useFormFieldState, WarningMessage, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { createContext, useContext, type ComponentProps } from 'react'\n\ninterface ControlGroupContextValue {\n messageId?: string\n isDisabled: boolean\n isInvalid: boolean\n isLoading: boolean\n orientation: 'horizontal' | 'vertical'\n state: FormFieldState\n}\n\nconst ControlGroupContext = createContext<ControlGroupContextValue | null>(null)\n\nexport const useControlGroup = () => {\n const context = useContext(ControlGroupContext)\n if (context === null) {\n throw new Error('useControlGroup must be used within a ControlGroup')\n }\n return context\n}\n\nexport interface ControlGroupProps extends ComponentProps<'div'> {\n disabled?: boolean\n errorMessage?: string | string[] | Record<string, unknown> | null\n id?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n name?: string\n orientation?: 'horizontal' | 'vertical'\n state?: FormFieldState\n warningMessage?: string | string[] | Record<string, unknown> | null\n}\n\nexport const ControlGroup = ({ className, disabled, errorMessage, id, messageReserveLines = 1, messageReserveSpace = true, name, orientation = 'horizontal', state = 'default', warningMessage, ...props }: ControlGroupProps) => {\n const groupId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(groupId)\n const warningMessageId = `${groupId}-warning`\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const messageId = state === 'error' && errorMessage ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n\n return (\n <ControlGroupContext.Provider value={{ messageId, isDisabled, isInvalid, isLoading, orientation, state }}>\n <div data-slot='control-group-field' className='space-y-1.5 w-full'>\n <div\n data-slot='control-group'\n data-orientation={orientation}\n data-state={state}\n id={groupId}\n role='group'\n aria-describedby={messageId}\n className={cn('group flex w-full', orientation === 'vertical' && 'flex-col', isDisabled && 'pointer-events-none opacity-50', className)}\n {...props}\n />\n <ErrorMessage dataTestId='spectral-control-group-error-message' id={errorMessageId} message={isInvalid ? errorMessage : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'error'} />\n <WarningMessage dataTestId='spectral-control-group-warning-message' id={warningMessageId} message={state === 'warning' ? warningMessage : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'warning'} />\n </div>\n </ControlGroupContext.Provider>\n )\n}\n\nexport const ControlGroupItem = ({ className, ...props }: ComponentProps<typeof Slot>) => {\n const { orientation } = useControlGroup()\n\n return (\n <Slot\n data-slot='control-group-item'\n className={cn(\n 'rounded-none focus-within:z-10 hover:z-10',\n orientation === 'horizontal' && 'first:rounded-s-md last:me-0 last:rounded-e-md -me-0.5 h-auto',\n orientation === 'vertical' && 'first:rounded-ss-md first:rounded-se-md last:rounded-ee-md last:rounded-es-md last:mbe-0 -mbe-0.5 w-auto',\n className,\n )}\n {...props}\n />\n )\n}\n"],"mappings":";;;;;;;;;AAcA,MAAM,sBAAsB,cAA+C,KAAK;AAEhF,MAAa,wBAAwB;CACnC,MAAM,UAAU,WAAW,oBAAoB;AAC/C,KAAI,YAAY,KACd,OAAM,IAAI,MAAM,qDAAqD;AAEvE,QAAO;;AAeT,MAAa,gBAAgB,EAAE,WAAW,UAAU,cAAc,IAAI,sBAAsB,GAAG,sBAAsB,MAAM,MAAM,cAAc,cAAc,QAAQ,WAAW,gBAAgB,GAAG,YAA+B;CAChO,MAAM,UAAU,eAAe,IAAI,KAAK;CACxC,MAAM,iBAAiB,kBAAkB,QAAQ;CACjD,MAAM,mBAAmB,GAAG,QAAQ;CACpC,MAAM,EAAE,YAAY,WAAW,cAAc,kBAAkB,UAAU,MAAM;CAC/E,MAAM,YAAY,UAAU,WAAW,eAAe,iBAAiB,UAAU,aAAa,iBAAiB,mBAAmB;AAElI,QACE,oBAAC,oBAAoB,UAArB;EAA8B,OAAO;GAAE;GAAW;GAAY;GAAW;GAAW;GAAa;GAAO;YACtG,qBAAC,OAAD;GAAK,aAAU;GAAsB,WAAU;aAA/C;IACE,oBAAC,OAAD;KACE,aAAU;KACV,oBAAkB;KAClB,cAAY;KACZ,IAAI;KACJ,MAAK;KACL,oBAAkB;KAClB,WAAW,GAAG,qBAAqB,gBAAgB,cAAc,YAAY,cAAc,kCAAkC,UAAU;KACvI,GAAI;KACJ;IACF,oBAAC,cAAD;KAAc,YAAW;KAAuC,IAAI;KAAgB,SAAS,YAAY,eAAe;KAA2B;KAAqB,qBAAqB,uBAAuB,UAAU;KAAW;IACzO,oBAAC,gBAAD;KAAgB,YAAW;KAAyC,IAAI;KAAkB,SAAS,UAAU,YAAY,iBAAiB;KAA2B;KAAqB,qBAAqB,uBAAuB,UAAU;KAAa;IACzP;;EACuB;;AAInC,MAAa,oBAAoB,EAAE,WAAW,GAAG,YAAyC;CACxF,MAAM,EAAE,gBAAgB,iBAAiB;AAEzC,QACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,6CACA,gBAAgB,gBAAgB,iEAChC,gBAAgB,cAAc,4GAC9B,UACD;EACD,GAAI;EACJ"}
@@ -19,6 +19,8 @@ interface DateTimePickerProps extends Omit<ComponentProps<'div'>, 'onChange' | '
19
19
  hourFormat?: HourFormat;
20
20
  label?: string;
21
21
  locale?: Partial<Locale$1>;
22
+ messageReserveLines?: number;
23
+ messageReserveSpace?: boolean;
22
24
  onChange?: (date: Date | undefined) => void;
23
25
  showTimePicker?: boolean;
24
26
  state?: 'default' | 'disabled' | 'error';
@@ -37,6 +39,8 @@ declare function DateTimePicker({
37
39
  hourFormat,
38
40
  label,
39
41
  locale,
42
+ messageReserveLines,
43
+ messageReserveSpace,
40
44
  onChange,
41
45
  showTimePicker,
42
46
  state,
@@ -1 +1 @@
1
- {"version":3,"file":"DateTimePicker.d.ts","names":[],"sources":["../src/components/DateTimePicker/DateTimePicker.tsx"],"mappings":";;;;;;;;;;;;UAYiB,mBAAA,SAA4B,IAAA,CAAK,cAAA;EAChD,aAAA,GAAgB,IAAA,CAAK,aAAA;EACrB,YAAA,GAAe,IAAA;EACf,QAAA;EACA,gBAAA;EACA,YAAA,uBAAmC,MAAA;EACnC,UAAA,GAAa,UAAA;EACb,KAAA;EACA,MAAA,GAAS,OAAA,CAAQ,QAAA;EACjB,QAAA,IAAY,IAAA,EAAM,IAAA;EAClB,cAAA;EACA,KAAA;EACA,UAAA;EAVe;EAYf,gBAAA,GAAmB,OAAA,CAAQ,sBAAA;EAC3B,KAAA,GAAQ,IAAA;AAAA;AAAA;EAIR,aAAA;EACA,SAAA;EACA,YAAA;EACA,QAAA;EACA,gBAAA;EACA,YAAA;EACA,UAAA;EACA,KAAA;EACA,MAAA;EACA,QAAA;EACA,cAAA;EACA,KAAA;EACA,UAAA;EACA,gBAAA;EACA,KAAA;EACA,EAAA;EAAA,GACG;AAAA,GACF,mBAAA,GAAmB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"DateTimePicker.d.ts","names":[],"sources":["../src/components/DateTimePicker/DateTimePicker.tsx"],"mappings":";;;;;;;;;;;;UAYiB,mBAAA,SAA4B,IAAA,CAAK,cAAA;EAChD,aAAA,GAAgB,IAAA,CAAK,aAAA;EACrB,YAAA,GAAe,IAAA;EACf,QAAA;EACA,gBAAA;EACA,YAAA,uBAAmC,MAAA;EACnC,UAAA,GAAa,UAAA;EACb,KAAA;EACA,MAAA,GAAS,OAAA,CAAQ,QAAA;EACjB,mBAAA;EACA,mBAAA;EACA,QAAA,IAAY,IAAA,EAAM,IAAA;EAClB,cAAA;EACA,KAAA;EACA,UAAA;EARa;EAUb,gBAAA,GAAmB,OAAA,CAAQ,sBAAA;EAC3B,KAAA,GAAQ,IAAA;AAAA;AAAA;EAIR,aAAA;EACA,SAAA;EACA,YAAA;EACA,QAAA;EACA,gBAAA;EACA,YAAA;EACA,UAAA;EACA,KAAA;EACA,MAAA;EACA,mBAAA;EACA,mBAAA;EACA,QAAA;EACA,cAAA;EACA,KAAA;EACA,UAAA;EACA,gBAAA;EACA,KAAA;EACA,EAAA;EAAA,GACG;AAAA,GACF,mBAAA,GAAmB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -16,7 +16,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
16
16
  import "react-day-picker";
17
17
 
18
18
  //#region src/components/DateTimePicker/DateTimePicker.tsx
19
- const DateTimePicker = ({ calendarProps, className, defaultValue, disabled = false, disablePastDates = true, errorMessage, hourFormat, label, locale, onChange, showTimePicker = true, state = "default", timeLocale, timeTranslations, value, id, ...props }) => {
19
+ const DateTimePicker = ({ calendarProps, className, defaultValue, disabled = false, disablePastDates = true, errorMessage, hourFormat, label, locale, messageReserveLines = 1, messageReserveSpace = true, onChange, showTimePicker = true, state = "default", timeLocale, timeTranslations, value, id, ...props }) => {
20
20
  const fieldId = useFormFieldId(id);
21
21
  const errorMessageId = getErrorMessageId(fieldId);
22
22
  const describedBy = state === "error" && errorMessage ? errorMessageId : void 0;
@@ -73,7 +73,9 @@ const DateTimePicker = ({ calendarProps, className, defaultValue, disabled = fal
73
73
  }), /* @__PURE__ */ jsx(ErrorMessage, {
74
74
  dataTestId: "spectral-datetime-picker-error-message",
75
75
  id: errorMessageId,
76
- message: state === "error" ? errorMessage : null
76
+ message: state === "error" ? errorMessage : null,
77
+ messageReserveLines,
78
+ messageReserveSpace
77
79
  })]
78
80
  }), /* @__PURE__ */ jsxs(PopoverContent, {
79
81
  align: "start",