@fanvue/ui 1.14.0 → 1.15.0

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 (31) hide show
  1. package/dist/cjs/components/Badge/Badge.cjs +4 -0
  2. package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
  3. package/dist/cjs/components/Chip/Chip.cjs +2 -1
  4. package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
  5. package/dist/cjs/components/Pill/Pill.cjs +13 -1
  6. package/dist/cjs/components/Pill/Pill.cjs.map +1 -1
  7. package/dist/cjs/components/Skeleton/Skeleton.cjs +61 -0
  8. package/dist/cjs/components/Skeleton/Skeleton.cjs.map +1 -0
  9. package/dist/cjs/components/TextArea/TextArea.cjs +1 -1
  10. package/dist/cjs/components/TextArea/TextArea.cjs.map +1 -1
  11. package/dist/cjs/components/TextField/TextField.cjs +2 -2
  12. package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
  13. package/dist/cjs/index.cjs +2 -0
  14. package/dist/cjs/index.cjs.map +1 -1
  15. package/dist/components/Badge/Badge.mjs +4 -0
  16. package/dist/components/Badge/Badge.mjs.map +1 -1
  17. package/dist/components/Chip/Chip.mjs +2 -1
  18. package/dist/components/Chip/Chip.mjs.map +1 -1
  19. package/dist/components/Pill/Pill.mjs +13 -1
  20. package/dist/components/Pill/Pill.mjs.map +1 -1
  21. package/dist/components/Skeleton/Skeleton.mjs +44 -0
  22. package/dist/components/Skeleton/Skeleton.mjs.map +1 -0
  23. package/dist/components/TextArea/TextArea.mjs +1 -1
  24. package/dist/components/TextArea/TextArea.mjs.map +1 -1
  25. package/dist/components/TextField/TextField.mjs +2 -2
  26. package/dist/components/TextField/TextField.mjs.map +1 -1
  27. package/dist/index.d.ts +41 -0
  28. package/dist/index.mjs +2 -0
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/styles/theme.css +27 -0
  31. package/package.json +1 -1
@@ -60,6 +60,7 @@ const Badge = React__namespace.forwardRef(
60
60
  leftIcon,
61
61
  rightIcon,
62
62
  asChild = false,
63
+ onClick,
63
64
  children,
64
65
  ...props
65
66
  }, ref) => {
@@ -74,9 +75,12 @@ const Badge = React__namespace.forwardRef(
74
75
  "typography-semibold-body-sm inline-flex h-5 items-center gap-2 rounded-full px-2",
75
76
  // Variant styles
76
77
  badgeVariants.variant[variant],
78
+ // Interactive
79
+ onClick && "cursor-pointer",
77
80
  // Manual CSS overrides
78
81
  className
79
82
  ),
83
+ onClick,
80
84
  ...props,
81
85
  children: [
82
86
  leftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-3", "aria-hidden": "true", children: leftIcon }),
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.cjs","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-100 text-foreground-secondary\",\n dark: \"bg-neutral-50 text-foreground-inverse dark:text-foreground-onaccentinverse\",\n success: \"bg-neutral-100 text-foreground-secondary\",\n warning: \"bg-neutral-100 text-foreground-secondary\",\n error: \"bg-neutral-100 text-foreground-secondary\",\n special: \"bg-neutral-100 text-foreground-secondary\",\n info: \"bg-neutral-100 text-foreground-secondary\",\n online: \"bg-neutral-100 text-brand-accent-default\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n pink: \"bg-brand-secondary-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-onaccent\",\n },\n dotColor: {\n default: \"bg-foreground-onaccent\",\n dark: \"bg-foreground-inverse dark:bg-foreground-onaccentinverse\",\n success: \"bg-success-default\",\n warning: \"bg-warning-default\",\n error: \"bg-error-default\",\n special: \"bg-special-default\",\n info: \"bg-info-default\",\n online: \"bg-brand-accent-default\",\n brand: \"bg-foreground-onaccent\",\n pink: \"bg-foreground-onaccent\",\n brandLight: \"bg-foreground-onaccent\",\n pinkLight: \"bg-foreground-onaccent\",\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 | \"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 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-semibold-body-sm inline-flex h-5 items-center gap-2 rounded-full px-2\",\n // Variant styles\n badgeVariants.variant[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {leftIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\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 <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nBadge.displayName = \"Badge\";\n"],"names":["React","Slot","jsxs","cn","jsx","Slottable"],"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,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,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AAsCO,MAAM,QAAQA,iBAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,OAAO,UAAUC,UAAAA,OAAO;AAE9B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA;AAAAA,UAET;AAAA;AAAA,UAEA,cAAc,QAAQ,OAAO;AAAA;AAAA,UAE7B;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,2CACE,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UACH;AAAA,UAED,WACCC,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWD,GAAAA,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGhBC,+BAACC,UAAAA,aAAW,UAAS;AAAA,UACpB,aACCD,2BAAAA,IAAC,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;;"}
1
+ {"version":3,"file":"Badge.cjs","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-100 text-foreground-secondary\",\n dark: \"bg-neutral-50 text-foreground-inverse dark:text-foreground-onaccentinverse\",\n success: \"bg-neutral-100 text-foreground-secondary\",\n warning: \"bg-neutral-100 text-foreground-secondary\",\n error: \"bg-neutral-100 text-foreground-secondary\",\n special: \"bg-neutral-100 text-foreground-secondary\",\n info: \"bg-neutral-100 text-foreground-secondary\",\n online: \"bg-neutral-100 text-brand-accent-default\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n pink: \"bg-brand-secondary-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-onaccent\",\n },\n dotColor: {\n default: \"bg-foreground-onaccent\",\n dark: \"bg-foreground-inverse dark:bg-foreground-onaccentinverse\",\n success: \"bg-success-default\",\n warning: \"bg-warning-default\",\n error: \"bg-error-default\",\n special: \"bg-special-default\",\n info: \"bg-info-default\",\n online: \"bg-brand-accent-default\",\n brand: \"bg-foreground-onaccent\",\n pink: \"bg-foreground-onaccent\",\n brandLight: \"bg-foreground-onaccent\",\n pinkLight: \"bg-foreground-onaccent\",\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 | \"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-semibold-body-sm inline-flex h-5 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 className=\"flex size-3\" aria-hidden=\"true\">\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 <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nBadge.displayName = \"Badge\";\n"],"names":["React","Slot","jsxs","cn","jsx","Slottable"],"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,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,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AAsCO,MAAM,QAAQA,iBAAM;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,UAAUC,UAAAA,OAAO;AAE9B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA;AAAAA,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,2CACE,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UACH;AAAA,UAED,WACCC,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWD,GAAAA,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGhBC,+BAACC,UAAAA,aAAW,UAAS;AAAA,UACpB,aACCD,2BAAAA,IAAC,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;;"}
@@ -57,7 +57,8 @@ const Chip = React__namespace.forwardRef(
57
57
  isDark && "bg-neutral-50 text-foreground-onaccentinverse",
58
58
  !isDark && selected && "bg-brand-accent-muted text-neutral-400",
59
59
  !isDark && !selected && "bg-neutral-100 text-neutral-400",
60
- // Hover
60
+ // Interactive
61
+ isInteractive && !disabled && "cursor-pointer",
61
62
  isInteractive && !disabled && !isDark && !selected && "hover:bg-brand-accent-muted active:bg-brand-accent-muted",
62
63
  // Focus
63
64
  "focus-visible:shadow-focus-ring focus-visible:outline-none",
@@ -1 +1 @@
1
- {"version":3,"file":"Chip.cjs","sources":["../../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\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 /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-semibold-body-sm relative inline-flex items-center justify-center gap-2 whitespace-nowrap px-3 motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-lg\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-50 text-foreground-onaccentinverse\",\n !isDark && selected && \"bg-brand-accent-muted text-neutral-400\",\n !isDark && !selected && \"bg-neutral-100 text-neutral-400\",\n // Hover\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n \"hover:bg-brand-accent-muted active:bg-brand-accent-muted\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-300\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {leftDot && <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />}\n {leftIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n {notificationLabel && (\n <span className=\"typography-semibold-body-sm absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground-default px-1 text-foreground-inverse\">\n {notificationLabel}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":["React","Slot","jsxs","cn","jsx","Slottable"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0CO,MAAM,OAAOA,iBAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAUC,UAAAA,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY;AAAA;AAAA,UAExB,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,WAAWC,2BAAAA,IAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,UACxF,YACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,UACH;AAAA,UAEFA,+BAACC,UAAAA,aAAW,UAAS;AAAA,UACpB,aACCD,2BAAAA,IAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,WACH;AAAA,UAED,qBACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,qKACb,UAAA,kBAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;;"}
1
+ {"version":3,"file":"Chip.cjs","sources":["../../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\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 /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-semibold-body-sm relative inline-flex items-center justify-center gap-2 whitespace-nowrap px-3 motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-lg\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-50 text-foreground-onaccentinverse\",\n !isDark && selected && \"bg-brand-accent-muted text-neutral-400\",\n !isDark && !selected && \"bg-neutral-100 text-neutral-400\",\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n \"hover:bg-brand-accent-muted active:bg-brand-accent-muted\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-300\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {leftDot && <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />}\n {leftIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n {notificationLabel && (\n <span className=\"typography-semibold-body-sm absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground-default px-1 text-foreground-inverse\">\n {notificationLabel}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":["React","Slot","jsxs","cn","jsx","Slottable"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0CO,MAAM,OAAOA,iBAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAUC,UAAAA,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY;AAAA;AAAA,UAExB,iBAAiB,CAAC,YAAY;AAAA,UAC9B,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,WAAWC,2BAAAA,IAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,UACxF,YACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,UACH;AAAA,UAEFA,+BAACC,UAAAA,aAAW,UAAS;AAAA,UACpB,aACCD,2BAAAA,IAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,WACH;AAAA,UAED,qBACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,qKACb,UAAA,kBAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;;"}
@@ -37,7 +37,16 @@ const pillVariants = {
37
37
  }
38
38
  };
39
39
  const Pill = React__namespace.forwardRef(
40
- ({ className, variant = "green", leftIcon, rightIcon, asChild = false, children, ...props }, ref) => {
40
+ ({
41
+ className,
42
+ variant = "green",
43
+ leftIcon,
44
+ rightIcon,
45
+ asChild = false,
46
+ onClick,
47
+ children,
48
+ ...props
49
+ }, ref) => {
41
50
  const Comp = asChild ? reactSlot.Slot : "span";
42
51
  return /* @__PURE__ */ jsxRuntime.jsxs(
43
52
  Comp,
@@ -51,9 +60,12 @@ const Pill = React__namespace.forwardRef(
51
60
  "typography-semibold-body-sm",
52
61
  // Variant styles
53
62
  pillVariants.variant[variant],
63
+ // Interactive
64
+ onClick && "cursor-pointer",
54
65
  // Manual CSS overrides
55
66
  className
56
67
  ),
68
+ onClick,
57
69
  ...props,
58
70
  children: [
59
71
  leftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex", "aria-hidden": "true", children: leftIcon }),
@@ -1 +1 @@
1
- {"version":3,"file":"Pill.cjs","sources":["../../../../src/components/Pill/Pill.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst pillVariants = {\n variant: {\n green: \"bg-success-background text-success-default\",\n grey: \"bg-neutral-100 text-foreground-secondary\",\n blue: \"bg-info-background text-info-default\",\n gold: \"bg-warning-background text-warning-default\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-default\",\n base: \"bg-neutral-solid text-foreground-inverse\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n beta: \"bg-brand-secondary-default text-foreground-onaccent\",\n error: \"bg-error-default text-error-background\",\n },\n} as const;\n\n/** Colour variant of the pill. */\nexport type PillVariant =\n | \"green\"\n | \"grey\"\n | \"blue\"\n | \"gold\"\n | \"pinkLight\"\n | \"base\"\n | \"brand\"\n | \"brandLight\"\n | \"beta\"\n | \"error\";\n\nexport interface PillProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Colour variant of the pill. @default \"green\" */\n variant?: PillVariant;\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 rounded label for categorisation, status, or tagging.\n *\n * @example\n * ```tsx\n * <Pill variant=\"brand\">New</Pill>\n * ```\n */\nexport const Pill = React.forwardRef<HTMLSpanElement, PillProps>(\n (\n { className, variant = \"green\", leftIcon, rightIcon, asChild = false, children, ...props },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"span\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"pill\"\n className={cn(\n // Base styles\n \"inline-flex items-center justify-center gap-2 rounded-full px-3 py-1\",\n // Typography\n \"typography-semibold-body-sm\",\n // Variant styles\n pillVariants.variant[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {leftIcon && (\n <span className=\"flex\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nPill.displayName = \"Pill\";\n"],"names":["React","Slot","jsxs","cn","jsx","Slottable"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAIA,MAAM,eAAe;AAAA,EACnB,SAAS;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAEX;AAkCO,MAAM,OAAOA,iBAAM;AAAA,EACxB,CACE,EAAE,WAAW,UAAU,SAAS,UAAU,WAAW,UAAU,OAAO,UAAU,GAAG,MAAA,GACnF,QACG;AACH,UAAM,OAAO,UAAUC,UAAAA,OAAO;AAE9B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA;AAAAA,UAET;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA,aAAa,QAAQ,OAAO;AAAA;AAAA,UAE5B;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,2CACE,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UACH;AAAA,UAEFC,+BAACC,UAAAA,aAAW,UAAS;AAAA,UACpB,aACCD,2BAAAA,IAAC,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;;"}
1
+ {"version":3,"file":"Pill.cjs","sources":["../../../../src/components/Pill/Pill.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst pillVariants = {\n variant: {\n green: \"bg-success-background text-success-default\",\n grey: \"bg-neutral-100 text-foreground-secondary\",\n blue: \"bg-info-background text-info-default\",\n gold: \"bg-warning-background text-warning-default\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-default\",\n base: \"bg-neutral-solid text-foreground-inverse\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n beta: \"bg-brand-secondary-default text-foreground-onaccent\",\n error: \"bg-error-default text-error-background\",\n },\n} as const;\n\n/** Colour variant of the pill. */\nexport type PillVariant =\n | \"green\"\n | \"grey\"\n | \"blue\"\n | \"gold\"\n | \"pinkLight\"\n | \"base\"\n | \"brand\"\n | \"brandLight\"\n | \"beta\"\n | \"error\";\n\nexport interface PillProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Colour variant of the pill. @default \"green\" */\n variant?: PillVariant;\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 rounded label for categorisation, status, or tagging.\n *\n * @example\n * ```tsx\n * <Pill variant=\"brand\">New</Pill>\n * ```\n */\nexport const Pill = React.forwardRef<HTMLSpanElement, PillProps>(\n (\n {\n className,\n variant = \"green\",\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=\"pill\"\n className={cn(\n // Base styles\n \"inline-flex items-center justify-center gap-2 rounded-full px-3 py-1\",\n // Typography\n \"typography-semibold-body-sm\",\n // Variant styles\n pillVariants.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 className=\"flex\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nPill.displayName = \"Pill\";\n"],"names":["React","Slot","jsxs","cn","jsx","Slottable"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAIA,MAAM,eAAe;AAAA,EACnB,SAAS;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAEX;AAkCO,MAAM,OAAOA,iBAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,OAAO,UAAUC,UAAAA,OAAO;AAE9B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA;AAAAA,UAET;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA,aAAa,QAAQ,OAAO;AAAA;AAAA,UAE5B,WAAW;AAAA;AAAA,UAEX;AAAA,QAAA;AAAA,QAEF;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,2CACE,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UACH;AAAA,UAEFC,+BAACC,UAAAA,aAAW,UAAS;AAAA,UACpB,aACCD,2BAAAA,IAAC,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;;"}
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const jsxRuntime = require("react/jsx-runtime");
5
+ const React = require("react");
6
+ const cn = require("../../utils/cn.cjs");
7
+ function _interopNamespaceDefault(e) {
8
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
9
+ if (e) {
10
+ for (const k in e) {
11
+ if (k !== "default") {
12
+ const d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: () => e[k]
16
+ });
17
+ }
18
+ }
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
24
+ const VARIANT_CLASSES = {
25
+ text: "rounded my-0.5",
26
+ circular: "rounded-full",
27
+ rectangular: "",
28
+ rounded: "rounded-lg"
29
+ };
30
+ const Skeleton = React__namespace.forwardRef(
31
+ ({ className, variant = "text", animation = "pulse", width, height, style, children, ...props }, ref) => {
32
+ const hasChildren = React__namespace.Children.count(children) > 0;
33
+ const sizeStyle = {
34
+ ...style,
35
+ ...width !== void 0 && { width: typeof width === "number" ? `${width}px` : width },
36
+ ...height !== void 0 && { height: typeof height === "number" ? `${height}px` : height }
37
+ };
38
+ return /* @__PURE__ */ jsxRuntime.jsx(
39
+ "span",
40
+ {
41
+ ref,
42
+ "aria-hidden": "true",
43
+ className: cn.cn(
44
+ "block bg-neutral-200 dark:bg-neutral-200",
45
+ VARIANT_CLASSES[variant],
46
+ variant === "text" && !height && !hasChildren && "h-[1em]",
47
+ animation === "pulse" && "animate-pulse",
48
+ animation === "wave" && "fv-skeleton-wave",
49
+ hasChildren && "relative overflow-hidden [&>*]:invisible",
50
+ className
51
+ ),
52
+ style: sizeStyle,
53
+ ...props,
54
+ children
55
+ }
56
+ );
57
+ }
58
+ );
59
+ Skeleton.displayName = "Skeleton";
60
+ exports.Skeleton = Skeleton;
61
+ //# sourceMappingURL=Skeleton.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skeleton.cjs","sources":["../../../../src/components/Skeleton/Skeleton.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Shape variant of the skeleton placeholder. */\nexport type SkeletonVariant = \"text\" | \"circular\" | \"rectangular\" | \"rounded\";\n\n/** Animation style of the skeleton. `false` disables animation. */\nexport type SkeletonAnimation = \"pulse\" | \"wave\" | false;\n\nexport interface SkeletonProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Shape variant of the skeleton.\n *\n * - `text` – a single line of text with slight vertical margin and rounded ends\n * - `circular` – a circle (width and height should match)\n * - `rectangular` – a sharp-cornered rectangle\n * - `rounded` – a rectangle with rounded corners\n *\n * @default \"text\"\n */\n variant?: SkeletonVariant;\n /** Animation style. Set to `false` to disable animation. @default \"pulse\" */\n animation?: SkeletonAnimation;\n /** Width of the skeleton. Accepts a CSS value (number is treated as `px`). */\n width?: number | string;\n /** Height of the skeleton. Accepts a CSS value (number is treated as `px`). */\n height?: number | string;\n}\n\nconst VARIANT_CLASSES: Record<SkeletonVariant, string> = {\n text: \"rounded my-0.5\",\n circular: \"rounded-full\",\n rectangular: \"\",\n rounded: \"rounded-lg\",\n};\n\n/**\n * A placeholder preview of content before data is loaded, reducing perceived\n * load time. Mirrors common MUI Skeleton props for easy migration.\n *\n * When used as a loading state, wrap the skeleton region with `aria-busy=\"true\"`\n * and provide an accessible label so screen readers can convey the loading state.\n *\n * @example\n * ```tsx\n * <Skeleton variant=\"text\" width={200} />\n * <Skeleton variant=\"circular\" width={40} height={40} />\n * <Skeleton variant=\"rectangular\" width=\"100%\" height={120} />\n * ```\n */\nexport const Skeleton = React.forwardRef<HTMLSpanElement, SkeletonProps>(\n (\n { className, variant = \"text\", animation = \"pulse\", width, height, style, children, ...props },\n ref,\n ) => {\n const hasChildren = React.Children.count(children) > 0;\n const sizeStyle: React.CSSProperties = {\n ...style,\n ...(width !== undefined && { width: typeof width === \"number\" ? `${width}px` : width }),\n ...(height !== undefined && { height: typeof height === \"number\" ? `${height}px` : height }),\n };\n\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn(\n \"block bg-neutral-200 dark:bg-neutral-200\",\n VARIANT_CLASSES[variant],\n variant === \"text\" && !height && !hasChildren && \"h-[1em]\",\n animation === \"pulse\" && \"animate-pulse\",\n animation === \"wave\" && \"fv-skeleton-wave\",\n hasChildren && \"relative overflow-hidden [&>*]:invisible\",\n className,\n )}\n style={sizeStyle}\n {...props}\n >\n {children}\n </span>\n );\n },\n);\n\nSkeleton.displayName = \"Skeleton\";\n"],"names":["React","jsx","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,kBAAmD;AAAA,EACvD,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AACX;AAgBO,MAAM,WAAWA,iBAAM;AAAA,EAC5B,CACE,EAAE,WAAW,UAAU,QAAQ,YAAY,SAAS,OAAO,QAAQ,OAAO,UAAU,GAAG,MAAA,GACvF,QACG;AACH,UAAM,cAAcA,iBAAM,SAAS,MAAM,QAAQ,IAAI;AACrD,UAAM,YAAiC;AAAA,MACrC,GAAG;AAAA,MACH,GAAI,UAAU,UAAa,EAAE,OAAO,OAAO,UAAU,WAAW,GAAG,KAAK,OAAO,MAAA;AAAA,MAC/E,GAAI,WAAW,UAAa,EAAE,QAAQ,OAAO,WAAW,WAAW,GAAG,MAAM,OAAO,OAAA;AAAA,IAAO;AAG5F,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA,UACA,gBAAgB,OAAO;AAAA,UACvB,YAAY,UAAU,CAAC,UAAU,CAAC,eAAe;AAAA,UACjD,cAAc,WAAW;AAAA,UACzB,cAAc,UAAU;AAAA,UACxB,eAAe;AAAA,UACf;AAAA,QAAA;AAAA,QAEF,OAAO;AAAA,QACN,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,SAAS,cAAc;;"}
@@ -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-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
54
+ "relative overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
55
55
  error ? "border-error-default" : "border-transparent",
56
56
  !disabled && !error && "hover:border-neutral-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-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-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-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-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 \"w-full rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\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-default\" />\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-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-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 overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-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 \"w-full rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\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-default\" />\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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"}
@@ -44,7 +44,7 @@ const ICON_SPACING = {
44
44
  };
45
45
  function getContainerClassName(size, error, disabled) {
46
46
  return cn.cn(
47
- "flex items-center rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
47
+ "flex items-center overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
48
48
  error ? "border-error-default" : "border-transparent",
49
49
  !disabled && !error && "hover:border-neutral-400",
50
50
  CONTAINER_HEIGHT[size],
@@ -55,7 +55,7 @@ function getContainerClassName(size, error, disabled) {
55
55
  }
56
56
  function getInputClassName(size) {
57
57
  return cn.cn(
58
- "h-full flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed",
58
+ "h-full min-w-0 flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed",
59
59
  INPUT_SIZE_CLASSES[size]
60
60
  );
61
61
  }
@@ -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 /** 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 INPUT_SIZE_CLASSES: Record<TextFieldSize, string> = {\n \"48\": \"py-3 typography-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-body-md\",\n};\n\nconst PADDING_HORIZONTAL: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst ICON_SPACING: Record<TextFieldSize, string> = {\n \"48\": \"gap-3\",\n \"40\": \"gap-3\",\n \"32\": \"gap-2\",\n};\n\nfunction getContainerClassName(size: TextFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"flex items-center rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-400\",\n CONTAINER_HEIGHT[size],\n PADDING_HORIZONTAL[size],\n ICON_SPACING[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getInputClassName(size: TextFieldSize) {\n return cn(\n \"h-full flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n );\n}\n\nfunction TextFieldIcon({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-foreground-secondary\">\n {children}\n </div>\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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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, and icon slots.\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 * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\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 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 bottomText = error && errorMessage ? errorMessage : helperText;\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n {leftIcon && <TextFieldIcon>{leftIcon}</TextFieldIcon>}\n\n <input\n ref={ref}\n id={inputId}\n disabled={disabled}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n getInputClassName(size),\n // Hide native clear button for input[type=\"search\"] in WebKit browsers (Safari/Chrome)\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n {rightIcon && <TextFieldIcon>{rightIcon}</TextFieldIcon>}\n {validated && (\n <TextFieldIcon>\n <CheckOutlineIcon className=\"text-success-default\" />\n </TextFieldIcon>\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","React","jsxs","CheckOutlineIcon"],"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 /** 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 INPUT_SIZE_CLASSES: Record<TextFieldSize, string> = {\n \"48\": \"py-3 typography-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-body-md\",\n};\n\nconst PADDING_HORIZONTAL: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst ICON_SPACING: Record<TextFieldSize, string> = {\n \"48\": \"gap-3\",\n \"40\": \"gap-3\",\n \"32\": \"gap-2\",\n};\n\nfunction getContainerClassName(size: TextFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"flex items-center overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-400\",\n CONTAINER_HEIGHT[size],\n PADDING_HORIZONTAL[size],\n ICON_SPACING[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getInputClassName(size: TextFieldSize) {\n return cn(\n \"h-full min-w-0 flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n );\n}\n\nfunction TextFieldIcon({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-foreground-secondary\">\n {children}\n </div>\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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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, and icon slots.\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 * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\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 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 bottomText = error && errorMessage ? errorMessage : helperText;\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n {leftIcon && <TextFieldIcon>{leftIcon}</TextFieldIcon>}\n\n <input\n ref={ref}\n id={inputId}\n disabled={disabled}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n getInputClassName(size),\n // Hide native clear button for input[type=\"search\"] in WebKit browsers (Safari/Chrome)\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n {rightIcon && <TextFieldIcon>{rightIcon}</TextFieldIcon>}\n {validated && (\n <TextFieldIcon>\n <CheckOutlineIcon className=\"text-success-default\" />\n </TextFieldIcon>\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","React","jsxs","CheckOutlineIcon"],"mappings":"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"}
@@ -139,6 +139,7 @@ const Radio = require("./components/Radio/Radio.cjs");
139
139
  const RadioGroup = require("./components/RadioGroup/RadioGroup.cjs");
140
140
  const SearchField = require("./components/SearchField/SearchField.cjs");
141
141
  const Select = require("./components/Select/Select.cjs");
142
+ const Skeleton = require("./components/Skeleton/Skeleton.cjs");
142
143
  const Slider = require("./components/Slider/Slider.cjs");
143
144
  const Snackbar = require("./components/Snackbar/Snackbar.cjs");
144
145
  const Switch = require("./components/Switch/Switch.cjs");
@@ -299,6 +300,7 @@ exports.SelectGroup = Select.SelectGroup;
299
300
  exports.SelectItem = Select.SelectItem;
300
301
  exports.SelectLabel = Select.SelectLabel;
301
302
  exports.SelectSeparator = Select.SelectSeparator;
303
+ exports.Skeleton = Skeleton.Skeleton;
302
304
  exports.Slider = Slider.Slider;
303
305
  exports.Snackbar = Snackbar.Snackbar;
304
306
  exports.Switch = Switch.Switch;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -41,6 +41,7 @@ const Badge = React.forwardRef(
41
41
  leftIcon,
42
42
  rightIcon,
43
43
  asChild = false,
44
+ onClick,
44
45
  children,
45
46
  ...props
46
47
  }, ref) => {
@@ -55,9 +56,12 @@ const Badge = React.forwardRef(
55
56
  "typography-semibold-body-sm inline-flex h-5 items-center gap-2 rounded-full px-2",
56
57
  // Variant styles
57
58
  badgeVariants.variant[variant],
59
+ // Interactive
60
+ onClick && "cursor-pointer",
58
61
  // Manual CSS overrides
59
62
  className
60
63
  ),
64
+ onClick,
61
65
  ...props,
62
66
  children: [
63
67
  leftIcon && /* @__PURE__ */ jsx("span", { className: "flex size-3", "aria-hidden": "true", children: leftIcon }),
@@ -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-100 text-foreground-secondary\",\n dark: \"bg-neutral-50 text-foreground-inverse dark:text-foreground-onaccentinverse\",\n success: \"bg-neutral-100 text-foreground-secondary\",\n warning: \"bg-neutral-100 text-foreground-secondary\",\n error: \"bg-neutral-100 text-foreground-secondary\",\n special: \"bg-neutral-100 text-foreground-secondary\",\n info: \"bg-neutral-100 text-foreground-secondary\",\n online: \"bg-neutral-100 text-brand-accent-default\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n pink: \"bg-brand-secondary-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-onaccent\",\n },\n dotColor: {\n default: \"bg-foreground-onaccent\",\n dark: \"bg-foreground-inverse dark:bg-foreground-onaccentinverse\",\n success: \"bg-success-default\",\n warning: \"bg-warning-default\",\n error: \"bg-error-default\",\n special: \"bg-special-default\",\n info: \"bg-info-default\",\n online: \"bg-brand-accent-default\",\n brand: \"bg-foreground-onaccent\",\n pink: \"bg-foreground-onaccent\",\n brandLight: \"bg-foreground-onaccent\",\n pinkLight: \"bg-foreground-onaccent\",\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 | \"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 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-semibold-body-sm inline-flex h-5 items-center gap-2 rounded-full px-2\",\n // Variant styles\n badgeVariants.variant[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {leftIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\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 <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\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,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,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AAsCO,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,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;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,gCACE,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UACH;AAAA,UAED,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGhB,oBAAC,aAAW,UAAS;AAAA,UACpB,aACC,oBAAC,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UAAA,CACH;AAAA,QAAA;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\";\n\nconst badgeVariants = {\n variant: {\n default: \"bg-neutral-100 text-foreground-secondary\",\n dark: \"bg-neutral-50 text-foreground-inverse dark:text-foreground-onaccentinverse\",\n success: \"bg-neutral-100 text-foreground-secondary\",\n warning: \"bg-neutral-100 text-foreground-secondary\",\n error: \"bg-neutral-100 text-foreground-secondary\",\n special: \"bg-neutral-100 text-foreground-secondary\",\n info: \"bg-neutral-100 text-foreground-secondary\",\n online: \"bg-neutral-100 text-brand-accent-default\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n pink: \"bg-brand-secondary-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-onaccent\",\n },\n dotColor: {\n default: \"bg-foreground-onaccent\",\n dark: \"bg-foreground-inverse dark:bg-foreground-onaccentinverse\",\n success: \"bg-success-default\",\n warning: \"bg-warning-default\",\n error: \"bg-error-default\",\n special: \"bg-special-default\",\n info: \"bg-info-default\",\n online: \"bg-brand-accent-default\",\n brand: \"bg-foreground-onaccent\",\n pink: \"bg-foreground-onaccent\",\n brandLight: \"bg-foreground-onaccent\",\n pinkLight: \"bg-foreground-onaccent\",\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 | \"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-semibold-body-sm inline-flex h-5 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 className=\"flex size-3\" aria-hidden=\"true\">\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 <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\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,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,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AAsCO,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,gCACE,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UACH;AAAA,UAED,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGhB,oBAAC,aAAW,UAAS;AAAA,UACpB,aACC,oBAAC,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;"}
@@ -38,7 +38,8 @@ const Chip = React.forwardRef(
38
38
  isDark && "bg-neutral-50 text-foreground-onaccentinverse",
39
39
  !isDark && selected && "bg-brand-accent-muted text-neutral-400",
40
40
  !isDark && !selected && "bg-neutral-100 text-neutral-400",
41
- // Hover
41
+ // Interactive
42
+ isInteractive && !disabled && "cursor-pointer",
42
43
  isInteractive && !disabled && !isDark && !selected && "hover:bg-brand-accent-muted active:bg-brand-accent-muted",
43
44
  // Focus
44
45
  "focus-visible:shadow-focus-ring focus-visible:outline-none",
@@ -1 +1 @@
1
- {"version":3,"file":"Chip.mjs","sources":["../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\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 /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-semibold-body-sm relative inline-flex items-center justify-center gap-2 whitespace-nowrap px-3 motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-lg\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-50 text-foreground-onaccentinverse\",\n !isDark && selected && \"bg-brand-accent-muted text-neutral-400\",\n !isDark && !selected && \"bg-neutral-100 text-neutral-400\",\n // Hover\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n \"hover:bg-brand-accent-muted active:bg-brand-accent-muted\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-300\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {leftDot && <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />}\n {leftIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n {notificationLabel && (\n <span className=\"typography-semibold-body-sm absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground-default px-1 text-foreground-inverse\">\n {notificationLabel}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":[],"mappings":";;;;;AA0CO,MAAM,OAAO,MAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAU,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY;AAAA;AAAA,UAExB,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,WAAW,oBAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,UACxF,YACC,oBAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,UACH;AAAA,UAEF,oBAAC,aAAW,UAAS;AAAA,UACpB,aACC,oBAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,WACH;AAAA,UAED,qBACC,oBAAC,QAAA,EAAK,WAAU,qKACb,UAAA,kBAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;"}
1
+ {"version":3,"file":"Chip.mjs","sources":["../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\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 /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-semibold-body-sm relative inline-flex items-center justify-center gap-2 whitespace-nowrap px-3 motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-lg\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-50 text-foreground-onaccentinverse\",\n !isDark && selected && \"bg-brand-accent-muted text-neutral-400\",\n !isDark && !selected && \"bg-neutral-100 text-neutral-400\",\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n \"hover:bg-brand-accent-muted active:bg-brand-accent-muted\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-300\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {leftDot && <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />}\n {leftIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex size-5 shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n {notificationLabel && (\n <span className=\"typography-semibold-body-sm absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground-default px-1 text-foreground-inverse\">\n {notificationLabel}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":[],"mappings":";;;;;AA0CO,MAAM,OAAO,MAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAU,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY;AAAA;AAAA,UAExB,iBAAiB,CAAC,YAAY;AAAA,UAC9B,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,WAAW,oBAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,UACxF,YACC,oBAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,UACH;AAAA,UAEF,oBAAC,aAAW,UAAS;AAAA,UACpB,aACC,oBAAC,QAAA,EAAK,WAAU,oDAAmD,eAAY,QAC5E,UAAA,WACH;AAAA,UAED,qBACC,oBAAC,QAAA,EAAK,WAAU,qKACb,UAAA,kBAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;"}
@@ -18,7 +18,16 @@ const pillVariants = {
18
18
  }
19
19
  };
20
20
  const Pill = React.forwardRef(
21
- ({ className, variant = "green", leftIcon, rightIcon, asChild = false, children, ...props }, ref) => {
21
+ ({
22
+ className,
23
+ variant = "green",
24
+ leftIcon,
25
+ rightIcon,
26
+ asChild = false,
27
+ onClick,
28
+ children,
29
+ ...props
30
+ }, ref) => {
22
31
  const Comp = asChild ? Slot : "span";
23
32
  return /* @__PURE__ */ jsxs(
24
33
  Comp,
@@ -32,9 +41,12 @@ const Pill = React.forwardRef(
32
41
  "typography-semibold-body-sm",
33
42
  // Variant styles
34
43
  pillVariants.variant[variant],
44
+ // Interactive
45
+ onClick && "cursor-pointer",
35
46
  // Manual CSS overrides
36
47
  className
37
48
  ),
49
+ onClick,
38
50
  ...props,
39
51
  children: [
40
52
  leftIcon && /* @__PURE__ */ jsx("span", { className: "flex", "aria-hidden": "true", children: leftIcon }),
@@ -1 +1 @@
1
- {"version":3,"file":"Pill.mjs","sources":["../../../src/components/Pill/Pill.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst pillVariants = {\n variant: {\n green: \"bg-success-background text-success-default\",\n grey: \"bg-neutral-100 text-foreground-secondary\",\n blue: \"bg-info-background text-info-default\",\n gold: \"bg-warning-background text-warning-default\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-default\",\n base: \"bg-neutral-solid text-foreground-inverse\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n beta: \"bg-brand-secondary-default text-foreground-onaccent\",\n error: \"bg-error-default text-error-background\",\n },\n} as const;\n\n/** Colour variant of the pill. */\nexport type PillVariant =\n | \"green\"\n | \"grey\"\n | \"blue\"\n | \"gold\"\n | \"pinkLight\"\n | \"base\"\n | \"brand\"\n | \"brandLight\"\n | \"beta\"\n | \"error\";\n\nexport interface PillProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Colour variant of the pill. @default \"green\" */\n variant?: PillVariant;\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 rounded label for categorisation, status, or tagging.\n *\n * @example\n * ```tsx\n * <Pill variant=\"brand\">New</Pill>\n * ```\n */\nexport const Pill = React.forwardRef<HTMLSpanElement, PillProps>(\n (\n { className, variant = \"green\", leftIcon, rightIcon, asChild = false, children, ...props },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"span\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"pill\"\n className={cn(\n // Base styles\n \"inline-flex items-center justify-center gap-2 rounded-full px-3 py-1\",\n // Typography\n \"typography-semibold-body-sm\",\n // Variant styles\n pillVariants.variant[variant],\n // Manual CSS overrides\n className,\n )}\n {...props}\n >\n {leftIcon && (\n <span className=\"flex\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nPill.displayName = \"Pill\";\n"],"names":[],"mappings":";;;;;AAIA,MAAM,eAAe;AAAA,EACnB,SAAS;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAEX;AAkCO,MAAM,OAAO,MAAM;AAAA,EACxB,CACE,EAAE,WAAW,UAAU,SAAS,UAAU,WAAW,UAAU,OAAO,UAAU,GAAG,MAAA,GACnF,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;AAAA;AAAA,UAEA,aAAa,QAAQ,OAAO;AAAA;AAAA,UAE5B;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,gCACE,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UACH;AAAA,UAEF,oBAAC,aAAW,UAAS;AAAA,UACpB,aACC,oBAAC,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;"}
1
+ {"version":3,"file":"Pill.mjs","sources":["../../../src/components/Pill/Pill.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst pillVariants = {\n variant: {\n green: \"bg-success-background text-success-default\",\n grey: \"bg-neutral-100 text-foreground-secondary\",\n blue: \"bg-info-background text-info-default\",\n gold: \"bg-warning-background text-warning-default\",\n pinkLight: \"bg-brand-secondary-muted text-foreground-default\",\n base: \"bg-neutral-solid text-foreground-inverse\",\n brand: \"bg-brand-accent-default text-foreground-onaccent\",\n brandLight: \"bg-brand-accent-muted text-foreground-onaccent\",\n beta: \"bg-brand-secondary-default text-foreground-onaccent\",\n error: \"bg-error-default text-error-background\",\n },\n} as const;\n\n/** Colour variant of the pill. */\nexport type PillVariant =\n | \"green\"\n | \"grey\"\n | \"blue\"\n | \"gold\"\n | \"pinkLight\"\n | \"base\"\n | \"brand\"\n | \"brandLight\"\n | \"beta\"\n | \"error\";\n\nexport interface PillProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Colour variant of the pill. @default \"green\" */\n variant?: PillVariant;\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 rounded label for categorisation, status, or tagging.\n *\n * @example\n * ```tsx\n * <Pill variant=\"brand\">New</Pill>\n * ```\n */\nexport const Pill = React.forwardRef<HTMLSpanElement, PillProps>(\n (\n {\n className,\n variant = \"green\",\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=\"pill\"\n className={cn(\n // Base styles\n \"inline-flex items-center justify-center gap-2 rounded-full px-3 py-1\",\n // Typography\n \"typography-semibold-body-sm\",\n // Variant styles\n pillVariants.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 className=\"flex\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <Slottable>{children}</Slottable>\n {rightIcon && (\n <span className=\"flex\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nPill.displayName = \"Pill\";\n"],"names":[],"mappings":";;;;;AAIA,MAAM,eAAe;AAAA,EACnB,SAAS;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAEX;AAkCO,MAAM,OAAO,MAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,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;AAAA;AAAA,UAEA,aAAa,QAAQ,OAAO;AAAA;AAAA,UAE5B,WAAW;AAAA;AAAA,UAEX;AAAA,QAAA;AAAA,QAEF;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,gCACE,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UACH;AAAA,UAEF,oBAAC,aAAW,UAAS;AAAA,UACpB,aACC,oBAAC,QAAA,EAAK,WAAU,QAAO,eAAY,QAChC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;"}
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.mjs";
5
+ const VARIANT_CLASSES = {
6
+ text: "rounded my-0.5",
7
+ circular: "rounded-full",
8
+ rectangular: "",
9
+ rounded: "rounded-lg"
10
+ };
11
+ const Skeleton = React.forwardRef(
12
+ ({ className, variant = "text", animation = "pulse", width, height, style, children, ...props }, ref) => {
13
+ const hasChildren = React.Children.count(children) > 0;
14
+ const sizeStyle = {
15
+ ...style,
16
+ ...width !== void 0 && { width: typeof width === "number" ? `${width}px` : width },
17
+ ...height !== void 0 && { height: typeof height === "number" ? `${height}px` : height }
18
+ };
19
+ return /* @__PURE__ */ jsx(
20
+ "span",
21
+ {
22
+ ref,
23
+ "aria-hidden": "true",
24
+ className: cn(
25
+ "block bg-neutral-200 dark:bg-neutral-200",
26
+ VARIANT_CLASSES[variant],
27
+ variant === "text" && !height && !hasChildren && "h-[1em]",
28
+ animation === "pulse" && "animate-pulse",
29
+ animation === "wave" && "fv-skeleton-wave",
30
+ hasChildren && "relative overflow-hidden [&>*]:invisible",
31
+ className
32
+ ),
33
+ style: sizeStyle,
34
+ ...props,
35
+ children
36
+ }
37
+ );
38
+ }
39
+ );
40
+ Skeleton.displayName = "Skeleton";
41
+ export {
42
+ Skeleton
43
+ };
44
+ //# sourceMappingURL=Skeleton.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skeleton.mjs","sources":["../../../src/components/Skeleton/Skeleton.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Shape variant of the skeleton placeholder. */\nexport type SkeletonVariant = \"text\" | \"circular\" | \"rectangular\" | \"rounded\";\n\n/** Animation style of the skeleton. `false` disables animation. */\nexport type SkeletonAnimation = \"pulse\" | \"wave\" | false;\n\nexport interface SkeletonProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Shape variant of the skeleton.\n *\n * - `text` – a single line of text with slight vertical margin and rounded ends\n * - `circular` – a circle (width and height should match)\n * - `rectangular` – a sharp-cornered rectangle\n * - `rounded` – a rectangle with rounded corners\n *\n * @default \"text\"\n */\n variant?: SkeletonVariant;\n /** Animation style. Set to `false` to disable animation. @default \"pulse\" */\n animation?: SkeletonAnimation;\n /** Width of the skeleton. Accepts a CSS value (number is treated as `px`). */\n width?: number | string;\n /** Height of the skeleton. Accepts a CSS value (number is treated as `px`). */\n height?: number | string;\n}\n\nconst VARIANT_CLASSES: Record<SkeletonVariant, string> = {\n text: \"rounded my-0.5\",\n circular: \"rounded-full\",\n rectangular: \"\",\n rounded: \"rounded-lg\",\n};\n\n/**\n * A placeholder preview of content before data is loaded, reducing perceived\n * load time. Mirrors common MUI Skeleton props for easy migration.\n *\n * When used as a loading state, wrap the skeleton region with `aria-busy=\"true\"`\n * and provide an accessible label so screen readers can convey the loading state.\n *\n * @example\n * ```tsx\n * <Skeleton variant=\"text\" width={200} />\n * <Skeleton variant=\"circular\" width={40} height={40} />\n * <Skeleton variant=\"rectangular\" width=\"100%\" height={120} />\n * ```\n */\nexport const Skeleton = React.forwardRef<HTMLSpanElement, SkeletonProps>(\n (\n { className, variant = \"text\", animation = \"pulse\", width, height, style, children, ...props },\n ref,\n ) => {\n const hasChildren = React.Children.count(children) > 0;\n const sizeStyle: React.CSSProperties = {\n ...style,\n ...(width !== undefined && { width: typeof width === \"number\" ? `${width}px` : width }),\n ...(height !== undefined && { height: typeof height === \"number\" ? `${height}px` : height }),\n };\n\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn(\n \"block bg-neutral-200 dark:bg-neutral-200\",\n VARIANT_CLASSES[variant],\n variant === \"text\" && !height && !hasChildren && \"h-[1em]\",\n animation === \"pulse\" && \"animate-pulse\",\n animation === \"wave\" && \"fv-skeleton-wave\",\n hasChildren && \"relative overflow-hidden [&>*]:invisible\",\n className,\n )}\n style={sizeStyle}\n {...props}\n >\n {children}\n </span>\n );\n },\n);\n\nSkeleton.displayName = \"Skeleton\";\n"],"names":[],"mappings":";;;;AA4BA,MAAM,kBAAmD;AAAA,EACvD,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AACX;AAgBO,MAAM,WAAW,MAAM;AAAA,EAC5B,CACE,EAAE,WAAW,UAAU,QAAQ,YAAY,SAAS,OAAO,QAAQ,OAAO,UAAU,GAAG,MAAA,GACvF,QACG;AACH,UAAM,cAAc,MAAM,SAAS,MAAM,QAAQ,IAAI;AACrD,UAAM,YAAiC;AAAA,MACrC,GAAG;AAAA,MACH,GAAI,UAAU,UAAa,EAAE,OAAO,OAAO,UAAU,WAAW,GAAG,KAAK,OAAO,MAAA;AAAA,MAC/E,GAAI,WAAW,UAAa,EAAE,QAAQ,OAAO,WAAW,WAAW,GAAG,MAAM,OAAO,OAAA;AAAA,IAAO;AAG5F,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA,gBAAgB,OAAO;AAAA,UACvB,YAAY,UAAU,CAAC,UAAU,CAAC,eAAe;AAAA,UACjD,cAAc,WAAW;AAAA,UACzB,cAAc,UAAU;AAAA,UACxB,eAAe;AAAA,UACf;AAAA,QAAA;AAAA,QAEF,OAAO;AAAA,QACN,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,SAAS,cAAc;"}
@@ -32,7 +32,7 @@ const CLEAR_BUTTON_RIGHT = {
32
32
  };
33
33
  function getContainerClassName(size, error, disabled) {
34
34
  return cn(
35
- "relative rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
35
+ "relative overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
36
36
  error ? "border-error-default" : "border-transparent",
37
37
  !disabled && !error && "hover:border-neutral-400",
38
38
  CONTAINER_MIN_HEIGHT[size],
@@ -1 +1 @@
1
- {"version":3,"file":"TextArea.mjs","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-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-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-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-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 \"w-full rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\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-default\" />\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":[],"mappings":"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"}
1
+ {"version":3,"file":"TextArea.mjs","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-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-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 overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-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 \"w-full rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\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-default\" />\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":[],"mappings":"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"}
@@ -25,7 +25,7 @@ const ICON_SPACING = {
25
25
  };
26
26
  function getContainerClassName(size, error, disabled) {
27
27
  return cn(
28
- "flex items-center rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
28
+ "flex items-center overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors",
29
29
  error ? "border-error-default" : "border-transparent",
30
30
  !disabled && !error && "hover:border-neutral-400",
31
31
  CONTAINER_HEIGHT[size],
@@ -36,7 +36,7 @@ function getContainerClassName(size, error, disabled) {
36
36
  }
37
37
  function getInputClassName(size) {
38
38
  return cn(
39
- "h-full flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed",
39
+ "h-full min-w-0 flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed",
40
40
  INPUT_SIZE_CLASSES[size]
41
41
  );
42
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TextField.mjs","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 /** 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 INPUT_SIZE_CLASSES: Record<TextFieldSize, string> = {\n \"48\": \"py-3 typography-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-body-md\",\n};\n\nconst PADDING_HORIZONTAL: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst ICON_SPACING: Record<TextFieldSize, string> = {\n \"48\": \"gap-3\",\n \"40\": \"gap-3\",\n \"32\": \"gap-2\",\n};\n\nfunction getContainerClassName(size: TextFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"flex items-center rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-400\",\n CONTAINER_HEIGHT[size],\n PADDING_HORIZONTAL[size],\n ICON_SPACING[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getInputClassName(size: TextFieldSize) {\n return cn(\n \"h-full flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n );\n}\n\nfunction TextFieldIcon({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-foreground-secondary\">\n {children}\n </div>\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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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, and icon slots.\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 * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\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 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 bottomText = error && errorMessage ? errorMessage : helperText;\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n {leftIcon && <TextFieldIcon>{leftIcon}</TextFieldIcon>}\n\n <input\n ref={ref}\n id={inputId}\n disabled={disabled}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n getInputClassName(size),\n // Hide native clear button for input[type=\"search\"] in WebKit browsers (Safari/Chrome)\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n {rightIcon && <TextFieldIcon>{rightIcon}</TextFieldIcon>}\n {validated && (\n <TextFieldIcon>\n <CheckOutlineIcon className=\"text-success-default\" />\n </TextFieldIcon>\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":[],"mappings":"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"}
1
+ {"version":3,"file":"TextField.mjs","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 /** 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 INPUT_SIZE_CLASSES: Record<TextFieldSize, string> = {\n \"48\": \"py-3 typography-regular-body-lg\",\n \"40\": \"py-2 typography-regular-body-lg\",\n \"32\": \"py-2 typography-regular-body-md\",\n};\n\nconst PADDING_HORIZONTAL: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst ICON_SPACING: Record<TextFieldSize, string> = {\n \"48\": \"gap-3\",\n \"40\": \"gap-3\",\n \"32\": \"gap-2\",\n};\n\nfunction getContainerClassName(size: TextFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"flex items-center overflow-hidden rounded-xl border bg-neutral-100 has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-default\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-400\",\n CONTAINER_HEIGHT[size],\n PADDING_HORIZONTAL[size],\n ICON_SPACING[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getInputClassName(size: TextFieldSize) {\n return cn(\n \"h-full min-w-0 flex-1 rounded-xl bg-transparent text-foreground-default no-underline placeholder:text-foreground-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n );\n}\n\nfunction TextFieldIcon({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-foreground-secondary\">\n {children}\n </div>\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-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-default\" : \"text-foreground-secondary\",\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, and icon slots.\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 * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\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 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 bottomText = error && errorMessage ? errorMessage : helperText;\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-semibold-body-sm px-1 pt-1 pb-2 text-foreground-default\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n {leftIcon && <TextFieldIcon>{leftIcon}</TextFieldIcon>}\n\n <input\n ref={ref}\n id={inputId}\n disabled={disabled}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n getInputClassName(size),\n // Hide native clear button for input[type=\"search\"] in WebKit browsers (Safari/Chrome)\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n {rightIcon && <TextFieldIcon>{rightIcon}</TextFieldIcon>}\n {validated && (\n <TextFieldIcon>\n <CheckOutlineIcon className=\"text-success-default\" />\n </TextFieldIcon>\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":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1104,6 +1104,47 @@ export declare const ShareIcon: React_2.ForwardRefExoticComponent<React_2.SVGAtt
1104
1104
  className?: string;
1105
1105
  } & React_2.RefAttributes<SVGSVGElement>>;
1106
1106
 
1107
+ /**
1108
+ * A placeholder preview of content before data is loaded, reducing perceived
1109
+ * load time. Mirrors common MUI Skeleton props for easy migration.
1110
+ *
1111
+ * When used as a loading state, wrap the skeleton region with `aria-busy="true"`
1112
+ * and provide an accessible label so screen readers can convey the loading state.
1113
+ *
1114
+ * @example
1115
+ * ```tsx
1116
+ * <Skeleton variant="text" width={200} />
1117
+ * <Skeleton variant="circular" width={40} height={40} />
1118
+ * <Skeleton variant="rectangular" width="100%" height={120} />
1119
+ * ```
1120
+ */
1121
+ export declare const Skeleton: React_2.ForwardRefExoticComponent<SkeletonProps & React_2.RefAttributes<HTMLSpanElement>>;
1122
+
1123
+ /** Animation style of the skeleton. `false` disables animation. */
1124
+ export declare type SkeletonAnimation = "pulse" | "wave" | false;
1125
+
1126
+ export declare interface SkeletonProps extends React_2.HTMLAttributes<HTMLSpanElement> {
1127
+ /** Shape variant of the skeleton.
1128
+ *
1129
+ * - `text` – a single line of text with slight vertical margin and rounded ends
1130
+ * - `circular` – a circle (width and height should match)
1131
+ * - `rectangular` – a sharp-cornered rectangle
1132
+ * - `rounded` – a rectangle with rounded corners
1133
+ *
1134
+ * @default "text"
1135
+ */
1136
+ variant?: SkeletonVariant;
1137
+ /** Animation style. Set to `false` to disable animation. @default "pulse" */
1138
+ animation?: SkeletonAnimation;
1139
+ /** Width of the skeleton. Accepts a CSS value (number is treated as `px`). */
1140
+ width?: number | string;
1141
+ /** Height of the skeleton. Accepts a CSS value (number is treated as `px`). */
1142
+ height?: number | string;
1143
+ }
1144
+
1145
+ /** Shape variant of the skeleton placeholder. */
1146
+ export declare type SkeletonVariant = "text" | "circular" | "rectangular" | "rounded";
1147
+
1107
1148
  /**
1108
1149
  * A range input for selecting one or more numeric values along a track.
1109
1150
  * Supports single and multi-thumb modes, optional labels, and a value tooltip.
package/dist/index.mjs CHANGED
@@ -137,6 +137,7 @@ import { Radio } from "./components/Radio/Radio.mjs";
137
137
  import { RadioGroup } from "./components/RadioGroup/RadioGroup.mjs";
138
138
  import { SearchField } from "./components/SearchField/SearchField.mjs";
139
139
  import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator } from "./components/Select/Select.mjs";
140
+ import { Skeleton } from "./components/Skeleton/Skeleton.mjs";
140
141
  import { Slider } from "./components/Slider/Slider.mjs";
141
142
  import { Snackbar } from "./components/Snackbar/Snackbar.mjs";
142
143
  import { Switch } from "./components/Switch/Switch.mjs";
@@ -268,6 +269,7 @@ export {
268
269
  SendIcon,
269
270
  SettingsIcon,
270
271
  ShareIcon,
272
+ Skeleton,
271
273
  Slider,
272
274
  Snackbar,
273
275
  SpinnerIcon,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -477,3 +477,30 @@
477
477
  letter-spacing: 0px;
478
478
  line-height: 64px;
479
479
  }
480
+
481
+ @utility fv-skeleton-wave {
482
+ position: relative;
483
+ overflow: hidden;
484
+
485
+ &::after {
486
+ position: absolute;
487
+ inset: 0;
488
+ transform: translateX(-100%);
489
+ will-change: transform;
490
+ background-image: linear-gradient(
491
+ 90deg,
492
+ transparent,
493
+ color-mix(in srgb, var(--color-foreground-default) 8%, transparent),
494
+ color-mix(in srgb, var(--color-foreground-default) 16%, transparent),
495
+ transparent
496
+ );
497
+ animation: fv-skeleton-shimmer 1.5s ease-in-out infinite;
498
+ content: "";
499
+ }
500
+ }
501
+
502
+ @keyframes fv-skeleton-shimmer {
503
+ 100% {
504
+ transform: translateX(100%);
505
+ }
506
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/ui",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "React component library built with Tailwind CSS for Fanvue ecosystem",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",