@fanvue/ui 3.18.0 → 3.19.1

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 (39) hide show
  1. package/dist/cjs/components/Autocomplete/Autocomplete.cjs +1 -1
  2. package/dist/cjs/components/Autocomplete/Autocomplete.cjs.map +1 -1
  3. package/dist/cjs/components/Badge/Badge.cjs +13 -11
  4. package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
  5. package/dist/cjs/components/Banner/Banner.cjs +1 -1
  6. package/dist/cjs/components/Banner/Banner.cjs.map +1 -1
  7. package/dist/cjs/components/Chip/Chip.cjs +21 -1
  8. package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
  9. package/dist/cjs/components/Count/Count.cjs +36 -7
  10. package/dist/cjs/components/Count/Count.cjs.map +1 -1
  11. package/dist/cjs/components/CyclingText/CyclingText.cjs +4 -1
  12. package/dist/cjs/components/CyclingText/CyclingText.cjs.map +1 -1
  13. package/dist/cjs/components/PhoneField/PhoneField.cjs +1 -1
  14. package/dist/cjs/components/PhoneField/PhoneField.cjs.map +1 -1
  15. package/dist/cjs/components/TextArea/TextArea.cjs +1 -1
  16. package/dist/cjs/components/TextArea/TextArea.cjs.map +1 -1
  17. package/dist/cjs/components/TextField/TextField.cjs +1 -1
  18. package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
  19. package/dist/components/Autocomplete/Autocomplete.mjs +1 -1
  20. package/dist/components/Autocomplete/Autocomplete.mjs.map +1 -1
  21. package/dist/components/Badge/Badge.mjs +13 -11
  22. package/dist/components/Badge/Badge.mjs.map +1 -1
  23. package/dist/components/Banner/Banner.mjs +1 -1
  24. package/dist/components/Banner/Banner.mjs.map +1 -1
  25. package/dist/components/Chip/Chip.mjs +21 -1
  26. package/dist/components/Chip/Chip.mjs.map +1 -1
  27. package/dist/components/Count/Count.mjs +36 -7
  28. package/dist/components/Count/Count.mjs.map +1 -1
  29. package/dist/components/CyclingText/CyclingText.mjs +4 -1
  30. package/dist/components/CyclingText/CyclingText.mjs.map +1 -1
  31. package/dist/components/PhoneField/PhoneField.mjs +1 -1
  32. package/dist/components/PhoneField/PhoneField.mjs.map +1 -1
  33. package/dist/components/TextArea/TextArea.mjs +1 -1
  34. package/dist/components/TextArea/TextArea.mjs.map +1 -1
  35. package/dist/components/TextField/TextField.mjs +1 -1
  36. package/dist/components/TextField/TextField.mjs.map +1 -1
  37. package/dist/index.d.ts +27 -7
  38. package/dist/styles/base.css +7 -0
  39. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CyclingText.cjs","sources":["../../../../src/components/CyclingText/CyclingText.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useCyclingCycle } from \"./useCyclingCycle\";\nimport { useCyclingTextTrackWidth } from \"./useCyclingTextTrackWidth\";\nimport { usePageVisibility } from \"./usePageVisibility\";\nimport { usePrefersReducedMotion } from \"./usePrefersReducedMotion\";\n\nconst DEFAULT_INTERVAL_MS = 2100;\nconst DEFAULT_TRANSITION_MS = 200;\n\nconst SLIDE_OFFSET_PX = 18;\n\n/** How the wrapper should be sized to accommodate variable-length items. */\nexport type CyclingTextSizing = \"longest\" | \"current\";\n\nexport interface CyclingTextProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** Strings to cycle through, in order. Cycles back to the first after the last. */\n items: readonly string[];\n /**\n * Milliseconds to wait after the previous transition finishes before starting the next one.\n * @default 2100\n */\n intervalMs?: number;\n /** Slide and cross-fade duration in milliseconds. @default 200 */\n transitionMs?: number;\n /** Direction the outgoing item slides. @default \"up\" */\n direction?: \"up\" | \"down\";\n /** When true, freezes on the current item — no further cycling until cleared. @default false */\n paused?: boolean;\n /**\n * How the wrapper sizes itself horizontally.\n * - `longest` reserves space for the longest item — no width jitter as items cycle.\n * - `current` shrinks/grows with each item, animating width between cycles.\n * @default \"longest\"\n */\n sizing?: CyclingTextSizing;\n /**\n * When `true`, updates are exposed to assistive technologies via `aria-live=\"polite\"`.\n * Leave `false` for decorative or frequently changing copy so screen readers are not interrupted on every cycle.\n * @default false\n */\n announceChanges?: boolean;\n /**\n * Class applied to each visible label span (current + incoming). Use this for\n * effects that have to sit on the text element itself, e.g. `background-clip: text`.\n */\n labelClassName?: string;\n /**\n * Called with the index of the settled, fully-visible item — once on mount and\n * again whenever it changes (after each transition completes). Lets a parent\n * stay in lockstep with what is on screen (e.g. to act on the item the user is\n * currently looking at) without running a second, drifting timer of its own.\n */\n onActiveIndexChange?: (index: number) => void;\n}\n\n/**\n * Cycles through a list of strings with a slide-in/slide-out animation. Lives\n * inline so it can sit inside divs, spans, buttons, or as a fake placeholder\n * overlay.\n *\n * @example\n * ```tsx\n * <CyclingText items={[\"Thinking\", \"Reading messages\", \"Drafting reply\"]} />\n * ```\n */\nexport const CyclingText = React.forwardRef<HTMLSpanElement, CyclingTextProps>(\n (\n {\n items,\n intervalMs = DEFAULT_INTERVAL_MS,\n transitionMs = DEFAULT_TRANSITION_MS,\n direction = \"up\",\n paused = false,\n sizing = \"longest\",\n className,\n labelClassName,\n announceChanges = false,\n onActiveIndexChange,\n ...rest\n },\n ref,\n ) => {\n const docVisible = usePageVisibility();\n const reducedMotion = usePrefersReducedMotion();\n const { sizingLabelRef, trackWidth } = useCyclingTextTrackWidth();\n const {\n cycle,\n currentIndex,\n currentLabel,\n incomingLabel,\n sizingLabel,\n onOutgoingTransitionEnd,\n } = useCyclingCycle(items, sizing, intervalMs, paused, docVisible, transitionMs);\n\n const itemCount = items.length;\n\n // Notify the parent of the settled active index without re-subscribing when\n // an inline callback identity changes each render — the latest is always\n // read from the ref.\n const onActiveIndexChangeRef = React.useRef(onActiveIndexChange);\n React.useEffect(() => {\n onActiveIndexChangeRef.current = onActiveIndexChange;\n }, [onActiveIndexChange]);\n React.useEffect(() => {\n onActiveIndexChangeRef.current?.(currentIndex);\n }, [currentIndex]);\n\n const outgoingMotionStyle = React.useMemo((): React.CSSProperties => {\n const durMs = reducedMotion ? 0 : transitionMs;\n const exiting = cycle.transitioning;\n const yExit = direction === \"up\" ? -SLIDE_OFFSET_PX : SLIDE_OFFSET_PX;\n return {\n opacity: exiting ? 0 : 1,\n transform: exiting ? `translate3d(0, ${yExit}px, 0)` : \"translate3d(0, 0, 0)\",\n transition:\n exiting && durMs > 0\n ? `opacity ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1)`\n : \"none\",\n };\n }, [cycle.transitioning, direction, transitionMs, reducedMotion]);\n\n const incomingMotionStyle = React.useMemo((): React.CSSProperties => {\n const durMs = reducedMotion ? 0 : transitionMs;\n const entered = cycle.incomingEntered;\n const yEnter = direction === \"up\" ? SLIDE_OFFSET_PX : -SLIDE_OFFSET_PX;\n return {\n opacity: entered ? 1 : 0,\n transform: entered ? \"translate3d(0, 0, 0)\" : `translate3d(0, ${yEnter}px, 0)`,\n transition:\n durMs > 0\n ? `opacity ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1)`\n : \"none\",\n };\n }, [cycle.incomingEntered, direction, transitionMs, reducedMotion]);\n\n if (itemCount === 0) {\n return null;\n }\n\n const wrapperStyle = {\n ...(trackWidth !== null ? { width: `${trackWidth}px` } : {}),\n // paddingTop: SLIDE_OFFSET_PX,\n } as React.CSSProperties;\n\n const showIncoming = incomingLabel !== null && cycle.transitioning;\n\n return (\n <span\n ref={ref}\n data-testid=\"cycling-text\"\n data-paused={paused ? \"true\" : undefined}\n className={cn(\n \"relative inline-flex items-center overflow-hidden align-middle leading-[inherit]\",\n \"motion-safe:transition-[width] motion-safe:duration-300 motion-safe:ease-out\",\n className,\n )}\n style={wrapperStyle}\n {...rest}\n >\n <span\n ref={sizingLabelRef}\n aria-hidden=\"true\"\n className=\"pointer-events-none invisible inline-block select-none whitespace-nowrap leading-[inherit]\"\n >\n {sizingLabel}\n </span>\n\n <span\n data-layer=\"current\"\n aria-live={announceChanges ? \"polite\" : undefined}\n aria-atomic={announceChanges ? true : undefined}\n data-state={cycle.transitioning ? \"exit\" : \"idle\"}\n onTransitionEnd={onOutgoingTransitionEnd}\n className={cn(\n \"absolute inset-0 flex items-center whitespace-nowrap leading-[inherit]\",\n labelClassName,\n )}\n style={outgoingMotionStyle}\n >\n {currentLabel}\n </span>\n\n {showIncoming ? (\n <span\n aria-hidden=\"true\"\n data-layer=\"incoming\"\n data-state={cycle.incomingEntered ? \"idle\" : \"enter\"}\n className={cn(\n \"absolute inset-0 flex items-center whitespace-nowrap leading-[inherit]\",\n labelClassName,\n )}\n style={incomingMotionStyle}\n >\n {incomingLabel}\n </span>\n ) : null}\n </span>\n );\n },\n);\n\nCyclingText.displayName = \"CyclingText\";\n"],"names":["React","usePageVisibility","usePrefersReducedMotion","useCyclingTextTrackWidth","useCyclingCycle","jsxs","cn","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"CyclingText.cjs","sources":["../../../../src/components/CyclingText/CyclingText.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useCyclingCycle } from \"./useCyclingCycle\";\nimport { useCyclingTextTrackWidth } from \"./useCyclingTextTrackWidth\";\nimport { usePageVisibility } from \"./usePageVisibility\";\nimport { usePrefersReducedMotion } from \"./usePrefersReducedMotion\";\n\nconst DEFAULT_INTERVAL_MS = 2100;\nconst DEFAULT_TRANSITION_MS = 200;\n\nconst SLIDE_OFFSET_PX = 18;\n\n/** How the wrapper should be sized to accommodate variable-length items. */\nexport type CyclingTextSizing = \"longest\" | \"current\";\n\nexport interface CyclingTextProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** Strings to cycle through, in order. Cycles back to the first after the last. */\n items: readonly string[];\n /**\n * Milliseconds to wait after the previous transition finishes before starting the next one.\n * @default 2100\n */\n intervalMs?: number;\n /** Slide and cross-fade duration in milliseconds. @default 200 */\n transitionMs?: number;\n /** Direction the outgoing item slides. @default \"up\" */\n direction?: \"up\" | \"down\";\n /** When true, freezes on the current item — no further cycling until cleared. @default false */\n paused?: boolean;\n /**\n * How the wrapper sizes itself horizontally.\n * - `longest` reserves space for the longest item — no width jitter as items cycle.\n * - `current` shrinks/grows with each item, animating width between cycles.\n * @default \"longest\"\n */\n sizing?: CyclingTextSizing;\n /**\n * When `true`, updates are exposed to assistive technologies via `aria-live=\"polite\"`.\n * Leave `false` for decorative or frequently changing copy so screen readers are not interrupted on every cycle.\n * @default false\n */\n announceChanges?: boolean;\n /**\n * Class applied to each visible label span (current + incoming). Use this for\n * effects that have to sit on the text element itself, e.g. `background-clip: text`.\n */\n labelClassName?: string;\n /**\n * Called with the index of the settled, fully-visible item — once on mount and\n * again whenever it changes (after each transition completes). Lets a parent\n * stay in lockstep with what is on screen (e.g. to act on the item the user is\n * currently looking at) without running a second, drifting timer of its own.\n */\n onActiveIndexChange?: (index: number) => void;\n}\n\n/**\n * Cycles through a list of strings with a slide-in/slide-out animation. Lives\n * inline so it can sit inside divs, spans, buttons, or as a fake placeholder\n * overlay.\n *\n * @example\n * ```tsx\n * <CyclingText items={[\"Thinking\", \"Reading messages\", \"Drafting reply\"]} />\n * ```\n */\nexport const CyclingText = React.forwardRef<HTMLSpanElement, CyclingTextProps>(\n (\n {\n items,\n intervalMs = DEFAULT_INTERVAL_MS,\n transitionMs = DEFAULT_TRANSITION_MS,\n direction = \"up\",\n paused = false,\n sizing = \"longest\",\n className,\n labelClassName,\n announceChanges = false,\n onActiveIndexChange,\n ...rest\n },\n ref,\n ) => {\n const docVisible = usePageVisibility();\n const reducedMotion = usePrefersReducedMotion();\n const { sizingLabelRef, trackWidth } = useCyclingTextTrackWidth();\n const {\n cycle,\n currentIndex,\n currentLabel,\n incomingLabel,\n sizingLabel,\n onOutgoingTransitionEnd,\n } = useCyclingCycle(items, sizing, intervalMs, paused, docVisible, transitionMs);\n\n const itemCount = items.length;\n\n // Notify the parent of the settled active index without re-subscribing when\n // an inline callback identity changes each render — the latest is always\n // read from the ref.\n const onActiveIndexChangeRef = React.useRef(onActiveIndexChange);\n React.useEffect(() => {\n onActiveIndexChangeRef.current = onActiveIndexChange;\n }, [onActiveIndexChange]);\n React.useEffect(() => {\n onActiveIndexChangeRef.current?.(currentIndex);\n }, [currentIndex]);\n\n const outgoingMotionStyle = React.useMemo((): React.CSSProperties => {\n const durMs = reducedMotion ? 0 : transitionMs;\n const exiting = cycle.transitioning;\n const yExit = direction === \"up\" ? -SLIDE_OFFSET_PX : SLIDE_OFFSET_PX;\n return {\n opacity: exiting ? 0 : 1,\n transform: exiting ? `translate3d(0, ${yExit}px, 0)` : \"translate3d(0, 0, 0)\",\n transition:\n exiting && durMs > 0\n ? `opacity ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1)`\n : \"none\",\n };\n }, [cycle.transitioning, direction, transitionMs, reducedMotion]);\n\n const incomingMotionStyle = React.useMemo((): React.CSSProperties => {\n const durMs = reducedMotion ? 0 : transitionMs;\n const entered = cycle.incomingEntered;\n const yEnter = direction === \"up\" ? SLIDE_OFFSET_PX : -SLIDE_OFFSET_PX;\n return {\n opacity: entered ? 1 : 0,\n transform: entered ? \"translate3d(0, 0, 0)\" : `translate3d(0, ${yEnter}px, 0)`,\n transition:\n durMs > 0\n ? `opacity ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${durMs}ms cubic-bezier(0.4, 0, 0.2, 1)`\n : \"none\",\n };\n }, [cycle.incomingEntered, direction, transitionMs, reducedMotion]);\n\n if (itemCount === 0) {\n return null;\n }\n\n const wrapperStyle = {\n ...(trackWidth !== null ? { width: `${trackWidth}px` } : {}),\n // paddingTop: SLIDE_OFFSET_PX,\n } as React.CSSProperties;\n\n const showIncoming = incomingLabel !== null && cycle.transitioning;\n\n return (\n <span\n ref={ref}\n data-testid=\"cycling-text\"\n data-paused={paused ? \"true\" : undefined}\n className={cn(\n \"relative inline-flex items-center overflow-hidden align-middle leading-[inherit]\",\n \"motion-safe:transition-[width] motion-safe:duration-300 motion-safe:ease-out\",\n className,\n )}\n style={wrapperStyle}\n {...rest}\n >\n <span\n ref={sizingLabelRef}\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none invisible inline-block select-none whitespace-nowrap leading-[inherit]\",\n labelClassName,\n )}\n >\n {sizingLabel}\n </span>\n\n <span\n data-layer=\"current\"\n aria-live={announceChanges ? \"polite\" : undefined}\n aria-atomic={announceChanges ? true : undefined}\n data-state={cycle.transitioning ? \"exit\" : \"idle\"}\n onTransitionEnd={onOutgoingTransitionEnd}\n className={cn(\n \"absolute inset-0 flex items-center whitespace-nowrap leading-[inherit]\",\n labelClassName,\n )}\n style={outgoingMotionStyle}\n >\n {currentLabel}\n </span>\n\n {showIncoming ? (\n <span\n aria-hidden=\"true\"\n data-layer=\"incoming\"\n data-state={cycle.incomingEntered ? \"idle\" : \"enter\"}\n className={cn(\n \"absolute inset-0 flex items-center whitespace-nowrap leading-[inherit]\",\n labelClassName,\n )}\n style={incomingMotionStyle}\n >\n {incomingLabel}\n </span>\n ) : null}\n </span>\n );\n },\n);\n\nCyclingText.displayName = \"CyclingText\";\n"],"names":["React","usePageVisibility","usePrefersReducedMotion","useCyclingTextTrackWidth","useCyclingCycle","jsxs","cn","jsx"],"mappings":"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"}
@@ -44,7 +44,7 @@ const PREFIX_TYPOGRAPHY = {
44
44
  };
45
45
  function getContainerClassName(size, error, disabled) {
46
46
  return cn.cn(
47
- "relative flex items-center gap-2 overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors",
47
+ "relative flex items-center gap-2 overflow-hidden rounded-sm border bg-inputs-inputs-primary after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors",
48
48
  CONTAINER_PADDING_X[size],
49
49
  CONTAINER_HEIGHT[size],
50
50
  error ? "border-error-content" : "border-border-primary",
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneField.cjs","sources":["../../../../src/components/PhoneField/PhoneField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\n\n/** Phone field height in pixels. */\nexport type PhoneFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface PhoneFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the phone field in pixels. @default \"48\" */\n size?: PhoneFieldSize;\n /** Whether the field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Dial code of the selected country, shown before the number (e.g. `\"+39\"`). */\n dialCode?: React.ReactNode;\n /** Flag of the selected country, rendered in the country selector (e.g. an image or flag icon). */\n flag?: React.ReactNode;\n /** Fired when the country selector (flag + chevron) is activated. Open the country picker here. */\n onCountrySelect?: () => void;\n /** Accessible name for the country selector button. @default \"Select country\" */\n countryButtonLabel?: string;\n /** Whether the field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<PhoneFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst CONTAINER_PADDING_X: Record<PhoneFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst INPUT_TYPOGRAPHY: Record<PhoneFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst PREFIX_TYPOGRAPHY: Record<PhoneFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nfunction getContainerClassName(size: PhoneFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative flex items-center gap-2 overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n CONTAINER_PADDING_X[size],\n CONTAINER_HEIGHT[size],\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n disabled && \"opacity-50\",\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"PhoneField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\nfunction PhoneFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\n/**\n * A phone number input with a country selector (flag + chevron), a fixed dial\n * code prefix, and a `tel` input. The country picker itself is owned by the\n * consumer: pass the selected country's `flag` and `dialCode`, and open your\n * picker from `onCountrySelect`.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <PhoneField\n * label=\"Phone number\"\n * flag={<img src={italyFlag} alt=\"\" />}\n * dialCode=\"+39\"\n * onCountrySelect={openCountryPicker}\n * value={number}\n * onChange={(e) => setNumber(e.target.value)}\n * />\n * ```\n */\nexport const PhoneField = React.forwardRef<HTMLInputElement, PhoneFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n dialCode,\n flag,\n onCountrySelect,\n countryButtonLabel = \"Select country\",\n className,\n id,\n disabled,\n fullWidth = false,\n type = \"tel\",\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const dialCodeId = `${inputId}-dial-code`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n const describedBy =\n [dialCode != null ? dialCodeId : null, bottomText ? helperTextId : null]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n const innerRef = React.useRef<HTMLInputElement>(null);\n const setRefs = React.useCallback(\n (node: HTMLInputElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.RefObject<HTMLInputElement | null>).current = node;\n },\n [ref],\n );\n\n const handleContainerMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return;\n const target = event.target as HTMLElement;\n if (target === innerRef.current) return;\n if (target.closest(\"[data-pf-interactive]\")) return;\n event.preventDefault();\n innerRef.current?.focus();\n };\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n {/* biome-ignore lint/a11y/noStaticElementInteractions: focus bridge delegates pointer clicks on the padding to the input */}\n <div\n className={getContainerClassName(size, error, disabled)}\n onMouseDown={handleContainerMouseDown}\n >\n <button\n type=\"button\"\n data-pf-interactive=\"\"\n onClick={onCountrySelect}\n disabled={disabled}\n aria-label={countryButtonLabel}\n className=\"flex shrink-0 items-center gap-1 rounded-2xs focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed\"\n >\n {flag != null && (\n <span className=\"flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full\">\n {flag}\n </span>\n )}\n <ChevronDownIcon size={16} className=\"shrink-0 text-content-secondary\" />\n </button>\n\n {dialCode != null && (\n <span\n id={dialCodeId}\n className={cn(\n \"shrink-0 select-none whitespace-nowrap text-content-primary\",\n PREFIX_TYPOGRAPHY[size],\n )}\n >\n {dialCode}\n </span>\n )}\n\n <input\n ref={setRefs}\n id={inputId}\n type={type}\n inputMode=\"tel\"\n disabled={disabled}\n aria-describedby={describedBy}\n aria-invalid={error || undefined}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_TYPOGRAPHY[size],\n )}\n {...props}\n />\n </div>\n\n {bottomText && (\n <PhoneFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </PhoneFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nPhoneField.displayName = \"PhoneField\";\n"],"names":["cn","jsx","React","jsxs","ChevronDownIcon"],"mappings":"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"}
1
+ {"version":3,"file":"PhoneField.cjs","sources":["../../../../src/components/PhoneField/PhoneField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\n\n/** Phone field height in pixels. */\nexport type PhoneFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface PhoneFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the phone field in pixels. @default \"48\" */\n size?: PhoneFieldSize;\n /** Whether the field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Dial code of the selected country, shown before the number (e.g. `\"+39\"`). */\n dialCode?: React.ReactNode;\n /** Flag of the selected country, rendered in the country selector (e.g. an image or flag icon). */\n flag?: React.ReactNode;\n /** Fired when the country selector (flag + chevron) is activated. Open the country picker here. */\n onCountrySelect?: () => void;\n /** Accessible name for the country selector button. @default \"Select country\" */\n countryButtonLabel?: string;\n /** Whether the field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<PhoneFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst CONTAINER_PADDING_X: Record<PhoneFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst INPUT_TYPOGRAPHY: Record<PhoneFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst PREFIX_TYPOGRAPHY: Record<PhoneFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nfunction getContainerClassName(size: PhoneFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative flex items-center gap-2 overflow-hidden rounded-sm border bg-inputs-inputs-primary after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors\",\n CONTAINER_PADDING_X[size],\n CONTAINER_HEIGHT[size],\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n disabled && \"opacity-50\",\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"PhoneField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\nfunction PhoneFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\n/**\n * A phone number input with a country selector (flag + chevron), a fixed dial\n * code prefix, and a `tel` input. The country picker itself is owned by the\n * consumer: pass the selected country's `flag` and `dialCode`, and open your\n * picker from `onCountrySelect`.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <PhoneField\n * label=\"Phone number\"\n * flag={<img src={italyFlag} alt=\"\" />}\n * dialCode=\"+39\"\n * onCountrySelect={openCountryPicker}\n * value={number}\n * onChange={(e) => setNumber(e.target.value)}\n * />\n * ```\n */\nexport const PhoneField = React.forwardRef<HTMLInputElement, PhoneFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n dialCode,\n flag,\n onCountrySelect,\n countryButtonLabel = \"Select country\",\n className,\n id,\n disabled,\n fullWidth = false,\n type = \"tel\",\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const dialCodeId = `${inputId}-dial-code`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n const describedBy =\n [dialCode != null ? dialCodeId : null, bottomText ? helperTextId : null]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n const innerRef = React.useRef<HTMLInputElement>(null);\n const setRefs = React.useCallback(\n (node: HTMLInputElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.RefObject<HTMLInputElement | null>).current = node;\n },\n [ref],\n );\n\n const handleContainerMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return;\n const target = event.target as HTMLElement;\n if (target === innerRef.current) return;\n if (target.closest(\"[data-pf-interactive]\")) return;\n event.preventDefault();\n innerRef.current?.focus();\n };\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n {/* biome-ignore lint/a11y/noStaticElementInteractions: focus bridge delegates pointer clicks on the padding to the input */}\n <div\n className={getContainerClassName(size, error, disabled)}\n onMouseDown={handleContainerMouseDown}\n >\n <button\n type=\"button\"\n data-pf-interactive=\"\"\n onClick={onCountrySelect}\n disabled={disabled}\n aria-label={countryButtonLabel}\n className=\"flex shrink-0 items-center gap-1 rounded-2xs focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed\"\n >\n {flag != null && (\n <span className=\"flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full\">\n {flag}\n </span>\n )}\n <ChevronDownIcon size={16} className=\"shrink-0 text-content-secondary\" />\n </button>\n\n {dialCode != null && (\n <span\n id={dialCodeId}\n className={cn(\n \"shrink-0 select-none whitespace-nowrap text-content-primary\",\n PREFIX_TYPOGRAPHY[size],\n )}\n >\n {dialCode}\n </span>\n )}\n\n <input\n ref={setRefs}\n id={inputId}\n type={type}\n inputMode=\"tel\"\n disabled={disabled}\n aria-describedby={describedBy}\n aria-invalid={error || undefined}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_TYPOGRAPHY[size],\n )}\n {...props}\n />\n </div>\n\n {bottomText && (\n <PhoneFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </PhoneFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nPhoneField.displayName = \"PhoneField\";\n"],"names":["cn","jsx","React","jsxs","ChevronDownIcon"],"mappings":"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"}
@@ -51,7 +51,7 @@ const CLEAR_BUTTON_RIGHT = {
51
51
  };
52
52
  function getContainerClassName(size, error, disabled) {
53
53
  return cn.cn(
54
- "relative rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors",
54
+ "relative rounded-sm border bg-inputs-inputs-primary after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors",
55
55
  error ? "border-error-content" : "border-border-primary",
56
56
  !disabled && !error && "hover:border-neutral-alphas-400",
57
57
  CONTAINER_MIN_HEIGHT[size],
@@ -1 +1 @@
1
- {"version":3,"file":"TextArea.cjs","sources":["../../../../src/components/TextArea/TextArea.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CheckOutlineIcon } from \"../Icons/CheckOutlineIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** Text area height in pixels. */\nexport type TextAreaSize = \"48\" | \"40\" | \"32\";\n\nexport interface TextAreaProps\n extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, \"size\"> {\n /** Label text displayed above the textarea. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the textarea. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Minimum height of the text area in pixels. @default \"48\" */\n size?: TextAreaSize;\n /** Whether the text area is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the textarea. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Whether the text area is validated. @default false */\n validated?: boolean;\n /** Whether the text area stretches to fill its container width. @default false */\n fullWidth?: boolean;\n /** Whether to show a clear button when text is present. @default false */\n showClearButton?: boolean;\n /** Callback fired when the clear button is clicked. Note: `onChange` is also called with an empty value when clearing. */\n onClear?: () => void;\n /** Minimum number of rows (lines) for the textarea. */\n minRows?: number;\n /** Maximum number of rows (lines) for the textarea. */\n maxRows?: number;\n /** Whether the textarea can be resized by the user. @default true */\n resizable?: boolean;\n}\n\nconst CONTAINER_MIN_HEIGHT: Record<TextAreaSize, string> = {\n \"48\": \"min-h-12\",\n \"40\": \"min-h-10\",\n \"32\": \"min-h-8\",\n};\n\nconst TEXTAREA_SIZE_CLASSES: Record<TextAreaSize, string> = {\n \"48\": \"py-3 typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"py-2 typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"py-2 typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst PADDING_HORIZONTAL: Record<TextAreaSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst PADDING_RIGHT_WITH_CLEAR: Record<TextAreaSize, string> = {\n \"48\": \"pr-11\",\n \"40\": \"pr-11\",\n \"32\": \"pr-10\",\n};\n\nconst CLEAR_BUTTON_RIGHT: Record<TextAreaSize, string> = {\n \"48\": \"right-4 top-3\",\n \"40\": \"right-4 top-2\",\n \"32\": \"right-3 top-2\",\n};\n\nfunction getContainerClassName(size: TextAreaSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n CONTAINER_MIN_HEIGHT[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getTextareaClassName(\n size: TextAreaSize,\n hasClearButton: boolean,\n hasMinRows: boolean,\n resizable: boolean,\n) {\n return cn(\n \"h-full w-full bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n resizable ? \"resize-y\" : \"resize-none\",\n !hasMinRows && \"min-h-[80px]\",\n TEXTAREA_SIZE_CLASSES[size],\n PADDING_HORIZONTAL[size],\n hasClearButton ? PADDING_RIGHT_WITH_CLEAR[size] : \"\",\n );\n}\n\nfunction TextAreaHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"TextArea: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\nfunction calculateMaxHeight(size: TextAreaSize, maxRows?: number): string | undefined {\n if (!maxRows) return undefined;\n\n // Line height is 24px for body-1 (sizes 48 and 40) and 20px for body-2 (size 32)\n const lineHeight = size === \"32\" ? 20 : 24;\n // py-2 = 8px, py-3 = 12px\n const verticalPadding = size === \"32\" ? 8 : size === \"40\" ? 8 : 12;\n\n return `${lineHeight * maxRows + verticalPadding * 2}px`;\n}\n\nfunction useTextAreaValue(\n value: React.TextareaHTMLAttributes<HTMLTextAreaElement>[\"value\"],\n defaultValue: React.TextareaHTMLAttributes<HTMLTextAreaElement>[\"defaultValue\"],\n showClearButton: boolean,\n onChange?: React.ChangeEventHandler<HTMLTextAreaElement>,\n onClear?: () => void,\n textareaRef?: React.RefObject<HTMLTextAreaElement | null>,\n) {\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInternalValue(e.target.value);\n onChange?.(e);\n };\n\n const handleClear = () => {\n setInternalValue(\"\");\n\n if (onChange && textareaRef?.current) {\n const syntheticEvent = {\n target: { ...textareaRef.current, value: \"\" },\n currentTarget: { ...textareaRef.current, value: \"\" },\n } as React.ChangeEvent<HTMLTextAreaElement>;\n onChange(syntheticEvent);\n }\n\n onClear?.();\n };\n\n return {\n resolvedValue,\n displayValue: showClearButton ? resolvedValue : value,\n resolvedDefaultValue: showClearButton ? undefined : defaultValue,\n handleChange,\n handleClear,\n };\n}\n\n/**\n * A multi-line text input with optional label, helper/error text, and clear button.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <TextArea\n * label=\"Description\"\n * placeholder=\"Enter your description...\"\n * showClearButton\n * error={!!descError}\n * errorMessage={descError}\n * />\n * ```\n */\nexport const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n validated = false,\n className,\n id,\n disabled,\n fullWidth = false,\n showClearButton = false,\n onClear,\n value,\n defaultValue,\n onChange,\n minRows,\n maxRows,\n resizable = false,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n const maxHeight = calculateMaxHeight(size, maxRows);\n\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n\n const { resolvedValue, displayValue, resolvedDefaultValue, handleChange, handleClear } =\n useTextAreaValue(value, defaultValue, showClearButton, onChange, onClear, internalRef);\n\n const mergedRef = (node: HTMLTextAreaElement | null) => {\n internalRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n };\n\n const showClear = showClearButton && resolvedValue !== \"\" && !disabled;\n const showValidated = validated && !showClear;\n const ariaDescribedBy = bottomText ? helperTextId : undefined;\n const textareaStyle = maxHeight ? { maxHeight } : undefined;\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n <textarea\n ref={mergedRef}\n id={inputId}\n disabled={disabled}\n aria-describedby={ariaDescribedBy}\n aria-invalid={error || undefined}\n className={getTextareaClassName(size, showClear, !!minRows, resizable)}\n value={displayValue}\n defaultValue={resolvedDefaultValue}\n onChange={handleChange}\n rows={minRows}\n style={textareaStyle}\n {...props}\n />\n\n {showClear && (\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n icon={<CloseIcon />}\n aria-label=\"Clear text\"\n onClick={handleClear}\n className={cn(\n \"absolute flex size-5 items-center justify-center self-end\",\n CLEAR_BUTTON_RIGHT[size],\n )}\n />\n )}\n {showValidated && (\n <div\n className={cn(\n \"pointer-events-none absolute flex size-5 items-center justify-center\",\n CLEAR_BUTTON_RIGHT[size],\n )}\n >\n <CheckOutlineIcon className=\"text-success-content\" />\n </div>\n )}\n </div>\n\n {bottomText && (\n <TextAreaHelperText id={helperTextId} error={error}>\n {bottomText}\n </TextAreaHelperText>\n )}\n </div>\n );\n },\n);\n\nTextArea.displayName = \"TextArea\";\n"],"names":["cn","jsx","React","jsxs","IconButton","CloseIcon","CheckOutlineIcon"],"mappings":"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"}
1
+ {"version":3,"file":"TextArea.cjs","sources":["../../../../src/components/TextArea/TextArea.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CheckOutlineIcon } from \"../Icons/CheckOutlineIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** Text area height in pixels. */\nexport type TextAreaSize = \"48\" | \"40\" | \"32\";\n\nexport interface TextAreaProps\n extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, \"size\"> {\n /** Label text displayed above the textarea. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the textarea. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Minimum height of the text area in pixels. @default \"48\" */\n size?: TextAreaSize;\n /** Whether the text area is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the textarea. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Whether the text area is validated. @default false */\n validated?: boolean;\n /** Whether the text area stretches to fill its container width. @default false */\n fullWidth?: boolean;\n /** Whether to show a clear button when text is present. @default false */\n showClearButton?: boolean;\n /** Callback fired when the clear button is clicked. Note: `onChange` is also called with an empty value when clearing. */\n onClear?: () => void;\n /** Minimum number of rows (lines) for the textarea. */\n minRows?: number;\n /** Maximum number of rows (lines) for the textarea. */\n maxRows?: number;\n /** Whether the textarea can be resized by the user. @default true */\n resizable?: boolean;\n}\n\nconst CONTAINER_MIN_HEIGHT: Record<TextAreaSize, string> = {\n \"48\": \"min-h-12\",\n \"40\": \"min-h-10\",\n \"32\": \"min-h-8\",\n};\n\nconst TEXTAREA_SIZE_CLASSES: Record<TextAreaSize, string> = {\n \"48\": \"py-3 typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"py-2 typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"py-2 typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst PADDING_HORIZONTAL: Record<TextAreaSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst PADDING_RIGHT_WITH_CLEAR: Record<TextAreaSize, string> = {\n \"48\": \"pr-11\",\n \"40\": \"pr-11\",\n \"32\": \"pr-10\",\n};\n\nconst CLEAR_BUTTON_RIGHT: Record<TextAreaSize, string> = {\n \"48\": \"right-4 top-3\",\n \"40\": \"right-4 top-2\",\n \"32\": \"right-3 top-2\",\n};\n\nfunction getContainerClassName(size: TextAreaSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative rounded-sm border bg-inputs-inputs-primary after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n CONTAINER_MIN_HEIGHT[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getTextareaClassName(\n size: TextAreaSize,\n hasClearButton: boolean,\n hasMinRows: boolean,\n resizable: boolean,\n) {\n return cn(\n \"h-full w-full bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n resizable ? \"resize-y\" : \"resize-none\",\n !hasMinRows && \"min-h-[80px]\",\n TEXTAREA_SIZE_CLASSES[size],\n PADDING_HORIZONTAL[size],\n hasClearButton ? PADDING_RIGHT_WITH_CLEAR[size] : \"\",\n );\n}\n\nfunction TextAreaHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"TextArea: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\nfunction calculateMaxHeight(size: TextAreaSize, maxRows?: number): string | undefined {\n if (!maxRows) return undefined;\n\n // Line height is 24px for body-1 (sizes 48 and 40) and 20px for body-2 (size 32)\n const lineHeight = size === \"32\" ? 20 : 24;\n // py-2 = 8px, py-3 = 12px\n const verticalPadding = size === \"32\" ? 8 : size === \"40\" ? 8 : 12;\n\n return `${lineHeight * maxRows + verticalPadding * 2}px`;\n}\n\nfunction useTextAreaValue(\n value: React.TextareaHTMLAttributes<HTMLTextAreaElement>[\"value\"],\n defaultValue: React.TextareaHTMLAttributes<HTMLTextAreaElement>[\"defaultValue\"],\n showClearButton: boolean,\n onChange?: React.ChangeEventHandler<HTMLTextAreaElement>,\n onClear?: () => void,\n textareaRef?: React.RefObject<HTMLTextAreaElement | null>,\n) {\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInternalValue(e.target.value);\n onChange?.(e);\n };\n\n const handleClear = () => {\n setInternalValue(\"\");\n\n if (onChange && textareaRef?.current) {\n const syntheticEvent = {\n target: { ...textareaRef.current, value: \"\" },\n currentTarget: { ...textareaRef.current, value: \"\" },\n } as React.ChangeEvent<HTMLTextAreaElement>;\n onChange(syntheticEvent);\n }\n\n onClear?.();\n };\n\n return {\n resolvedValue,\n displayValue: showClearButton ? resolvedValue : value,\n resolvedDefaultValue: showClearButton ? undefined : defaultValue,\n handleChange,\n handleClear,\n };\n}\n\n/**\n * A multi-line text input with optional label, helper/error text, and clear button.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <TextArea\n * label=\"Description\"\n * placeholder=\"Enter your description...\"\n * showClearButton\n * error={!!descError}\n * errorMessage={descError}\n * />\n * ```\n */\nexport const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n validated = false,\n className,\n id,\n disabled,\n fullWidth = false,\n showClearButton = false,\n onClear,\n value,\n defaultValue,\n onChange,\n minRows,\n maxRows,\n resizable = false,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n const maxHeight = calculateMaxHeight(size, maxRows);\n\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n\n const { resolvedValue, displayValue, resolvedDefaultValue, handleChange, handleClear } =\n useTextAreaValue(value, defaultValue, showClearButton, onChange, onClear, internalRef);\n\n const mergedRef = (node: HTMLTextAreaElement | null) => {\n internalRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n };\n\n const showClear = showClearButton && resolvedValue !== \"\" && !disabled;\n const showValidated = validated && !showClear;\n const ariaDescribedBy = bottomText ? helperTextId : undefined;\n const textareaStyle = maxHeight ? { maxHeight } : undefined;\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n <textarea\n ref={mergedRef}\n id={inputId}\n disabled={disabled}\n aria-describedby={ariaDescribedBy}\n aria-invalid={error || undefined}\n className={getTextareaClassName(size, showClear, !!minRows, resizable)}\n value={displayValue}\n defaultValue={resolvedDefaultValue}\n onChange={handleChange}\n rows={minRows}\n style={textareaStyle}\n {...props}\n />\n\n {showClear && (\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n icon={<CloseIcon />}\n aria-label=\"Clear text\"\n onClick={handleClear}\n className={cn(\n \"absolute flex size-5 items-center justify-center self-end\",\n CLEAR_BUTTON_RIGHT[size],\n )}\n />\n )}\n {showValidated && (\n <div\n className={cn(\n \"pointer-events-none absolute flex size-5 items-center justify-center\",\n CLEAR_BUTTON_RIGHT[size],\n )}\n >\n <CheckOutlineIcon className=\"text-success-content\" />\n </div>\n )}\n </div>\n\n {bottomText && (\n <TextAreaHelperText id={helperTextId} error={error}>\n {bottomText}\n </TextAreaHelperText>\n )}\n </div>\n );\n },\n);\n\nTextArea.displayName = \"TextArea\";\n"],"names":["cn","jsx","React","jsxs","IconButton","CloseIcon","CheckOutlineIcon"],"mappings":"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"}
@@ -55,7 +55,7 @@ const SIDE_LABEL_TYPOGRAPHY = {
55
55
  };
56
56
  function getContainerClassName(size, error, disabled, hasAction) {
57
57
  return cn.cn(
58
- "relative flex items-center overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors",
58
+ "relative flex items-center overflow-hidden rounded-sm border bg-inputs-inputs-primary after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors",
59
59
  hasAction ? CONTAINER_PADDING_X_WITH_ACTION[size] : CONTAINER_PADDING_X[size],
60
60
  CONTAINER_GAP[size],
61
61
  error ? "border-error-content" : "border-border-primary",
@@ -1 +1 @@
1
- {"version":3,"file":"TextField.cjs","sources":["../../../../src/components/TextField/TextField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CheckOutlineIcon } from \"@/index\";\nimport { cn } from \"../../utils/cn\";\n\n/** Text field height in pixels. */\nexport type TextFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface TextFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the text field in pixels. @default \"48\" */\n size?: TextFieldSize;\n /** Whether the text field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Whether the text field is validated. @default false */\n validated?: boolean;\n /** Icon element displayed at the left side of the input. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed at the right side of the input. */\n rightIcon?: React.ReactNode;\n /** Fixed, non-editable label pinned inside the left edge of the field — for a prefix such as a currency symbol or country code. */\n leftLabel?: React.ReactNode;\n /** Fixed, non-editable label pinned inside the right edge of the field — for a unit or suffix such as a currency code or domain. */\n rightLabel?: React.ReactNode;\n /**\n * Trailing interactive element pinned to the right edge — typically a `Chip`\n * or `Button` (the \"with button\" field type). Reduces the right padding so the\n * control sits flush, and clicks on it do not steal focus from the input.\n */\n action?: React.ReactNode;\n /** Whether the text field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<TextFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst CONTAINER_PADDING_X: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst CONTAINER_PADDING_X_WITH_ACTION: Record<TextFieldSize, string> = {\n \"48\": \"pl-4 pr-2\",\n \"40\": \"pl-4 pr-2\",\n \"32\": \"pl-3 pr-2\",\n};\n\nconst CONTAINER_GAP: Record<TextFieldSize, string> = {\n \"48\": \"gap-2.5\",\n \"40\": \"gap-2.5\",\n \"32\": \"gap-2\",\n};\n\nconst INPUT_TYPOGRAPHY: Record<TextFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst SIDE_LABEL_TYPOGRAPHY: Record<TextFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nfunction getContainerClassName(\n size: TextFieldSize,\n error: boolean,\n disabled?: boolean,\n hasAction?: boolean,\n) {\n return cn(\n \"relative flex items-center overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n hasAction ? CONTAINER_PADDING_X_WITH_ACTION[size] : CONTAINER_PADDING_X[size],\n CONTAINER_GAP[size],\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction LeadingIcon({ children }: { children?: React.ReactNode }) {\n if (!children) return null;\n return (\n <span className=\"pointer-events-none flex size-4 shrink-0 items-center justify-center text-content-secondary\">\n {children}\n </span>\n );\n}\n\nfunction SideLabel({\n id,\n size,\n align,\n children,\n}: {\n id?: string;\n size: TextFieldSize;\n align: \"left\" | \"right\";\n children?: React.ReactNode;\n}) {\n if (!children) return null;\n return (\n <span\n id={id}\n className={cn(\n \"shrink-0 select-none whitespace-nowrap text-content-tertiary\",\n align === \"right\" && \"text-right\",\n SIDE_LABEL_TYPOGRAPHY[size],\n )}\n >\n {children}\n </span>\n );\n}\n\nfunction TrailingAdornment({\n rightIcon,\n validated,\n}: {\n rightIcon?: React.ReactNode;\n validated: boolean;\n}) {\n if (!rightIcon && !validated) return null;\n return (\n <span className=\"flex shrink-0 items-center gap-2 text-content-secondary\">\n {rightIcon && (\n <span data-tf-interactive=\"\" className=\"flex size-4 shrink-0 items-center justify-center\">\n {rightIcon}\n </span>\n )}\n {validated && (\n <span className=\"pointer-events-none flex size-4 shrink-0 items-center justify-center\">\n <CheckOutlineIcon className=\"text-success-content\" />\n </span>\n )}\n </span>\n );\n}\n\nfunction TextFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"TextField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A text input field with optional label, helper/error text, icon slots, and side labels.\n *\n * Use `leftLabel` / `rightLabel` for fixed unit or prefix affordances (currency symbol,\n * country code, domain suffix). Provide at least one of `label`, `aria-label`, or\n * `aria-labelledby` for accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <TextField label=\"Price\" leftLabel=\"$\" rightLabel=\"USD\" placeholder=\"0.00\" />\n * ```\n *\n * @example\n * ```tsx\n * <TextField\n * label=\"Promo code\"\n * placeholder=\"Enter code\"\n * action={<Chip size=\"32\" onClick={apply}>Apply</Chip>}\n * />\n * ```\n */\nexport const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n validated = false,\n leftIcon,\n rightIcon,\n leftLabel,\n rightLabel,\n action,\n className,\n id,\n disabled,\n fullWidth = false,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const leftLabelId = `${inputId}-left-label`;\n const rightLabelId = `${inputId}-right-label`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n const describedBy =\n [\n leftLabel != null ? leftLabelId : null,\n rightLabel != null ? rightLabelId : null,\n bottomText ? helperTextId : null,\n ]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n const innerRef = React.useRef<HTMLInputElement>(null);\n const setRefs = React.useCallback(\n (node: HTMLInputElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n },\n [ref],\n );\n\n // Keep clicks on the non-interactive adornments (icons, side labels, padding)\n // focusing the input, matching the old absolute/pointer-events-none layout.\n const handleContainerMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return;\n const target = event.target as HTMLElement;\n if (target === innerRef.current) return;\n if (target.closest(\"[data-tf-interactive]\")) return;\n event.preventDefault();\n innerRef.current?.focus();\n };\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n {/* biome-ignore lint/a11y/noStaticElementInteractions: focus bridge delegates pointer clicks on adornments to the input */}\n <div\n className={getContainerClassName(size, error, disabled, action != null)}\n onMouseDown={handleContainerMouseDown}\n >\n <LeadingIcon>{leftIcon}</LeadingIcon>\n <SideLabel id={leftLabelId} size={size} align=\"left\">\n {leftLabel}\n </SideLabel>\n\n <input\n ref={setRefs}\n id={inputId}\n disabled={disabled}\n aria-describedby={describedBy}\n aria-invalid={error || undefined}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_TYPOGRAPHY[size],\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n <SideLabel id={rightLabelId} size={size} align=\"right\">\n {rightLabel}\n </SideLabel>\n <TrailingAdornment rightIcon={rightIcon} validated={validated} />\n {action != null && (\n <span data-tf-interactive=\"\" className=\"flex shrink-0 items-center\">\n {action}\n </span>\n )}\n </div>\n\n {bottomText && (\n <TextFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </TextFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nTextField.displayName = \"TextField\";\n"],"names":["cn","jsx","jsxs","CheckOutlineIcon","React"],"mappings":"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"}
1
+ {"version":3,"file":"TextField.cjs","sources":["../../../../src/components/TextField/TextField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CheckOutlineIcon } from \"@/index\";\nimport { cn } from \"../../utils/cn\";\n\n/** Text field height in pixels. */\nexport type TextFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface TextFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the text field in pixels. @default \"48\" */\n size?: TextFieldSize;\n /** Whether the text field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Whether the text field is validated. @default false */\n validated?: boolean;\n /** Icon element displayed at the left side of the input. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed at the right side of the input. */\n rightIcon?: React.ReactNode;\n /** Fixed, non-editable label pinned inside the left edge of the field — for a prefix such as a currency symbol or country code. */\n leftLabel?: React.ReactNode;\n /** Fixed, non-editable label pinned inside the right edge of the field — for a unit or suffix such as a currency code or domain. */\n rightLabel?: React.ReactNode;\n /**\n * Trailing interactive element pinned to the right edge — typically a `Chip`\n * or `Button` (the \"with button\" field type). Reduces the right padding so the\n * control sits flush, and clicks on it do not steal focus from the input.\n */\n action?: React.ReactNode;\n /** Whether the text field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<TextFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst CONTAINER_PADDING_X: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst CONTAINER_PADDING_X_WITH_ACTION: Record<TextFieldSize, string> = {\n \"48\": \"pl-4 pr-2\",\n \"40\": \"pl-4 pr-2\",\n \"32\": \"pl-3 pr-2\",\n};\n\nconst CONTAINER_GAP: Record<TextFieldSize, string> = {\n \"48\": \"gap-2.5\",\n \"40\": \"gap-2.5\",\n \"32\": \"gap-2\",\n};\n\nconst INPUT_TYPOGRAPHY: Record<TextFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst SIDE_LABEL_TYPOGRAPHY: Record<TextFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nfunction getContainerClassName(\n size: TextFieldSize,\n error: boolean,\n disabled?: boolean,\n hasAction?: boolean,\n) {\n return cn(\n \"relative flex items-center overflow-hidden rounded-sm border bg-inputs-inputs-primary after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors\",\n hasAction ? CONTAINER_PADDING_X_WITH_ACTION[size] : CONTAINER_PADDING_X[size],\n CONTAINER_GAP[size],\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction LeadingIcon({ children }: { children?: React.ReactNode }) {\n if (!children) return null;\n return (\n <span className=\"pointer-events-none flex size-4 shrink-0 items-center justify-center text-content-secondary\">\n {children}\n </span>\n );\n}\n\nfunction SideLabel({\n id,\n size,\n align,\n children,\n}: {\n id?: string;\n size: TextFieldSize;\n align: \"left\" | \"right\";\n children?: React.ReactNode;\n}) {\n if (!children) return null;\n return (\n <span\n id={id}\n className={cn(\n \"shrink-0 select-none whitespace-nowrap text-content-tertiary\",\n align === \"right\" && \"text-right\",\n SIDE_LABEL_TYPOGRAPHY[size],\n )}\n >\n {children}\n </span>\n );\n}\n\nfunction TrailingAdornment({\n rightIcon,\n validated,\n}: {\n rightIcon?: React.ReactNode;\n validated: boolean;\n}) {\n if (!rightIcon && !validated) return null;\n return (\n <span className=\"flex shrink-0 items-center gap-2 text-content-secondary\">\n {rightIcon && (\n <span data-tf-interactive=\"\" className=\"flex size-4 shrink-0 items-center justify-center\">\n {rightIcon}\n </span>\n )}\n {validated && (\n <span className=\"pointer-events-none flex size-4 shrink-0 items-center justify-center\">\n <CheckOutlineIcon className=\"text-success-content\" />\n </span>\n )}\n </span>\n );\n}\n\nfunction TextFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"TextField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A text input field with optional label, helper/error text, icon slots, and side labels.\n *\n * Use `leftLabel` / `rightLabel` for fixed unit or prefix affordances (currency symbol,\n * country code, domain suffix). Provide at least one of `label`, `aria-label`, or\n * `aria-labelledby` for accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <TextField label=\"Price\" leftLabel=\"$\" rightLabel=\"USD\" placeholder=\"0.00\" />\n * ```\n *\n * @example\n * ```tsx\n * <TextField\n * label=\"Promo code\"\n * placeholder=\"Enter code\"\n * action={<Chip size=\"32\" onClick={apply}>Apply</Chip>}\n * />\n * ```\n */\nexport const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n validated = false,\n leftIcon,\n rightIcon,\n leftLabel,\n rightLabel,\n action,\n className,\n id,\n disabled,\n fullWidth = false,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const leftLabelId = `${inputId}-left-label`;\n const rightLabelId = `${inputId}-right-label`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n const describedBy =\n [\n leftLabel != null ? leftLabelId : null,\n rightLabel != null ? rightLabelId : null,\n bottomText ? helperTextId : null,\n ]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n const innerRef = React.useRef<HTMLInputElement>(null);\n const setRefs = React.useCallback(\n (node: HTMLInputElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n },\n [ref],\n );\n\n // Keep clicks on the non-interactive adornments (icons, side labels, padding)\n // focusing the input, matching the old absolute/pointer-events-none layout.\n const handleContainerMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return;\n const target = event.target as HTMLElement;\n if (target === innerRef.current) return;\n if (target.closest(\"[data-tf-interactive]\")) return;\n event.preventDefault();\n innerRef.current?.focus();\n };\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n {/* biome-ignore lint/a11y/noStaticElementInteractions: focus bridge delegates pointer clicks on adornments to the input */}\n <div\n className={getContainerClassName(size, error, disabled, action != null)}\n onMouseDown={handleContainerMouseDown}\n >\n <LeadingIcon>{leftIcon}</LeadingIcon>\n <SideLabel id={leftLabelId} size={size} align=\"left\">\n {leftLabel}\n </SideLabel>\n\n <input\n ref={setRefs}\n id={inputId}\n disabled={disabled}\n aria-describedby={describedBy}\n aria-invalid={error || undefined}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_TYPOGRAPHY[size],\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n <SideLabel id={rightLabelId} size={size} align=\"right\">\n {rightLabel}\n </SideLabel>\n <TrailingAdornment rightIcon={rightIcon} validated={validated} />\n {action != null && (\n <span data-tf-interactive=\"\" className=\"flex shrink-0 items-center\">\n {action}\n </span>\n )}\n </div>\n\n {bottomText && (\n <TextFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </TextFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nTextField.displayName = \"TextField\";\n"],"names":["cn","jsx","jsxs","CheckOutlineIcon","React"],"mappings":"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"}
@@ -81,7 +81,7 @@ const Autocomplete = React.forwardRef((props, ref) => {
81
81
  "div",
82
82
  {
83
83
  className: cn(
84
- "flex flex-wrap items-center overflow-hidden rounded-sm border bg-neutral-alphas-100 has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors",
84
+ "relative flex flex-wrap items-center overflow-hidden rounded-sm border bg-neutral-alphas-100 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors",
85
85
  error ? "border-error-content" : "border-transparent",
86
86
  !disabled && !error && "hover:border-neutral-alphas-400",
87
87
  ac.isOpen && !error && !disabled && "border-neutral-alphas-400",
@@ -1 +1 @@
1
- {"version":3,"file":"Autocomplete.mjs","sources":["../../../src/components/Autocomplete/Autocomplete.tsx"],"sourcesContent":["import * as Popover from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"@/utils/floatingContentCollisionPadding\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\nimport { AutocompleteDropdownContent } from \"./AutocompleteDropdownContent\";\nimport { AutocompleteTag } from \"./AutocompleteTag\";\nimport { useAutocomplete } from \"./useAutocomplete\";\n\nexport type AutocompleteSize = \"48\" | \"40\" | \"32\";\n\nexport interface AutocompleteOption {\n /** Unique value identifying the option. Returned via `onChange`. */\n value: string;\n /** Visible label. Falls back to `value` when omitted. */\n label?: string;\n /** When `true`, the option renders but cannot be selected. @default false */\n disabled?: boolean;\n /**\n * ID of the group this option belongs to. Must match an entry in the\n * component's `groups` prop. Options without a `groupId` render in an\n * implicit \"ungrouped\" bucket above the first declared group.\n */\n groupId?: string;\n /**\n * Pinned options bypass the search filter and stay visible at the top of\n * the list regardless of the current query. Useful for \"+ Create new\"\n * affordances. Pinned options ignore `groupId` and render before any\n * grouped or ungrouped content. @default false\n */\n pinned?: boolean;\n}\n\n/**\n * Describes a single group rendered above its matching options.\n * Indentation of nested rows (e.g. price under product) is not built in —\n * consumers control it via `renderOption` styling.\n */\nexport interface AutocompleteGroup {\n /** Stable identifier referenced by `option.groupId`. */\n id: string;\n /**\n * Group heading text. Used as the group's accessible name and matched\n * against the search query: when the query matches a group's `label`,\n * every option under that group is kept regardless of whether it\n * individually matches the per-option filter. This supports the common\n * \"heading is the searchable label, items are sub-rows\" shape (e.g.\n * heading = product name, items = prices).\n */\n label: string;\n}\n\ninterface AutocompleteBaseProps {\n label?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n helperText?: string;\n /** @default \"48\" */\n size?: AutocompleteSize;\n /** @default false */\n error?: boolean;\n errorMessage?: string;\n placeholder?: string;\n leftIcon?: React.ReactNode;\n /** @default false */\n fullWidth?: boolean;\n /** @default false */\n disabled?: boolean;\n /** @default false */\n clearable?: boolean;\n clearAriaLabel?: string;\n id?: string;\n className?: string;\n options: AutocompleteOption[];\n inputValue?: string;\n onInputChange?: (value: string) => void;\n filterFn?: (option: AutocompleteOption, query: string) => boolean;\n /** @default false */\n creatable?: boolean;\n creatableLabel?: (inputValue: string) => string;\n onCreate?: (inputValue: string) => void;\n /** @default false */\n loading?: boolean;\n loadingText?: string;\n emptyText?: string;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * Ordered list of groups. When provided, options whose `groupId` matches\n * an entry render under the corresponding heading. Groups with no visible\n * (post-filter) options collapse silently. Pinned options render above\n * everything; ungrouped options render between pinned and the first group.\n * Only one level of grouping is supported.\n */\n groups?: AutocompleteGroup[];\n /**\n * Custom renderer for group headings. The returned node is wrapped by the\n * component in an element carrying the `id` referenced by the surrounding\n * `role=\"group\"` wrapper's `aria-labelledby`, so consumers only need to\n * return visual content.\n */\n renderGroupHeading?: (group: AutocompleteGroup) => React.ReactNode;\n}\n\ninterface AutocompleteSingleProps extends AutocompleteBaseProps {\n multiple?: false;\n value?: string | null;\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n}\n\ninterface AutocompleteMultiProps extends AutocompleteBaseProps {\n multiple: true;\n value?: string[];\n defaultValue?: string[];\n onChange?: (values: string[]) => void;\n}\n\nexport type AutocompleteProps = AutocompleteSingleProps | AutocompleteMultiProps;\n\nconst CONTAINER_HEIGHT: Record<AutocompleteSize, string> = {\n \"48\": \"min-h-12\",\n \"40\": \"min-h-10\",\n \"32\": \"min-h-8\",\n};\n\nconst INPUT_SIZE_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nconst PADDING_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"px-4 py-1.5 gap-3\",\n \"40\": \"px-4 py-1 gap-3\",\n \"32\": \"px-3 py-1 gap-2\",\n};\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"Autocomplete: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A combobox input with single- or multi-select, optional async loading, and\n * native support for grouped + pinned options.\n *\n * - Pass `groups` plus an `options` array whose entries reference each group\n * via `groupId` to render hierarchical lists with proper `role=\"group\"` +\n * `aria-labelledby` semantics. Options without a `groupId` render above\n * the first group; options marked `pinned` render above everything and\n * bypass the search filter.\n * - Indentation of nested rows (e.g. price under product) is controlled by\n * the consumer via `renderOption` styling — there is no built-in indent.\n *\n * @example\n * ```tsx\n * <Autocomplete\n * aria-label=\"Choose a product\"\n * options={[\n * { value: \"__new__\", label: \"+ Create new product\", pinned: true },\n * { value: \"product:abc\", label: \"Demo Product\", groupId: \"recent\" },\n * ]}\n * groups={[{ id: \"recent\", label: \"Recent products\" }]}\n * onChange={handleSelect}\n * />\n * ```\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Conditional JSX branches in the render template\nexport const Autocomplete = React.forwardRef<HTMLInputElement, AutocompleteProps>((props, ref) => {\n const {\n label,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n placeholder,\n leftIcon,\n fullWidth = false,\n disabled = false,\n clearable = false,\n clearAriaLabel = \"Clear\",\n className,\n loading = false,\n loadingText,\n emptyText = \"No results\",\n renderOption,\n renderTag,\n renderGroupHeading,\n } = props;\n\n const ac = useAutocomplete(props);\n\n React.useImperativeHandle(ref, () => ac.inputRef.current as HTMLInputElement);\n\n warnMissingAccessibleName(label, ariaLabel, ariaLabelledby);\n\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n return (\n <Popover.Root open={ac.isOpen && !disabled} onOpenChange={ac.handleOpenChange}>\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-autocomplete-root=\"\"\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={ac.inputId}\n className=\"typography-description-12px-semibold px-1 pt-1 pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <Popover.Anchor asChild>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: Container delegates click to the inner input */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: Keyboard interaction is handled by the inner combobox input */}\n <div\n className={cn(\n \"flex flex-wrap items-center overflow-hidden rounded-sm border bg-neutral-alphas-100 has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n ac.isOpen && !error && !disabled && \"border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n PADDING_CLASSES[size],\n disabled && \"opacity-50\",\n )}\n onClick={ac.handleContainerClick}\n >\n {leftIcon && (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n {leftIcon}\n </div>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-1.5\">\n {ac.isMulti &&\n ac.selectedMultiOptions.map((opt) => (\n <AutocompleteTag\n key={opt.value}\n option={opt}\n disabled={disabled}\n onRemove={() => ac.toggleMulti(opt.value)}\n renderTag={renderTag}\n />\n ))}\n\n <input\n ref={ac.inputRef}\n id={ac.inputId}\n role=\"combobox\"\n type=\"text\"\n disabled={disabled}\n aria-expanded={ac.isOpen}\n aria-controls={ac.isOpen ? ac.listboxId : undefined}\n aria-activedescendant={ac.activeDescendantId}\n aria-autocomplete=\"list\"\n aria-describedby={bottomText ? ac.helperTextId : undefined}\n aria-invalid={error || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n autoComplete=\"off\"\n placeholder={\n ac.isMulti && ac.selectedMultiValues.length > 0 ? undefined : placeholder\n }\n value={ac.displayInputValue}\n onChange={ac.handleInputChange}\n onKeyDown={ac.handleKeyDown}\n onFocus={ac.handleFocus}\n onBlur={ac.handleBlur}\n className={cn(\n \"min-w-[40px] flex-1 truncate bg-transparent text-content-primary no-underline placeholder:text-content-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n )}\n />\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {loading && <SpinnerIcon className=\"size-4 animate-spin text-content-secondary\" />}\n {clearable && ac.hasClearableValue && !disabled && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={clearAriaLabel}\n className=\"flex size-5 shrink-0 cursor-pointer items-center justify-center text-content-secondary hover:text-content-primary active:scale-95\"\n onClick={ac.handleClear}\n >\n <CloseIcon className=\"size-4\" />\n </button>\n )}\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n <ChevronDownIcon\n className={cn(\"size-5 transition-transform\", ac.isOpen && \"rotate-180\")}\n />\n </div>\n </div>\n </div>\n </Popover.Anchor>\n\n <Popover.Portal>\n <Popover.Content\n sideOffset={4}\n collisionPadding={FLOATING_CONTENT_COLLISION_PADDING}\n onOpenAutoFocus={(e) => e.preventDefault()}\n onCloseAutoFocus={(e) => e.preventDefault()}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\" }}\n className={cn(\n \"w-max min-w-(--radix-popper-anchor-width) max-w-(--radix-popover-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-background-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n )}\n >\n <div\n ref={ac.listRef}\n id={ac.listboxId}\n role=\"listbox\"\n aria-label={label ?? ariaLabel ?? \"Options\"}\n aria-multiselectable={ac.isMulti || undefined}\n className=\"max-h-60 overflow-y-auto p-1\"\n >\n <AutocompleteDropdownContent\n loading={loading}\n loadingText={loadingText}\n emptyText={emptyText}\n visibleOptions={ac.visibleOptions}\n visibleSections={ac.visibleSections}\n createOption={ac.createOption}\n listboxId={ac.listboxId}\n activeIndex={ac.activeIndex}\n isMulti={ac.isMulti}\n selectedMultiValues={ac.selectedMultiValues}\n selectedValue={ac.selectedValue}\n onSelect={ac.handleSelect}\n onMouseEnter={ac.setActiveIndex}\n renderOption={renderOption}\n renderGroupHeading={renderGroupHeading}\n />\n </div>\n </Popover.Content>\n </Popover.Portal>\n\n {bottomText && (\n <p\n id={ac.helperTextId}\n className={cn(\n \"typography-description-12px-regular px-2 pt-1 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {bottomText}\n </p>\n )}\n </div>\n </Popover.Root>\n );\n});\n\nAutocomplete.displayName = \"Autocomplete\";\n"],"names":["Popover"],"mappings":"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"}
1
+ {"version":3,"file":"Autocomplete.mjs","sources":["../../../src/components/Autocomplete/Autocomplete.tsx"],"sourcesContent":["import * as Popover from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"@/utils/floatingContentCollisionPadding\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\nimport { AutocompleteDropdownContent } from \"./AutocompleteDropdownContent\";\nimport { AutocompleteTag } from \"./AutocompleteTag\";\nimport { useAutocomplete } from \"./useAutocomplete\";\n\nexport type AutocompleteSize = \"48\" | \"40\" | \"32\";\n\nexport interface AutocompleteOption {\n /** Unique value identifying the option. Returned via `onChange`. */\n value: string;\n /** Visible label. Falls back to `value` when omitted. */\n label?: string;\n /** When `true`, the option renders but cannot be selected. @default false */\n disabled?: boolean;\n /**\n * ID of the group this option belongs to. Must match an entry in the\n * component's `groups` prop. Options without a `groupId` render in an\n * implicit \"ungrouped\" bucket above the first declared group.\n */\n groupId?: string;\n /**\n * Pinned options bypass the search filter and stay visible at the top of\n * the list regardless of the current query. Useful for \"+ Create new\"\n * affordances. Pinned options ignore `groupId` and render before any\n * grouped or ungrouped content. @default false\n */\n pinned?: boolean;\n}\n\n/**\n * Describes a single group rendered above its matching options.\n * Indentation of nested rows (e.g. price under product) is not built in —\n * consumers control it via `renderOption` styling.\n */\nexport interface AutocompleteGroup {\n /** Stable identifier referenced by `option.groupId`. */\n id: string;\n /**\n * Group heading text. Used as the group's accessible name and matched\n * against the search query: when the query matches a group's `label`,\n * every option under that group is kept regardless of whether it\n * individually matches the per-option filter. This supports the common\n * \"heading is the searchable label, items are sub-rows\" shape (e.g.\n * heading = product name, items = prices).\n */\n label: string;\n}\n\ninterface AutocompleteBaseProps {\n label?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n helperText?: string;\n /** @default \"48\" */\n size?: AutocompleteSize;\n /** @default false */\n error?: boolean;\n errorMessage?: string;\n placeholder?: string;\n leftIcon?: React.ReactNode;\n /** @default false */\n fullWidth?: boolean;\n /** @default false */\n disabled?: boolean;\n /** @default false */\n clearable?: boolean;\n clearAriaLabel?: string;\n id?: string;\n className?: string;\n options: AutocompleteOption[];\n inputValue?: string;\n onInputChange?: (value: string) => void;\n filterFn?: (option: AutocompleteOption, query: string) => boolean;\n /** @default false */\n creatable?: boolean;\n creatableLabel?: (inputValue: string) => string;\n onCreate?: (inputValue: string) => void;\n /** @default false */\n loading?: boolean;\n loadingText?: string;\n emptyText?: string;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * Ordered list of groups. When provided, options whose `groupId` matches\n * an entry render under the corresponding heading. Groups with no visible\n * (post-filter) options collapse silently. Pinned options render above\n * everything; ungrouped options render between pinned and the first group.\n * Only one level of grouping is supported.\n */\n groups?: AutocompleteGroup[];\n /**\n * Custom renderer for group headings. The returned node is wrapped by the\n * component in an element carrying the `id` referenced by the surrounding\n * `role=\"group\"` wrapper's `aria-labelledby`, so consumers only need to\n * return visual content.\n */\n renderGroupHeading?: (group: AutocompleteGroup) => React.ReactNode;\n}\n\ninterface AutocompleteSingleProps extends AutocompleteBaseProps {\n multiple?: false;\n value?: string | null;\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n}\n\ninterface AutocompleteMultiProps extends AutocompleteBaseProps {\n multiple: true;\n value?: string[];\n defaultValue?: string[];\n onChange?: (values: string[]) => void;\n}\n\nexport type AutocompleteProps = AutocompleteSingleProps | AutocompleteMultiProps;\n\nconst CONTAINER_HEIGHT: Record<AutocompleteSize, string> = {\n \"48\": \"min-h-12\",\n \"40\": \"min-h-10\",\n \"32\": \"min-h-8\",\n};\n\nconst INPUT_SIZE_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nconst PADDING_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"px-4 py-1.5 gap-3\",\n \"40\": \"px-4 py-1 gap-3\",\n \"32\": \"px-3 py-1 gap-2\",\n};\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"Autocomplete: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A combobox input with single- or multi-select, optional async loading, and\n * native support for grouped + pinned options.\n *\n * - Pass `groups` plus an `options` array whose entries reference each group\n * via `groupId` to render hierarchical lists with proper `role=\"group\"` +\n * `aria-labelledby` semantics. Options without a `groupId` render above\n * the first group; options marked `pinned` render above everything and\n * bypass the search filter.\n * - Indentation of nested rows (e.g. price under product) is controlled by\n * the consumer via `renderOption` styling — there is no built-in indent.\n *\n * @example\n * ```tsx\n * <Autocomplete\n * aria-label=\"Choose a product\"\n * options={[\n * { value: \"__new__\", label: \"+ Create new product\", pinned: true },\n * { value: \"product:abc\", label: \"Demo Product\", groupId: \"recent\" },\n * ]}\n * groups={[{ id: \"recent\", label: \"Recent products\" }]}\n * onChange={handleSelect}\n * />\n * ```\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Conditional JSX branches in the render template\nexport const Autocomplete = React.forwardRef<HTMLInputElement, AutocompleteProps>((props, ref) => {\n const {\n label,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n placeholder,\n leftIcon,\n fullWidth = false,\n disabled = false,\n clearable = false,\n clearAriaLabel = \"Clear\",\n className,\n loading = false,\n loadingText,\n emptyText = \"No results\",\n renderOption,\n renderTag,\n renderGroupHeading,\n } = props;\n\n const ac = useAutocomplete(props);\n\n React.useImperativeHandle(ref, () => ac.inputRef.current as HTMLInputElement);\n\n warnMissingAccessibleName(label, ariaLabel, ariaLabelledby);\n\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n return (\n <Popover.Root open={ac.isOpen && !disabled} onOpenChange={ac.handleOpenChange}>\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-autocomplete-root=\"\"\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={ac.inputId}\n className=\"typography-description-12px-semibold px-1 pt-1 pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <Popover.Anchor asChild>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: Container delegates click to the inner input */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: Keyboard interaction is handled by the inner combobox input */}\n <div\n className={cn(\n \"relative flex flex-wrap items-center overflow-hidden rounded-sm border bg-neutral-alphas-100 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] has-focus-visible:outline-none has-focus-visible:after:shadow-focus-ring motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n ac.isOpen && !error && !disabled && \"border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n PADDING_CLASSES[size],\n disabled && \"opacity-50\",\n )}\n onClick={ac.handleContainerClick}\n >\n {leftIcon && (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n {leftIcon}\n </div>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-1.5\">\n {ac.isMulti &&\n ac.selectedMultiOptions.map((opt) => (\n <AutocompleteTag\n key={opt.value}\n option={opt}\n disabled={disabled}\n onRemove={() => ac.toggleMulti(opt.value)}\n renderTag={renderTag}\n />\n ))}\n\n <input\n ref={ac.inputRef}\n id={ac.inputId}\n role=\"combobox\"\n type=\"text\"\n disabled={disabled}\n aria-expanded={ac.isOpen}\n aria-controls={ac.isOpen ? ac.listboxId : undefined}\n aria-activedescendant={ac.activeDescendantId}\n aria-autocomplete=\"list\"\n aria-describedby={bottomText ? ac.helperTextId : undefined}\n aria-invalid={error || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n autoComplete=\"off\"\n placeholder={\n ac.isMulti && ac.selectedMultiValues.length > 0 ? undefined : placeholder\n }\n value={ac.displayInputValue}\n onChange={ac.handleInputChange}\n onKeyDown={ac.handleKeyDown}\n onFocus={ac.handleFocus}\n onBlur={ac.handleBlur}\n className={cn(\n \"min-w-[40px] flex-1 truncate bg-transparent text-content-primary no-underline placeholder:text-content-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n )}\n />\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {loading && <SpinnerIcon className=\"size-4 animate-spin text-content-secondary\" />}\n {clearable && ac.hasClearableValue && !disabled && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={clearAriaLabel}\n className=\"flex size-5 shrink-0 cursor-pointer items-center justify-center text-content-secondary hover:text-content-primary active:scale-95\"\n onClick={ac.handleClear}\n >\n <CloseIcon className=\"size-4\" />\n </button>\n )}\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n <ChevronDownIcon\n className={cn(\"size-5 transition-transform\", ac.isOpen && \"rotate-180\")}\n />\n </div>\n </div>\n </div>\n </Popover.Anchor>\n\n <Popover.Portal>\n <Popover.Content\n sideOffset={4}\n collisionPadding={FLOATING_CONTENT_COLLISION_PADDING}\n onOpenAutoFocus={(e) => e.preventDefault()}\n onCloseAutoFocus={(e) => e.preventDefault()}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\" }}\n className={cn(\n \"w-max min-w-(--radix-popper-anchor-width) max-w-(--radix-popover-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-background-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n )}\n >\n <div\n ref={ac.listRef}\n id={ac.listboxId}\n role=\"listbox\"\n aria-label={label ?? ariaLabel ?? \"Options\"}\n aria-multiselectable={ac.isMulti || undefined}\n className=\"max-h-60 overflow-y-auto p-1\"\n >\n <AutocompleteDropdownContent\n loading={loading}\n loadingText={loadingText}\n emptyText={emptyText}\n visibleOptions={ac.visibleOptions}\n visibleSections={ac.visibleSections}\n createOption={ac.createOption}\n listboxId={ac.listboxId}\n activeIndex={ac.activeIndex}\n isMulti={ac.isMulti}\n selectedMultiValues={ac.selectedMultiValues}\n selectedValue={ac.selectedValue}\n onSelect={ac.handleSelect}\n onMouseEnter={ac.setActiveIndex}\n renderOption={renderOption}\n renderGroupHeading={renderGroupHeading}\n />\n </div>\n </Popover.Content>\n </Popover.Portal>\n\n {bottomText && (\n <p\n id={ac.helperTextId}\n className={cn(\n \"typography-description-12px-regular px-2 pt-1 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {bottomText}\n </p>\n )}\n </div>\n </Popover.Root>\n );\n});\n\nAutocomplete.displayName = \"Autocomplete\";\n"],"names":["Popover"],"mappings":"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"}
@@ -5,19 +5,20 @@ import * as React from "react";
5
5
  import { cn } from "../../utils/cn.mjs";
6
6
  const badgeVariants = {
7
7
  variant: {
8
- default: "bg-neutral-alphas-50 text-content-primary",
8
+ default: "bg-buttons-secondary-default text-content-primary",
9
9
  dark: "bg-neutral-alphas-600 text-content-always-white",
10
- success: "bg-success-surface text-success-content",
11
- warning: "bg-warning-surface text-warning-content",
12
- error: "bg-error-surface text-error-content",
13
- special: "bg-neutral-alphas-50 text-content-primary",
14
- info: "bg-info-surface text-info-content",
10
+ success: "bg-buttons-secondary-default text-content-primary",
11
+ warning: "bg-buttons-secondary-default text-content-primary",
12
+ error: "bg-buttons-secondary-default text-content-primary",
13
+ special: "bg-buttons-secondary-default text-content-primary",
14
+ info: "bg-buttons-secondary-default text-content-primary",
15
15
  successColour: "bg-success-surface text-content-primary",
16
16
  warningColour: "bg-warning-surface text-content-primary",
17
17
  errorColour: "bg-error-surface text-content-primary",
18
18
  infoColour: "bg-info-surface text-content-primary",
19
19
  aiGenerated: "bg-buttons-always-white-default text-content-always-black",
20
20
  negative: "bg-buttons-secondary-negative-default text-content-primary-inverted",
21
+ alwaysWhite: "bg-buttons-secondary-negative-default text-content-always-white",
21
22
  online: "bg-background-primary text-brand-primary-default",
22
23
  brand: "bg-brand-primary-default text-content-always-black",
23
24
  pink: "bg-brand-secondary-default text-content-always-black",
@@ -25,7 +26,7 @@ const badgeVariants = {
25
26
  pinkLight: "bg-brand-secondary-muted text-content-primary"
26
27
  },
27
28
  dotColor: {
28
- default: "bg-content-primary",
29
+ default: "bg-icons-primary",
29
30
  dark: "bg-content-always-white",
30
31
  success: "bg-success-content",
31
32
  warning: "bg-warning-content",
@@ -38,6 +39,7 @@ const badgeVariants = {
38
39
  infoColour: "bg-info-content",
39
40
  aiGenerated: "bg-content-always-black",
40
41
  negative: "bg-icons-primary-inverted",
42
+ alwaysWhite: "bg-brand-primary-default",
41
43
  online: "bg-brand-primary-default",
42
44
  brand: "bg-content-always-black",
43
45
  pink: "bg-content-always-black",
@@ -65,7 +67,7 @@ const Badge = React.forwardRef(
65
67
  "data-testid": "badge",
66
68
  className: cn(
67
69
  // Base styles
68
- "typography-description-12px-semibold inline-flex h-6 min-w-0 items-center gap-2 rounded-full px-2",
70
+ "typography-badge-badgecaps inline-flex h-5 min-w-0 items-center gap-1 rounded-full px-2",
69
71
  // Variant styles
70
72
  badgeVariants.variant[variant],
71
73
  // Interactive
@@ -79,7 +81,7 @@ const Badge = React.forwardRef(
79
81
  leftIcon && /* @__PURE__ */ jsx(
80
82
  "span",
81
83
  {
82
- className: "flex shrink-0 items-center justify-center [&>svg]:size-3",
84
+ className: "flex shrink-0 items-center justify-center [&>svg]:size-2.5",
83
85
  "aria-hidden": "true",
84
86
  children: leftIcon
85
87
  }
@@ -91,11 +93,11 @@ const Badge = React.forwardRef(
91
93
  "aria-hidden": "true"
92
94
  }
93
95
  ),
94
- asChild ? /* @__PURE__ */ jsx(Slottable, { children }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children }),
96
+ asChild ? /* @__PURE__ */ jsx(Slottable, { children }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate [text-box:trim-both_cap_alphabetic]", children }),
95
97
  rightIcon && /* @__PURE__ */ jsx(
96
98
  "span",
97
99
  {
98
- className: "flex shrink-0 items-center justify-center [&>svg]:size-3",
100
+ className: "flex shrink-0 items-center justify-center [&>svg]:size-2.5",
99
101
  "aria-hidden": "true",
100
102
  children: rightIcon
101
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.mjs","sources":["../../../src/components/Badge/Badge.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst badgeVariants = {\n variant: {\n default: \"bg-neutral-alphas-50 text-content-primary\",\n dark: \"bg-neutral-alphas-600 text-content-always-white\",\n success: \"bg-success-surface text-success-content\",\n warning: \"bg-warning-surface text-warning-content\",\n error: \"bg-error-surface text-error-content\",\n special: \"bg-neutral-alphas-50 text-content-primary\",\n info: \"bg-info-surface text-info-content\",\n successColour: \"bg-success-surface text-content-primary\",\n warningColour: \"bg-warning-surface text-content-primary\",\n errorColour: \"bg-error-surface text-content-primary\",\n infoColour: \"bg-info-surface text-content-primary\",\n aiGenerated: \"bg-buttons-always-white-default text-content-always-black\",\n negative: \"bg-buttons-secondary-negative-default text-content-primary-inverted\",\n online: \"bg-background-primary text-brand-primary-default\",\n brand: \"bg-brand-primary-default text-content-always-black\",\n pink: \"bg-brand-secondary-default text-content-always-black\",\n brandLight: \"bg-brand-primary-muted text-content-primary\",\n pinkLight: \"bg-brand-secondary-muted text-content-primary\",\n },\n dotColor: {\n default: \"bg-content-primary\",\n dark: \"bg-content-always-white\",\n success: \"bg-success-content\",\n warning: \"bg-warning-content\",\n error: \"bg-error-content\",\n special: \"bg-special-default\",\n info: \"bg-info-content\",\n successColour: \"bg-success-content\",\n warningColour: \"bg-warning-content\",\n errorColour: \"bg-error-content\",\n infoColour: \"bg-info-content\",\n aiGenerated: \"bg-content-always-black\",\n negative: \"bg-icons-primary-inverted\",\n online: \"bg-brand-primary-default\",\n brand: \"bg-content-always-black\",\n pink: \"bg-content-always-black\",\n brandLight: \"bg-content-always-black\",\n pinkLight: \"bg-content-always-black\",\n },\n} as const;\n\n/** Visual style variant of the badge. */\nexport type BadgeVariant =\n | \"default\"\n | \"dark\"\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"special\"\n | \"info\"\n | \"successColour\"\n | \"warningColour\"\n | \"errorColour\"\n | \"infoColour\"\n | \"aiGenerated\"\n | \"negative\"\n | \"online\"\n | \"brand\"\n | \"pink\"\n | \"brandLight\"\n | \"pinkLight\";\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Visual style variant of the badge. @default \"default\" */\n variant?: BadgeVariant;\n /** Whether to show a coloured status dot at the leading edge. @default true */\n leftDot?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Merge props onto a child element instead of rendering a `<span>`. @default false */\n asChild?: boolean;\n}\n\n/**\n * A small inline label for status, category, or metadata information.\n *\n * @example\n * ```tsx\n * <Badge variant=\"success\">Active</Badge>\n * ```\n */\nexport const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n (\n {\n className,\n variant = \"default\",\n leftDot = true,\n leftIcon,\n rightIcon,\n asChild = false,\n onClick,\n children,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"span\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"badge\"\n className={cn(\n // Base styles\n \"typography-description-12px-semibold inline-flex h-6 min-w-0 items-center gap-2 rounded-full px-2\",\n // Variant styles\n badgeVariants.variant[variant],\n // Interactive\n onClick && \"cursor-pointer\",\n // Manual CSS overrides\n className,\n )}\n onClick={onClick}\n {...props}\n >\n {leftIcon && (\n <span\n className=\"flex shrink-0 items-center justify-center [&>svg]:size-3\"\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n )}\n {leftDot && (\n <span\n className={cn(\"size-1 shrink-0 rounded-full\", badgeVariants.dotColor[variant])}\n aria-hidden=\"true\"\n />\n )}\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <span className=\"min-w-0 truncate\">{children}</span>\n )}\n {rightIcon && (\n <span\n className=\"flex shrink-0 items-center justify-center [&>svg]:size-3\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nBadge.displayName = \"Badge\";\n"],"names":[],"mappings":";;;;;AAIA,MAAM,gBAAgB;AAAA,EACpB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAAA,EAEb,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AA4CO,MAAM,QAAQ,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,OAAO,UAAU,OAAO;AAE9B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA;AAAA,UAET;AAAA;AAAA,UAEA,cAAc,QAAQ,OAAO;AAAA;AAAA,UAE7B,WAAW;AAAA;AAAA,UAEX;AAAA,QAAA;AAAA,QAEF;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,YACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,eAAY;AAAA,cAEX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGf,8BACE,WAAA,EAAW,SAAA,CAAS,IAErB,oBAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,UAE9C,aACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,eAAY;AAAA,cAEX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;"}
1
+ {"version":3,"file":"Badge.mjs","sources":["../../../src/components/Badge/Badge.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn.ts\";\n\nconst badgeVariants = {\n variant: {\n default: \"bg-buttons-secondary-default text-content-primary\",\n dark: \"bg-neutral-alphas-600 text-content-always-white\",\n success: \"bg-buttons-secondary-default text-content-primary\",\n warning: \"bg-buttons-secondary-default text-content-primary\",\n error: \"bg-buttons-secondary-default text-content-primary\",\n special: \"bg-buttons-secondary-default text-content-primary\",\n info: \"bg-buttons-secondary-default text-content-primary\",\n successColour: \"bg-success-surface text-content-primary\",\n warningColour: \"bg-warning-surface text-content-primary\",\n errorColour: \"bg-error-surface text-content-primary\",\n infoColour: \"bg-info-surface text-content-primary\",\n aiGenerated: \"bg-buttons-always-white-default text-content-always-black\",\n negative: \"bg-buttons-secondary-negative-default text-content-primary-inverted\",\n alwaysWhite: \"bg-buttons-secondary-negative-default text-content-always-white\",\n online: \"bg-background-primary text-brand-primary-default\",\n brand: \"bg-brand-primary-default text-content-always-black\",\n pink: \"bg-brand-secondary-default text-content-always-black\",\n brandLight: \"bg-brand-primary-muted text-content-primary\",\n pinkLight: \"bg-brand-secondary-muted text-content-primary\",\n },\n dotColor: {\n default: \"bg-icons-primary\",\n dark: \"bg-content-always-white\",\n success: \"bg-success-content\",\n warning: \"bg-warning-content\",\n error: \"bg-error-content\",\n special: \"bg-special-default\",\n info: \"bg-info-content\",\n successColour: \"bg-success-content\",\n warningColour: \"bg-warning-content\",\n errorColour: \"bg-error-content\",\n infoColour: \"bg-info-content\",\n aiGenerated: \"bg-content-always-black\",\n negative: \"bg-icons-primary-inverted\",\n alwaysWhite: \"bg-brand-primary-default\",\n online: \"bg-brand-primary-default\",\n brand: \"bg-content-always-black\",\n pink: \"bg-content-always-black\",\n brandLight: \"bg-content-always-black\",\n pinkLight: \"bg-content-always-black\",\n },\n} as const;\n\n/** Visual style variant of the badge. */\nexport type BadgeVariant =\n | \"default\"\n | \"dark\"\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"special\"\n | \"info\"\n | \"successColour\"\n | \"warningColour\"\n | \"errorColour\"\n | \"infoColour\"\n | \"aiGenerated\"\n | \"negative\"\n | \"alwaysWhite\"\n | \"online\"\n | \"brand\"\n | \"pink\"\n | \"brandLight\"\n | \"pinkLight\";\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Visual style variant of the badge. @default \"default\" */\n variant?: BadgeVariant;\n /** Whether to show a coloured status dot at the leading edge. @default true */\n leftDot?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Merge props onto a child element instead of rendering a `<span>`. @default false */\n asChild?: boolean;\n}\n\n/**\n * A small inline label for status, category, or metadata information.\n *\n * @example\n * ```tsx\n * <Badge variant=\"success\">Active</Badge>\n * ```\n */\nexport const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n (\n {\n className,\n variant = \"default\",\n leftDot = true,\n leftIcon,\n rightIcon,\n asChild = false,\n onClick,\n children,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"span\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"badge\"\n className={cn(\n // Base styles\n \"typography-badge-badgecaps inline-flex h-5 min-w-0 items-center gap-1 rounded-full px-2\",\n // Variant styles\n badgeVariants.variant[variant],\n // Interactive\n onClick && \"cursor-pointer\",\n // Manual CSS overrides\n className,\n )}\n onClick={onClick}\n {...props}\n >\n {leftIcon && (\n <span\n className=\"flex shrink-0 items-center justify-center [&>svg]:size-2.5\"\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n )}\n {leftDot && (\n <span\n className={cn(\"size-1 shrink-0 rounded-full\", badgeVariants.dotColor[variant])}\n aria-hidden=\"true\"\n />\n )}\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <span className=\"min-w-0 truncate [text-box:trim-both_cap_alphabetic]\">{children}</span>\n )}\n {rightIcon && (\n <span\n className=\"flex shrink-0 items-center justify-center [&>svg]:size-2.5\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nBadge.displayName = \"Badge\";\n"],"names":[],"mappings":";;;;;AAIA,MAAM,gBAAgB;AAAA,EACpB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,UAAU;AAAA,IACV,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAAA,EAEb,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,UAAU;AAAA,IACV,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AA6CO,MAAM,QAAQ,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,OAAO,UAAU,OAAO;AAE9B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA;AAAA,UAET;AAAA;AAAA,UAEA,cAAc,QAAQ,OAAO;AAAA;AAAA,UAE7B,WAAW;AAAA;AAAA,UAEX;AAAA,QAAA;AAAA,QAEF;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,YACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,eAAY;AAAA,cAEX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGf,8BACE,WAAA,EAAW,SAAA,CAAS,IAErB,oBAAC,QAAA,EAAK,WAAU,wDAAwD,SAAA,CAAS;AAAA,UAElF,aACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,eAAY;AAAA,cAEX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;"}
@@ -97,7 +97,7 @@ function BannerGuideBody({
97
97
  {
98
98
  variant: GUIDE_BADGE_VARIANT[appStoreVariant],
99
99
  leftDot: false,
100
- className: "typography-badge-badgecaps self-start",
100
+ className: "self-start",
101
101
  children: eyebrow
102
102
  }
103
103
  ),