@fanvue/ui 1.2.0 → 1.3.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 (147) hide show
  1. package/dist/cjs/components/Alert/Alert.cjs +4 -4
  2. package/dist/cjs/components/Alert/Alert.cjs.map +1 -1
  3. package/dist/cjs/components/Avatar/Avatar.cjs.map +1 -1
  4. package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
  5. package/dist/cjs/components/Button/Button.cjs +9 -9
  6. package/dist/cjs/components/Button/Button.cjs.map +1 -1
  7. package/dist/cjs/components/Checkbox/Checkbox.cjs +6 -7
  8. package/dist/cjs/components/Checkbox/Checkbox.cjs.map +1 -1
  9. package/dist/cjs/components/Chip/Chip.cjs +2 -2
  10. package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
  11. package/dist/cjs/components/Count/Count.cjs.map +1 -1
  12. package/dist/cjs/components/DatePicker/DatePicker.cjs +30 -7
  13. package/dist/cjs/components/DatePicker/DatePicker.cjs.map +1 -1
  14. package/dist/cjs/components/Divider/Divider.cjs.map +1 -1
  15. package/dist/cjs/components/IconButton/IconButton.cjs +10 -10
  16. package/dist/cjs/components/IconButton/IconButton.cjs.map +1 -1
  17. package/dist/cjs/components/Icons/ArrowRightIcon.cjs.map +1 -1
  18. package/dist/cjs/components/Icons/ArrowUpRightIcon.cjs.map +1 -1
  19. package/dist/cjs/components/Icons/CheckCircleIcon.cjs.map +1 -1
  20. package/dist/cjs/components/Icons/ChevronLeftIcon.cjs.map +1 -1
  21. package/dist/cjs/components/Icons/ChevronRightIcon.cjs.map +1 -1
  22. package/dist/cjs/components/Icons/CloseIcon.cjs.map +1 -1
  23. package/dist/cjs/components/Icons/CrossIcon.cjs.map +1 -1
  24. package/dist/cjs/components/Icons/CrownIcon.cjs.map +1 -1
  25. package/dist/cjs/components/Icons/ErrorCircleIcon.cjs.map +1 -1
  26. package/dist/cjs/components/Icons/ErrorIcon.cjs.map +1 -1
  27. package/dist/cjs/components/Icons/EyeClosedIcon.cjs +49 -0
  28. package/dist/cjs/components/Icons/EyeClosedIcon.cjs.map +1 -0
  29. package/dist/cjs/components/Icons/EyeIcon.cjs.map +1 -1
  30. package/dist/cjs/components/Icons/FireIcon.cjs.map +1 -1
  31. package/dist/cjs/components/Icons/HomeIcon.cjs.map +1 -1
  32. package/dist/cjs/components/Icons/InfoCircleIcon.cjs.map +1 -1
  33. package/dist/cjs/components/Icons/InfoIcon.cjs.map +1 -1
  34. package/dist/cjs/components/Icons/MicrophoneIcon.cjs.map +1 -1
  35. package/dist/cjs/components/Icons/PlusIcon.cjs.map +1 -1
  36. package/dist/cjs/components/Icons/SpinnerIcon.cjs.map +1 -1
  37. package/dist/cjs/components/Icons/StopIcon.cjs.map +1 -1
  38. package/dist/cjs/components/Icons/SuccessIcon.cjs.map +1 -1
  39. package/dist/cjs/components/Icons/WarningIcon.cjs.map +1 -1
  40. package/dist/cjs/components/Icons/WarningTriangleIcon.cjs.map +1 -1
  41. package/dist/cjs/components/Logo/Logo.cjs +11 -11
  42. package/dist/cjs/components/Logo/Logo.cjs.map +1 -1
  43. package/dist/cjs/components/Pagination/Pagination.cjs +3 -3
  44. package/dist/cjs/components/Pagination/Pagination.cjs.map +1 -1
  45. package/dist/cjs/components/PasswordField/PasswordField.cjs +59 -0
  46. package/dist/cjs/components/PasswordField/PasswordField.cjs.map +1 -0
  47. package/dist/cjs/components/Pill/Pill.cjs.map +1 -1
  48. package/dist/cjs/components/ProgressBar/ProgressBar.cjs.map +1 -1
  49. package/dist/cjs/components/Radio/Radio.cjs +1 -1
  50. package/dist/cjs/components/Radio/Radio.cjs.map +1 -1
  51. package/dist/cjs/components/RadioGroup/RadioGroup.cjs.map +1 -1
  52. package/dist/cjs/components/Slider/Slider.cjs.map +1 -1
  53. package/dist/cjs/components/Slider/SliderThumb.cjs +1 -0
  54. package/dist/cjs/components/Slider/SliderThumb.cjs.map +1 -1
  55. package/dist/cjs/components/Snackbar/Snackbar.cjs.map +1 -1
  56. package/dist/cjs/components/Switch/Switch.cjs +1 -0
  57. package/dist/cjs/components/Switch/Switch.cjs.map +1 -1
  58. package/dist/cjs/components/SwitchField/SwitchField.cjs.map +1 -1
  59. package/dist/cjs/components/SwitchToggle/SwitchToggle.cjs +48 -31
  60. package/dist/cjs/components/SwitchToggle/SwitchToggle.cjs.map +1 -1
  61. package/dist/cjs/components/Tabs/Tabs.cjs.map +1 -1
  62. package/dist/cjs/components/Tabs/TabsContent.cjs.map +1 -1
  63. package/dist/cjs/components/Tabs/TabsList.cjs.map +1 -1
  64. package/dist/cjs/components/Tabs/TabsTrigger.cjs +2 -0
  65. package/dist/cjs/components/Tabs/TabsTrigger.cjs.map +1 -1
  66. package/dist/cjs/components/TextField/TextField.cjs +1 -1
  67. package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
  68. package/dist/cjs/components/Toast/Toast.cjs +1 -1
  69. package/dist/cjs/components/Toast/Toast.cjs.map +1 -1
  70. package/dist/cjs/components/Tooltip/Tooltip.cjs.map +1 -1
  71. package/dist/cjs/index.cjs +4 -0
  72. package/dist/cjs/index.cjs.map +1 -1
  73. package/dist/components/Alert/Alert.mjs +4 -4
  74. package/dist/components/Alert/Alert.mjs.map +1 -1
  75. package/dist/components/Avatar/Avatar.mjs.map +1 -1
  76. package/dist/components/Badge/Badge.mjs.map +1 -1
  77. package/dist/components/Button/Button.mjs +9 -9
  78. package/dist/components/Button/Button.mjs.map +1 -1
  79. package/dist/components/Checkbox/Checkbox.mjs +6 -7
  80. package/dist/components/Checkbox/Checkbox.mjs.map +1 -1
  81. package/dist/components/Chip/Chip.mjs +2 -2
  82. package/dist/components/Chip/Chip.mjs.map +1 -1
  83. package/dist/components/Count/Count.mjs.map +1 -1
  84. package/dist/components/DatePicker/DatePicker.mjs +30 -7
  85. package/dist/components/DatePicker/DatePicker.mjs.map +1 -1
  86. package/dist/components/Divider/Divider.mjs.map +1 -1
  87. package/dist/components/IconButton/IconButton.mjs +10 -10
  88. package/dist/components/IconButton/IconButton.mjs.map +1 -1
  89. package/dist/components/Icons/ArrowRightIcon.mjs.map +1 -1
  90. package/dist/components/Icons/ArrowUpRightIcon.mjs.map +1 -1
  91. package/dist/components/Icons/CheckCircleIcon.mjs.map +1 -1
  92. package/dist/components/Icons/ChevronLeftIcon.mjs.map +1 -1
  93. package/dist/components/Icons/ChevronRightIcon.mjs.map +1 -1
  94. package/dist/components/Icons/CloseIcon.mjs.map +1 -1
  95. package/dist/components/Icons/CrossIcon.mjs.map +1 -1
  96. package/dist/components/Icons/CrownIcon.mjs.map +1 -1
  97. package/dist/components/Icons/ErrorCircleIcon.mjs.map +1 -1
  98. package/dist/components/Icons/ErrorIcon.mjs.map +1 -1
  99. package/dist/components/Icons/EyeClosedIcon.mjs +32 -0
  100. package/dist/components/Icons/EyeClosedIcon.mjs.map +1 -0
  101. package/dist/components/Icons/EyeIcon.mjs.map +1 -1
  102. package/dist/components/Icons/FireIcon.mjs.map +1 -1
  103. package/dist/components/Icons/HomeIcon.mjs.map +1 -1
  104. package/dist/components/Icons/InfoCircleIcon.mjs.map +1 -1
  105. package/dist/components/Icons/InfoIcon.mjs.map +1 -1
  106. package/dist/components/Icons/MicrophoneIcon.mjs.map +1 -1
  107. package/dist/components/Icons/PlusIcon.mjs.map +1 -1
  108. package/dist/components/Icons/SpinnerIcon.mjs.map +1 -1
  109. package/dist/components/Icons/StopIcon.mjs.map +1 -1
  110. package/dist/components/Icons/SuccessIcon.mjs.map +1 -1
  111. package/dist/components/Icons/WarningIcon.mjs.map +1 -1
  112. package/dist/components/Icons/WarningTriangleIcon.mjs.map +1 -1
  113. package/dist/components/Logo/Logo.mjs +11 -11
  114. package/dist/components/Logo/Logo.mjs.map +1 -1
  115. package/dist/components/Pagination/Pagination.mjs +3 -3
  116. package/dist/components/Pagination/Pagination.mjs.map +1 -1
  117. package/dist/components/PasswordField/PasswordField.mjs +42 -0
  118. package/dist/components/PasswordField/PasswordField.mjs.map +1 -0
  119. package/dist/components/Pill/Pill.mjs.map +1 -1
  120. package/dist/components/ProgressBar/ProgressBar.mjs.map +1 -1
  121. package/dist/components/Radio/Radio.mjs +1 -1
  122. package/dist/components/Radio/Radio.mjs.map +1 -1
  123. package/dist/components/RadioGroup/RadioGroup.mjs.map +1 -1
  124. package/dist/components/Slider/Slider.mjs.map +1 -1
  125. package/dist/components/Slider/SliderThumb.mjs +1 -0
  126. package/dist/components/Slider/SliderThumb.mjs.map +1 -1
  127. package/dist/components/Snackbar/Snackbar.mjs.map +1 -1
  128. package/dist/components/Switch/Switch.mjs +1 -0
  129. package/dist/components/Switch/Switch.mjs.map +1 -1
  130. package/dist/components/SwitchField/SwitchField.mjs.map +1 -1
  131. package/dist/components/SwitchToggle/SwitchToggle.mjs +48 -31
  132. package/dist/components/SwitchToggle/SwitchToggle.mjs.map +1 -1
  133. package/dist/components/Tabs/Tabs.mjs.map +1 -1
  134. package/dist/components/Tabs/TabsContent.mjs.map +1 -1
  135. package/dist/components/Tabs/TabsList.mjs.map +1 -1
  136. package/dist/components/Tabs/TabsTrigger.mjs +2 -0
  137. package/dist/components/Tabs/TabsTrigger.mjs.map +1 -1
  138. package/dist/components/TextField/TextField.mjs +1 -1
  139. package/dist/components/TextField/TextField.mjs.map +1 -1
  140. package/dist/components/Toast/Toast.mjs +1 -1
  141. package/dist/components/Toast/Toast.mjs.map +1 -1
  142. package/dist/components/Tooltip/Tooltip.mjs.map +1 -1
  143. package/dist/index.d.ts +535 -142
  144. package/dist/index.mjs +4 -0
  145. package/dist/index.mjs.map +1 -1
  146. package/dist/styles/theme.css +9 -0
  147. package/package.json +3 -2
@@ -82,8 +82,8 @@ const Pagination = React.forwardRef(
82
82
  "aria-current": page === currentPage ? "page" : void 0,
83
83
  onClick: () => onPageChange?.(page),
84
84
  className: cn(
85
- "flex size-4 cursor-pointer items-center justify-center rounded-full text-xs motion-safe:transition-colors motion-safe:duration-150 focus-visible:shadow-focus-ring focus-visible:outline-none",
86
- page === currentPage ? "bg-neutral-400 text-body-300" : "bg-neutral-100 text-body-100 hover:bg-neutral-200"
85
+ "flex size-4 cursor-pointer items-center justify-center rounded-full text-xs focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150",
86
+ page === currentPage ? "bg-neutral-400 text-body-300" : "bg-neutral-100 text-body-100 hover:bg-neutral-200 active:bg-neutral-200"
87
87
  ),
88
88
  children: page
89
89
  },
@@ -103,7 +103,7 @@ const Pagination = React.forwardRef(
103
103
  {
104
104
  className: cn(
105
105
  "block rounded-full motion-safe:transition-all motion-safe:duration-150",
106
- page === currentPage ? "size-2 bg-neutral-400" : "size-1.5 bg-neutral-200 hover:bg-neutral-250"
106
+ page === currentPage ? "size-2 bg-neutral-400" : "size-1.5 bg-neutral-200 hover:bg-neutral-250 active:bg-neutral-250"
107
107
  )
108
108
  }
109
109
  )
@@ -1 +1 @@
1
- {"version":3,"file":"Pagination.mjs","sources":["../../../src/components/Pagination/Pagination.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport type PaginationVariant = \"default\" | \"dots\";\n\nexport interface PaginationProps extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Visual style variant */\n variant?: PaginationVariant;\n /** Total number of pages */\n totalPages: number;\n /** Current active page (1-indexed) */\n currentPage: number;\n /** Called when the page changes */\n onPageChange?: (page: number) => void;\n /** Label for the nav landmark. @default \"Pagination\" */\n ariaLabel?: string;\n /** Label for the previous button. @default \"Previous page\" */\n previousLabel?: string;\n /** Label for the next button. @default \"Next page\" */\n nextLabel?: string;\n /** Generates the aria-label for each page button. @default (page) => \\`Page ${page}\\` */\n getPageLabel?: (page: number) => string;\n}\n\ntype PageItem = number | \"ellipsis-start\" | \"ellipsis-end\";\n\nfunction getVisiblePages(currentPage: number, totalPages: number): PageItem[] {\n if (totalPages <= 7) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages: PageItem[] = [1];\n\n if (currentPage <= 4) {\n pages.push(2, 3, 4, 5, \"ellipsis-end\");\n } else if (currentPage >= totalPages - 3) {\n pages.push(\"ellipsis-start\", totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1);\n } else {\n pages.push(\"ellipsis-start\", currentPage - 1, currentPage, currentPage + 1, \"ellipsis-end\");\n }\n\n pages.push(totalPages);\n return pages;\n}\n\nexport const Pagination = React.forwardRef<HTMLElement, PaginationProps>(\n (\n {\n variant = \"default\",\n totalPages,\n currentPage,\n onPageChange,\n ariaLabel = \"Pagination\",\n previousLabel = \"Previous page\",\n nextLabel = \"Next page\",\n getPageLabel = (page: number) => `Page ${page}`,\n className,\n ...props\n },\n ref,\n ) => {\n const isFirstPage = currentPage <= 1;\n const isLastPage = currentPage >= totalPages;\n\n const handlePrevious = () => {\n if (!isFirstPage) onPageChange?.(currentPage - 1);\n };\n\n const handleNext = () => {\n if (!isLastPage) onPageChange?.(currentPage + 1);\n };\n\n return (\n <nav\n ref={ref}\n aria-label={ariaLabel}\n className={cn(\n \"inline-flex items-center\",\n variant === \"default\" && \"gap-3\",\n variant === \"dots\" && \"gap-4\",\n className,\n )}\n {...props}\n >\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ChevronLeftIcon />}\n aria-label={previousLabel}\n disabled={isFirstPage}\n onClick={handlePrevious}\n />\n\n {variant === \"default\" && (\n <div className=\"flex items-center gap-3\">\n {getVisiblePages(currentPage, totalPages).map((page) =>\n typeof page === \"string\" ? (\n <span\n key={page}\n className=\"flex size-4 items-center justify-center text-body-200 text-xs\"\n aria-hidden=\"true\"\n >\n &hellip;\n </span>\n ) : (\n <button\n key={page}\n type=\"button\"\n aria-label={getPageLabel(page)}\n aria-current={page === currentPage ? \"page\" : undefined}\n onClick={() => onPageChange?.(page)}\n className={cn(\n \"flex size-4 cursor-pointer items-center justify-center rounded-full text-xs motion-safe:transition-colors motion-safe:duration-150 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n page === currentPage\n ? \"bg-neutral-400 text-body-300\"\n : \"bg-neutral-100 text-body-100 hover:bg-neutral-200\",\n )}\n >\n {page}\n </button>\n ),\n )}\n </div>\n )}\n\n {variant === \"dots\" && (\n <div className=\"flex items-center\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (\n <button\n key={page}\n type=\"button\"\n aria-label={getPageLabel(page)}\n aria-current={page === currentPage ? \"page\" : undefined}\n onClick={() => onPageChange?.(page)}\n className=\"flex size-6 cursor-pointer items-center justify-center rounded-full focus-visible:shadow-focus-ring focus-visible:outline-none\"\n >\n <span\n className={cn(\n \"block rounded-full motion-safe:transition-all motion-safe:duration-150\",\n page === currentPage\n ? \"size-2 bg-neutral-400\"\n : \"size-1.5 bg-neutral-200 hover:bg-neutral-250\",\n )}\n />\n </button>\n ))}\n </div>\n )}\n\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ChevronRightIcon />}\n aria-label={nextLabel}\n disabled={isLastPage}\n onClick={handleNext}\n />\n </nav>\n );\n },\n);\n\nPagination.displayName = \"Pagination\";\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Pagination.mjs","sources":["../../../src/components/Pagination/Pagination.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\n/** Pagination display style — numbered buttons or minimal dots. */\nexport type PaginationVariant = \"default\" | \"dots\";\n\nexport interface PaginationProps extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Display style numbered page buttons or minimal dots. @default \"default\" */\n variant?: PaginationVariant;\n /** Total number of pages. */\n totalPages: number;\n /** Current active page (1-indexed). */\n currentPage: number;\n /** Callback fired when the active page changes. Receives the new 1-indexed page number. */\n onPageChange?: (page: number) => void;\n /** Accessible label for the `<nav>` landmark. @default \"Pagination\" */\n ariaLabel?: string;\n /** Accessible label for the previous-page button. @default \"Previous page\" */\n previousLabel?: string;\n /** Accessible label for the next-page button. @default \"Next page\" */\n nextLabel?: string;\n /** Function that returns an accessible label for each page button. @default (page) => \\`Page ${page}\\` */\n getPageLabel?: (page: number) => string;\n}\n\ntype PageItem = number | \"ellipsis-start\" | \"ellipsis-end\";\n\nfunction getVisiblePages(currentPage: number, totalPages: number): PageItem[] {\n if (totalPages <= 7) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages: PageItem[] = [1];\n\n if (currentPage <= 4) {\n pages.push(2, 3, 4, 5, \"ellipsis-end\");\n } else if (currentPage >= totalPages - 3) {\n pages.push(\"ellipsis-start\", totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1);\n } else {\n pages.push(\"ellipsis-start\", currentPage - 1, currentPage, currentPage + 1, \"ellipsis-end\");\n }\n\n pages.push(totalPages);\n return pages;\n}\n\n/**\n * Page navigation control with previous/next buttons and numbered page\n * indicators. Supports a numbered-buttons layout (`\"default\"`) and a compact\n * dots layout (`\"dots\"`).\n *\n * @example\n * ```tsx\n * <Pagination totalPages={10} currentPage={page} onPageChange={setPage} />\n * ```\n */\nexport const Pagination = React.forwardRef<HTMLElement, PaginationProps>(\n (\n {\n variant = \"default\",\n totalPages,\n currentPage,\n onPageChange,\n ariaLabel = \"Pagination\",\n previousLabel = \"Previous page\",\n nextLabel = \"Next page\",\n getPageLabel = (page: number) => `Page ${page}`,\n className,\n ...props\n },\n ref,\n ) => {\n const isFirstPage = currentPage <= 1;\n const isLastPage = currentPage >= totalPages;\n\n const handlePrevious = () => {\n if (!isFirstPage) onPageChange?.(currentPage - 1);\n };\n\n const handleNext = () => {\n if (!isLastPage) onPageChange?.(currentPage + 1);\n };\n\n return (\n <nav\n ref={ref}\n aria-label={ariaLabel}\n className={cn(\n \"inline-flex items-center\",\n variant === \"default\" && \"gap-3\",\n variant === \"dots\" && \"gap-4\",\n className,\n )}\n {...props}\n >\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ChevronLeftIcon />}\n aria-label={previousLabel}\n disabled={isFirstPage}\n onClick={handlePrevious}\n />\n\n {variant === \"default\" && (\n <div className=\"flex items-center gap-3\">\n {getVisiblePages(currentPage, totalPages).map((page) =>\n typeof page === \"string\" ? (\n <span\n key={page}\n className=\"flex size-4 items-center justify-center text-body-200 text-xs\"\n aria-hidden=\"true\"\n >\n &hellip;\n </span>\n ) : (\n <button\n key={page}\n type=\"button\"\n aria-label={getPageLabel(page)}\n aria-current={page === currentPage ? \"page\" : undefined}\n onClick={() => onPageChange?.(page)}\n className={cn(\n \"flex size-4 cursor-pointer items-center justify-center rounded-full text-xs focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150\",\n page === currentPage\n ? \"bg-neutral-400 text-body-300\"\n : \"bg-neutral-100 text-body-100 hover:bg-neutral-200 active:bg-neutral-200\",\n )}\n >\n {page}\n </button>\n ),\n )}\n </div>\n )}\n\n {variant === \"dots\" && (\n <div className=\"flex items-center\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (\n <button\n key={page}\n type=\"button\"\n aria-label={getPageLabel(page)}\n aria-current={page === currentPage ? \"page\" : undefined}\n onClick={() => onPageChange?.(page)}\n className=\"flex size-6 cursor-pointer items-center justify-center rounded-full focus-visible:shadow-focus-ring focus-visible:outline-none\"\n >\n <span\n className={cn(\n \"block rounded-full motion-safe:transition-all motion-safe:duration-150\",\n page === currentPage\n ? \"size-2 bg-neutral-400\"\n : \"size-1.5 bg-neutral-200 hover:bg-neutral-250 active:bg-neutral-250\",\n )}\n />\n </button>\n ))}\n </div>\n )}\n\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ChevronRightIcon />}\n aria-label={nextLabel}\n disabled={isLastPage}\n onClick={handleNext}\n />\n </nav>\n );\n },\n);\n\nPagination.displayName = \"Pagination\";\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { EyeClosedIcon } from "../Icons/EyeClosedIcon.mjs";
5
+ import { EyeIcon } from "../Icons/EyeIcon.mjs";
6
+ import { TextField } from "../TextField/TextField.mjs";
7
+ const PasswordField = React.forwardRef(
8
+ ({ disabled, ...props }, ref) => {
9
+ const [showPassword, setShowPassword] = React.useState(false);
10
+ const togglePasswordVisibility = () => {
11
+ setShowPassword((prev) => !prev);
12
+ };
13
+ const rightIcon = /* @__PURE__ */ jsx(
14
+ "button",
15
+ {
16
+ type: "button",
17
+ onClick: togglePasswordVisibility,
18
+ disabled,
19
+ "aria-label": showPassword ? "Hide password" : "Show password",
20
+ tabIndex: -1,
21
+ className: "flex size-5 shrink-0 items-center justify-center text-body-200 transition-colors hover:text-body-100 focus:outline-none disabled:cursor-not-allowed",
22
+ children: showPassword ? /* @__PURE__ */ jsx(EyeClosedIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
23
+ }
24
+ );
25
+ return /* @__PURE__ */ jsx(
26
+ TextField,
27
+ {
28
+ ref,
29
+ type: showPassword ? "text" : "password",
30
+ disabled,
31
+ rightIcon,
32
+ "aria-label": !props.label ? "Password field" : void 0,
33
+ ...props
34
+ }
35
+ );
36
+ }
37
+ );
38
+ PasswordField.displayName = "PasswordField";
39
+ export {
40
+ PasswordField
41
+ };
42
+ //# sourceMappingURL=PasswordField.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordField.mjs","sources":["../../../src/components/PasswordField/PasswordField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { EyeClosedIcon } from \"../Icons/EyeClosedIcon\";\nimport { EyeIcon } from \"../Icons/EyeIcon\";\nimport { TextField, type TextFieldProps } from \"../TextField/TextField\";\n\nexport type PasswordFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface PasswordFieldProps extends Omit<TextFieldProps, \"type\" | \"rightIcon\"> {\n /** Size variant of the password field */\n size?: PasswordFieldSize;\n}\n\nexport const PasswordField = React.forwardRef<HTMLInputElement, PasswordFieldProps>(\n ({ disabled, ...props }, ref) => {\n const [showPassword, setShowPassword] = React.useState(false);\n\n const togglePasswordVisibility = () => {\n setShowPassword((prev) => !prev);\n };\n\n const rightIcon = (\n <button\n type=\"button\"\n onClick={togglePasswordVisibility}\n disabled={disabled}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n tabIndex={-1}\n className=\"flex size-5 shrink-0 items-center justify-center text-body-200 transition-colors hover:text-body-100 focus:outline-none disabled:cursor-not-allowed\"\n >\n {showPassword ? <EyeClosedIcon /> : <EyeIcon />}\n </button>\n );\n\n return (\n <TextField\n ref={ref}\n type={showPassword ? \"text\" : \"password\"}\n disabled={disabled}\n rightIcon={rightIcon}\n aria-label={!props.label ? \"Password field\" : undefined}\n {...props}\n />\n );\n },\n);\n\nPasswordField.displayName = \"PasswordField\";\n"],"names":[],"mappings":";;;;;;AAYO,MAAM,gBAAgB,MAAM;AAAA,EACjC,CAAC,EAAE,UAAU,GAAG,MAAA,GAAS,QAAQ;AAC/B,UAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAE5D,UAAM,2BAA2B,MAAM;AACrC,sBAAgB,CAAC,SAAS,CAAC,IAAI;AAAA,IACjC;AAEA,UAAM,YACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,cAAY,eAAe,kBAAkB;AAAA,QAC7C,UAAU;AAAA,QACV,WAAU;AAAA,QAET,UAAA,eAAe,oBAAC,eAAA,CAAA,CAAc,wBAAM,SAAA,CAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAIjD,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAM,eAAe,SAAS;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,cAAY,CAAC,MAAM,QAAQ,mBAAmB;AAAA,QAC7C,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF;AAEA,cAAc,cAAc;"}
@@ -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-50 text-success-500\",\n grey: \"bg-neutral-100 text-body-200\",\n blue: \"bg-info-50 text-info-500\",\n gold: \"bg-warning-50 text-warning-500\",\n pinkLight: \"bg-brand-pink-50 text-body-100\",\n base: \"bg-neutral-400 text-body-300\",\n brand: \"bg-brand-green-500 text-body-black-solid-constant\",\n brandLight: \"bg-brand-green-50 text-body-black-solid-constant\",\n beta: \"bg-brand-pink-500 text-body-black-solid-constant\",\n error: \"bg-error-500 text-error-50\",\n },\n} as const;\n\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 /** Visual style variant of the pill */\n variant?: PillVariant;\n /** Left icon element */\n leftIcon?: React.ReactNode;\n /** Right icon element */\n rightIcon?: React.ReactNode;\n /** Render as a different element using Radix Slot */\n asChild?: boolean;\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-caption-semibold\",\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;AAyBO,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-50 text-success-500\",\n grey: \"bg-neutral-100 text-body-200\",\n blue: \"bg-info-50 text-info-500\",\n gold: \"bg-warning-50 text-warning-500\",\n pinkLight: \"bg-brand-pink-50 text-body-100\",\n base: \"bg-neutral-400 text-body-300\",\n brand: \"bg-brand-green-500 text-body-black-solid-constant\",\n brandLight: \"bg-brand-green-50 text-body-black-solid-constant\",\n beta: \"bg-brand-pink-500 text-body-black-solid-constant\",\n error: \"bg-error-500 text-error-50\",\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-caption-semibold\",\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 +1 @@
1
- {"version":3,"file":"ProgressBar.mjs","sources":["../../../src/components/ProgressBar/ProgressBar.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\n\nexport type ProgressBarSize = \"default\" | \"small\";\nexport type ProgressBarVariant = \"default\" | \"generic\";\n\nexport interface ProgressBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Current progress value (0-100) */\n value: number;\n /** Size variant - \"default\" (12px track) or \"small\" (6px track) */\n size?: ProgressBarSize;\n /** Color variant - \"default\" uses color-coded progress, \"generic\" always green */\n variant?: ProgressBarVariant;\n /** Title content shown at top left */\n title?: React.ReactNode;\n /** Show the completion percentage at top */\n showCompletion?: boolean;\n /** Steps label shown at top right (e.g. \"2/8 steps\") */\n stepsLabel?: React.ReactNode;\n /** Helper content at bottom left */\n helperLeft?: React.ReactNode;\n /** Helper content at bottom right */\n helperRight?: React.ReactNode;\n /** Icon shown at bottom left before helper text */\n leftIcon?: React.ReactNode;\n /** Accessible label for the progress bar (defaults to \"Progress\"). Use this for i18n. */\n ariaLabel?: string;\n}\n\nconst TRACK_HEIGHT: Record<ProgressBarSize, string> = {\n default: \"h-3\",\n small: \"h-1.5\",\n};\n\nconst GAP: Record<ProgressBarSize, string> = {\n default: \"gap-3\",\n small: \"gap-1\",\n};\n\nfunction getDefaultBarColor(value: number): string {\n if (value >= 100) return \"bg-success-500\";\n if (value >= 40) return \"bg-warning-500\";\n return \"bg-error-500\";\n}\n\nfunction getDefaultTextColor(value: number): string {\n if (value >= 100) return \"text-success-500\";\n if (value >= 40) return \"text-warning-500\";\n return \"text-error-500\";\n}\n\nexport const ProgressBar = React.forwardRef<HTMLDivElement, ProgressBarProps>(\n (\n {\n value,\n size = \"default\",\n variant = \"default\",\n title,\n showCompletion = false,\n stepsLabel,\n helperLeft,\n helperRight,\n leftIcon,\n ariaLabel,\n className,\n ...props\n },\n ref,\n ) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n const isGeneric = variant === \"generic\";\n const isSmall = size === \"small\";\n\n const barColor = isGeneric ? \"bg-brand-green-500\" : getDefaultBarColor(clampedValue);\n const textColor = isGeneric ? \"text-brand-green-500\" : getDefaultTextColor(clampedValue);\n\n const showHeader = title != null || showCompletion || stepsLabel != null;\n const showFooter = leftIcon != null || helperLeft != null || helperRight != null;\n\n return (\n <div ref={ref} className={cn(\"flex w-full flex-col\", GAP[size], className)} {...props}>\n {showHeader && (\n <div className=\"flex w-full items-end justify-between\">\n {title != null && <p className=\"typography-caption-semibold text-body-100\">{title}</p>}\n {showCompletion && (\n <span\n className={cn(textColor, isSmall ? \"typography-heading-3\" : \"typography-heading-1\")}\n >\n {Math.round(clampedValue)}%\n </span>\n )}\n {stepsLabel != null && (\n <span className=\"typography-caption-regular text-body-100\">{stepsLabel}</span>\n )}\n </div>\n )}\n\n <div\n role=\"progressbar\"\n aria-label={ariaLabel ?? \"Progress\"}\n aria-valuenow={clampedValue}\n aria-valuemin={0}\n aria-valuemax={100}\n className={cn(\"relative w-full rounded-full bg-neutral-100\", TRACK_HEIGHT[size])}\n >\n <div\n className={cn(\n \"absolute inset-y-0 left-0 rounded-full transition-[width] duration-300 ease-in-out\",\n barColor,\n )}\n style={{ width: `${clampedValue}%` }}\n />\n </div>\n\n {showFooter && (\n <div className=\"flex w-full items-center justify-between\">\n <div className=\"flex items-center gap-1\">\n {leftIcon != null && (\n <span className=\"flex size-5 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n {helperLeft != null && (\n <span className=\"typography-caption-regular text-primary-500\">{helperLeft}</span>\n )}\n </div>\n {helperRight != null && (\n <span className=\"typography-caption-regular text-primary-500\">{helperRight}</span>\n )}\n </div>\n )}\n </div>\n );\n },\n);\n\nProgressBar.displayName = \"ProgressBar\";\n"],"names":[],"mappings":";;;;AA6BA,MAAM,eAAgD;AAAA,EACpD,SAAS;AAAA,EACT,OAAO;AACT;AAEA,MAAM,MAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,OAAO;AACT;AAEA,SAAS,mBAAmB,OAAuB;AACjD,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEO,MAAM,cAAc,MAAM;AAAA,EAC/B,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,eAAe,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AACrD,UAAM,YAAY,YAAY;AAC9B,UAAM,UAAU,SAAS;AAEzB,UAAM,WAAW,YAAY,uBAAuB,mBAAmB,YAAY;AACnF,UAAM,YAAY,YAAY,yBAAyB,oBAAoB,YAAY;AAEvF,UAAM,aAAa,SAAS,QAAQ,kBAAkB,cAAc;AACpE,UAAM,aAAa,YAAY,QAAQ,cAAc,QAAQ,eAAe;AAE5E,WACE,qBAAC,OAAA,EAAI,KAAU,WAAW,GAAG,wBAAwB,IAAI,IAAI,GAAG,SAAS,GAAI,GAAG,OAC7E,UAAA;AAAA,MAAA,cACC,qBAAC,OAAA,EAAI,WAAU,yCACZ,UAAA;AAAA,QAAA,SAAS,QAAQ,oBAAC,KAAA,EAAE,WAAU,6CAA6C,UAAA,OAAM;AAAA,QACjF,kBACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,GAAG,WAAW,UAAU,yBAAyB,sBAAsB;AAAA,YAEjF,UAAA;AAAA,cAAA,KAAK,MAAM,YAAY;AAAA,cAAE;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAG7B,cAAc,QACb,oBAAC,QAAA,EAAK,WAAU,4CAA4C,UAAA,WAAA,CAAW;AAAA,MAAA,GAE3E;AAAA,MAGF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY,aAAa;AAAA,UACzB,iBAAe;AAAA,UACf,iBAAe;AAAA,UACf,iBAAe;AAAA,UACf,WAAW,GAAG,+CAA+C,aAAa,IAAI,CAAC;AAAA,UAE/E,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cAAA;AAAA,cAEF,OAAO,EAAE,OAAO,GAAG,YAAY,IAAA;AAAA,YAAI;AAAA,UAAA;AAAA,QACrC;AAAA,MAAA;AAAA,MAGD,cACC,qBAAC,OAAA,EAAI,WAAU,4CACb,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,UAAA,YAAY,QACX,oBAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QACnE,UAAA,SAAA,CACH;AAAA,UAED,cAAc,QACb,oBAAC,QAAA,EAAK,WAAU,+CAA+C,UAAA,WAAA,CAAW;AAAA,QAAA,GAE9E;AAAA,QACC,eAAe,QACd,oBAAC,QAAA,EAAK,WAAU,+CAA+C,UAAA,YAAA,CAAY;AAAA,MAAA,EAAA,CAE/E;AAAA,IAAA,GAEJ;AAAA,EAEJ;AACF;AAEA,YAAY,cAAc;"}
1
+ {"version":3,"file":"ProgressBar.mjs","sources":["../../../src/components/ProgressBar/ProgressBar.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\n\n/** Track height — `\"default\"` (12px) or `\"small\"` (6px). */\nexport type ProgressBarSize = \"default\" | \"small\";\n/** Colour mode — `\"default\"` uses red/yellow/green by value, `\"generic\"` always uses brand green. */\nexport type ProgressBarVariant = \"default\" | \"generic\";\n\nexport interface ProgressBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Current progress value, clamped to 0100. */\n value: number;\n /** Track height `\"default\"` (12px) or `\"small\"` (6px). @default \"default\" */\n size?: ProgressBarSize;\n /** Colour mode `\"default\"` is colour-coded by value, `\"generic\"` always uses brand green. @default \"default\" */\n variant?: ProgressBarVariant;\n /** Title content shown at the top-left of the bar. */\n title?: React.ReactNode;\n /** Whether to display the completion percentage above the track. @default false */\n showCompletion?: boolean;\n /** Steps label shown at the top-right (e.g. `\"2/8 steps\"`). */\n stepsLabel?: React.ReactNode;\n /** Helper content at the bottom-left of the bar. */\n helperLeft?: React.ReactNode;\n /** Helper content at the bottom-right of the bar. */\n helperRight?: React.ReactNode;\n /** Icon shown at the bottom-left before the helper text. */\n leftIcon?: React.ReactNode;\n /** Accessible label for the `progressbar` role. @default \"Progress\" */\n ariaLabel?: string;\n}\n\nconst TRACK_HEIGHT: Record<ProgressBarSize, string> = {\n default: \"h-3\",\n small: \"h-1.5\",\n};\n\nconst GAP: Record<ProgressBarSize, string> = {\n default: \"gap-3\",\n small: \"gap-1\",\n};\n\nfunction getDefaultBarColor(value: number): string {\n if (value >= 100) return \"bg-success-500\";\n if (value >= 40) return \"bg-warning-500\";\n return \"bg-error-500\";\n}\n\nfunction getDefaultTextColor(value: number): string {\n if (value >= 100) return \"text-success-500\";\n if (value >= 40) return \"text-warning-500\";\n return \"text-error-500\";\n}\n\n/**\n * A horizontal progress indicator with optional title, completion percentage,\n * step count, and helper text. The bar colour reflects progress when using the\n * `\"default\"` variant.\n *\n * @example\n * ```tsx\n * <ProgressBar value={65} title=\"Upload\" showCompletion />\n * ```\n */\nexport const ProgressBar = React.forwardRef<HTMLDivElement, ProgressBarProps>(\n (\n {\n value,\n size = \"default\",\n variant = \"default\",\n title,\n showCompletion = false,\n stepsLabel,\n helperLeft,\n helperRight,\n leftIcon,\n ariaLabel,\n className,\n ...props\n },\n ref,\n ) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n const isGeneric = variant === \"generic\";\n const isSmall = size === \"small\";\n\n const barColor = isGeneric ? \"bg-brand-green-500\" : getDefaultBarColor(clampedValue);\n const textColor = isGeneric ? \"text-brand-green-500\" : getDefaultTextColor(clampedValue);\n\n const showHeader = title != null || showCompletion || stepsLabel != null;\n const showFooter = leftIcon != null || helperLeft != null || helperRight != null;\n\n return (\n <div ref={ref} className={cn(\"flex w-full flex-col\", GAP[size], className)} {...props}>\n {showHeader && (\n <div className=\"flex w-full items-end justify-between\">\n {title != null && <p className=\"typography-caption-semibold text-body-100\">{title}</p>}\n {showCompletion && (\n <span\n className={cn(textColor, isSmall ? \"typography-heading-3\" : \"typography-heading-1\")}\n >\n {Math.round(clampedValue)}%\n </span>\n )}\n {stepsLabel != null && (\n <span className=\"typography-caption-regular text-body-100\">{stepsLabel}</span>\n )}\n </div>\n )}\n\n <div\n role=\"progressbar\"\n aria-label={ariaLabel ?? \"Progress\"}\n aria-valuenow={clampedValue}\n aria-valuemin={0}\n aria-valuemax={100}\n className={cn(\"relative w-full rounded-full bg-neutral-100\", TRACK_HEIGHT[size])}\n >\n <div\n className={cn(\n \"absolute inset-y-0 left-0 rounded-full transition-[width] duration-300 ease-in-out\",\n barColor,\n )}\n style={{ width: `${clampedValue}%` }}\n />\n </div>\n\n {showFooter && (\n <div className=\"flex w-full items-center justify-between\">\n <div className=\"flex items-center gap-1\">\n {leftIcon != null && (\n <span className=\"flex size-5 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n {helperLeft != null && (\n <span className=\"typography-caption-regular text-primary-500\">{helperLeft}</span>\n )}\n </div>\n {helperRight != null && (\n <span className=\"typography-caption-regular text-primary-500\">{helperRight}</span>\n )}\n </div>\n )}\n </div>\n );\n },\n);\n\nProgressBar.displayName = \"ProgressBar\";\n"],"names":[],"mappings":";;;;AA+BA,MAAM,eAAgD;AAAA,EACpD,SAAS;AAAA,EACT,OAAO;AACT;AAEA,MAAM,MAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,OAAO;AACT;AAEA,SAAS,mBAAmB,OAAuB;AACjD,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAYO,MAAM,cAAc,MAAM;AAAA,EAC/B,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,eAAe,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AACrD,UAAM,YAAY,YAAY;AAC9B,UAAM,UAAU,SAAS;AAEzB,UAAM,WAAW,YAAY,uBAAuB,mBAAmB,YAAY;AACnF,UAAM,YAAY,YAAY,yBAAyB,oBAAoB,YAAY;AAEvF,UAAM,aAAa,SAAS,QAAQ,kBAAkB,cAAc;AACpE,UAAM,aAAa,YAAY,QAAQ,cAAc,QAAQ,eAAe;AAE5E,WACE,qBAAC,OAAA,EAAI,KAAU,WAAW,GAAG,wBAAwB,IAAI,IAAI,GAAG,SAAS,GAAI,GAAG,OAC7E,UAAA;AAAA,MAAA,cACC,qBAAC,OAAA,EAAI,WAAU,yCACZ,UAAA;AAAA,QAAA,SAAS,QAAQ,oBAAC,KAAA,EAAE,WAAU,6CAA6C,UAAA,OAAM;AAAA,QACjF,kBACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,GAAG,WAAW,UAAU,yBAAyB,sBAAsB;AAAA,YAEjF,UAAA;AAAA,cAAA,KAAK,MAAM,YAAY;AAAA,cAAE;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAG7B,cAAc,QACb,oBAAC,QAAA,EAAK,WAAU,4CAA4C,UAAA,WAAA,CAAW;AAAA,MAAA,GAE3E;AAAA,MAGF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY,aAAa;AAAA,UACzB,iBAAe;AAAA,UACf,iBAAe;AAAA,UACf,iBAAe;AAAA,UACf,WAAW,GAAG,+CAA+C,aAAa,IAAI,CAAC;AAAA,UAE/E,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cAAA;AAAA,cAEF,OAAO,EAAE,OAAO,GAAG,YAAY,IAAA;AAAA,YAAI;AAAA,UAAA;AAAA,QACrC;AAAA,MAAA;AAAA,MAGD,cACC,qBAAC,OAAA,EAAI,WAAU,4CACb,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,UAAA,YAAY,QACX,oBAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QACnE,UAAA,SAAA,CACH;AAAA,UAED,cAAc,QACb,oBAAC,QAAA,EAAK,WAAU,+CAA+C,UAAA,WAAA,CAAW;AAAA,QAAA,GAE9E;AAAA,QACC,eAAe,QACd,oBAAC,QAAA,EAAK,WAAU,+CAA+C,UAAA,YAAA,CAAY;AAAA,MAAA,EAAA,CAE/E;AAAA,IAAA,GAEJ;AAAA,EAEJ;AACF;AAEA,YAAY,cAAc;"}
@@ -16,7 +16,7 @@ const Radio = React.forwardRef(({ className, size = "default", label, helperText
16
16
  "data-testid": "radio",
17
17
  "aria-describedby": helperText ? helperTextId : void 0,
18
18
  className: cn(
19
- "relative h-4 w-4 shrink-0 cursor-pointer appearance-none rounded-full border border-body-100 bg-transparent outline-brand-purple-500 transition-colors hover:bg-brand-green-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:border-disabled-400 disabled:bg-transparent data-[state=checked]:border-body-100 data-[state=checked]:bg-transparent dark:border-body-100 dark:disabled:border-disabled-400",
19
+ "relative h-4 w-4 shrink-0 cursor-pointer appearance-none rounded-full border border-body-100 bg-transparent outline-brand-purple-500 transition-colors hover:bg-brand-green-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 not-disabled:active:bg-brand-green-50 disabled:cursor-not-allowed disabled:border-disabled-400 disabled:bg-transparent data-[state=checked]:border-body-100 data-[state=checked]:bg-transparent dark:border-body-100 dark:disabled:border-disabled-400",
20
20
  helperText && "mt-1 self-start"
21
21
  ),
22
22
  ...props,
@@ -1 +1 @@
1
- {"version":3,"file":"Radio.mjs","sources":["../../../src/components/Radio/Radio.tsx"],"sourcesContent":["import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport interface RadioProps\n extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, \"asChild\"> {\n /** Size variant of the radio button */\n size?: \"default\" | \"small\";\n /** Label text for the radio button */\n label?: string;\n /** Optional helper text displayed below the label */\n helperText?: string;\n}\n\nexport const Radio = React.forwardRef<\n React.ComponentRef<typeof RadioGroupPrimitive.Item>,\n RadioProps\n>(({ className, size = \"default\", label, helperText, id, ...props }, ref) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n\n return (\n <div className={cn(\"group inline-flex items-center gap-2\", className)}>\n <RadioGroupPrimitive.Item\n ref={ref}\n id={inputId}\n data-testid=\"radio\"\n aria-describedby={helperText ? helperTextId : undefined}\n className={cn(\n \"relative h-4 w-4 shrink-0 cursor-pointer appearance-none rounded-full border border-body-100 bg-transparent outline-brand-purple-500 transition-colors hover:bg-brand-green-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:border-disabled-400 disabled:bg-transparent data-[state=checked]:border-body-100 data-[state=checked]:bg-transparent dark:border-body-100 dark:disabled:border-disabled-400\",\n helperText && \"mt-1 self-start\",\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"size-2 rounded-full bg-body-100 group-has-disabled:bg-disabled-400 dark:bg-body-100 dark:group-has-disabled:bg-disabled-400\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n {(label || helperText) && (\n <div className=\"flex flex-col gap-0.5\">\n {label && (\n <label\n htmlFor={inputId}\n className={cn(\n \"cursor-pointer select-none text-body-100 group-has-disabled:cursor-not-allowed group-has-disabled:text-disabled-100\",\n size === \"small\" ? \"typography-body-2-semibold\" : \"typography-body-1-semibold\",\n )}\n >\n {label}\n </label>\n )}\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"text-body-200 group-has-disabled:cursor-not-allowed group-has-disabled:text-disabled-100\",\n size === \"small\" ? \"typography-body-2-semibold\" : \"typography-caption-regular\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n )}\n </div>\n );\n});\n\nRadio.displayName = \"Radio\";\n"],"names":[],"mappings":";;;;;AAcO,MAAM,QAAQ,MAAM,WAGzB,CAAC,EAAE,WAAW,OAAO,WAAW,OAAO,YAAY,IAAI,GAAG,MAAA,GAAS,QAAQ;AAC3E,QAAM,cAAc,MAAM,MAAA;AAC1B,QAAM,UAAU,MAAM;AACtB,QAAM,eAAe,GAAG,OAAO;AAE/B,8BACG,OAAA,EAAI,WAAW,GAAG,wCAAwC,SAAS,GAClE,UAAA;AAAA,IAAA;AAAA,MAAC,oBAAoB;AAAA,MAApB;AAAA,QACC;AAAA,QACA,IAAI;AAAA,QACJ,eAAY;AAAA,QACZ,oBAAkB,aAAa,eAAe;AAAA,QAC9C,WAAW;AAAA,UACT;AAAA,UACA,cAAc;AAAA,QAAA;AAAA,QAEf,GAAG;AAAA,QAEJ,UAAA,oBAAC,oBAAoB,WAApB,EAA8B,WAAU,qDACvC,UAAA,oBAAC,QAAA,EAAK,WAAU,8HAAA,CAA8H,EAAA,CAChJ;AAAA,MAAA;AAAA,IAAA;AAAA,KAEA,SAAS,eACT,qBAAC,OAAA,EAAI,WAAU,yBACZ,UAAA;AAAA,MAAA,SACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAW;AAAA,YACT;AAAA,YACA,SAAS,UAAU,+BAA+B;AAAA,UAAA;AAAA,UAGnD,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGJ,cACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,YACA,SAAS,UAAU,+BAA+B;AAAA,UAAA;AAAA,UAGnD,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EAAA,CAEJ;AAAA,EAAA,GAEJ;AAEJ,CAAC;AAED,MAAM,cAAc;"}
1
+ {"version":3,"file":"Radio.mjs","sources":["../../../src/components/Radio/Radio.tsx"],"sourcesContent":["import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport interface RadioProps\n extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, \"asChild\"> {\n /** Size variant controlling label and helper text typography. @default \"default\" */\n size?: \"default\" | \"small\";\n /** Label text displayed next to the radio button. */\n label?: string;\n /** Descriptive text displayed below the label. */\n helperText?: string;\n}\n\n/**\n * A single radio option within a {@link RadioGroup}. Includes an optional label\n * and helper text.\n *\n * @example\n * ```tsx\n * <RadioGroup value={value} onValueChange={setValue}>\n * <Radio value=\"a\" label=\"Option A\" />\n * <Radio value=\"b\" label=\"Option B\" />\n * </RadioGroup>\n * ```\n */\nexport const Radio = React.forwardRef<\n React.ComponentRef<typeof RadioGroupPrimitive.Item>,\n RadioProps\n>(({ className, size = \"default\", label, helperText, id, ...props }, ref) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n\n return (\n <div className={cn(\"group inline-flex items-center gap-2\", className)}>\n <RadioGroupPrimitive.Item\n ref={ref}\n id={inputId}\n data-testid=\"radio\"\n aria-describedby={helperText ? helperTextId : undefined}\n className={cn(\n \"relative h-4 w-4 shrink-0 cursor-pointer appearance-none rounded-full border border-body-100 bg-transparent outline-brand-purple-500 transition-colors hover:bg-brand-green-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 not-disabled:active:bg-brand-green-50 disabled:cursor-not-allowed disabled:border-disabled-400 disabled:bg-transparent data-[state=checked]:border-body-100 data-[state=checked]:bg-transparent dark:border-body-100 dark:disabled:border-disabled-400\",\n helperText && \"mt-1 self-start\",\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"size-2 rounded-full bg-body-100 group-has-disabled:bg-disabled-400 dark:bg-body-100 dark:group-has-disabled:bg-disabled-400\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n {(label || helperText) && (\n <div className=\"flex flex-col gap-0.5\">\n {label && (\n <label\n htmlFor={inputId}\n className={cn(\n \"cursor-pointer select-none text-body-100 group-has-disabled:cursor-not-allowed group-has-disabled:text-disabled-100\",\n size === \"small\" ? \"typography-body-2-semibold\" : \"typography-body-1-semibold\",\n )}\n >\n {label}\n </label>\n )}\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"text-body-200 group-has-disabled:cursor-not-allowed group-has-disabled:text-disabled-100\",\n size === \"small\" ? \"typography-body-2-semibold\" : \"typography-caption-regular\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n )}\n </div>\n );\n});\n\nRadio.displayName = \"Radio\";\n"],"names":[],"mappings":";;;;;AA0BO,MAAM,QAAQ,MAAM,WAGzB,CAAC,EAAE,WAAW,OAAO,WAAW,OAAO,YAAY,IAAI,GAAG,MAAA,GAAS,QAAQ;AAC3E,QAAM,cAAc,MAAM,MAAA;AAC1B,QAAM,UAAU,MAAM;AACtB,QAAM,eAAe,GAAG,OAAO;AAE/B,8BACG,OAAA,EAAI,WAAW,GAAG,wCAAwC,SAAS,GAClE,UAAA;AAAA,IAAA;AAAA,MAAC,oBAAoB;AAAA,MAApB;AAAA,QACC;AAAA,QACA,IAAI;AAAA,QACJ,eAAY;AAAA,QACZ,oBAAkB,aAAa,eAAe;AAAA,QAC9C,WAAW;AAAA,UACT;AAAA,UACA,cAAc;AAAA,QAAA;AAAA,QAEf,GAAG;AAAA,QAEJ,UAAA,oBAAC,oBAAoB,WAApB,EAA8B,WAAU,qDACvC,UAAA,oBAAC,QAAA,EAAK,WAAU,8HAAA,CAA8H,EAAA,CAChJ;AAAA,MAAA;AAAA,IAAA;AAAA,KAEA,SAAS,eACT,qBAAC,OAAA,EAAI,WAAU,yBACZ,UAAA;AAAA,MAAA,SACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAW;AAAA,YACT;AAAA,YACA,SAAS,UAAU,+BAA+B;AAAA,UAAA;AAAA,UAGnD,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGJ,cACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,YACA,SAAS,UAAU,+BAA+B;AAAA,UAAA;AAAA,UAGnD,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EAAA,CAEJ;AAAA,EAAA,GAEJ;AAEJ,CAAC;AAED,MAAM,cAAc;"}
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.mjs","sources":["../../../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":["import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport * as React from \"react\";\n\nexport type RadioGroupProps = React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>;\n\nexport const RadioGroup = React.forwardRef<\n React.ComponentRef<typeof RadioGroupPrimitive.Root>,\n RadioGroupProps\n>((props, ref) => {\n return <RadioGroupPrimitive.Root ref={ref} {...props} />;\n});\n\nRadioGroup.displayName = \"RadioGroup\";\n"],"names":[],"mappings":";;;;AAKO,MAAM,aAAa,MAAM,WAG9B,CAAC,OAAO,QAAQ;AAChB,6BAAQ,oBAAoB,MAApB,EAAyB,KAAW,GAAG,OAAO;AACxD,CAAC;AAED,WAAW,cAAc;"}
1
+ {"version":3,"file":"RadioGroup.mjs","sources":["../../../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":["import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport * as React from \"react\";\n\n/** Props for the {@link RadioGroup} component. Extends the Radix `RadioGroup.Root` props. */\nexport type RadioGroupProps = React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>;\n\n/**\n * Groups {@link Radio} items into a single controlled or uncontrolled selection.\n * Built on Radix UI `RadioGroup.Root`.\n *\n * @example\n * ```tsx\n * <RadioGroup value={value} onValueChange={setValue}>\n * <Radio value=\"a\" label=\"Option A\" />\n * <Radio value=\"b\" label=\"Option B\" />\n * </RadioGroup>\n * ```\n */\nexport const RadioGroup = React.forwardRef<\n React.ComponentRef<typeof RadioGroupPrimitive.Root>,\n RadioGroupProps\n>((props, ref) => {\n return <RadioGroupPrimitive.Root ref={ref} {...props} />;\n});\n\nRadioGroup.displayName = \"RadioGroup\";\n"],"names":[],"mappings":";;;;AAkBO,MAAM,aAAa,MAAM,WAG9B,CAAC,OAAO,QAAQ;AAChB,6BAAQ,oBAAoB,MAApB,EAAyB,KAAW,GAAG,OAAO;AACxD,CAAC;AAED,WAAW,cAAc;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Slider.mjs","sources":["../../../src/components/Slider/Slider.tsx"],"sourcesContent":["import * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { SliderLayout } from \"./SliderLayout\";\nimport { SliderThumb } from \"./SliderThumb\";\n\nexport type SliderLabelPosition = \"top\" | \"left\";\n\nexport interface SliderProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>,\n \"asChild\" | \"children\"\n > {\n /** Label text displayed alongside the slider */\n label?: string;\n /** Position of the label relative to the slider track */\n labelPosition?: SliderLabelPosition;\n /** Text shown at the minimum end of the track */\n minLabel?: string;\n /** Text shown at the maximum end of the track */\n maxLabel?: string;\n /** Whether to show a tooltip with the current value above the thumb */\n showTooltip?: boolean;\n /** Override the displayed tooltip value (e.g. for formatting) */\n formatTooltip?: (value: number) => string;\n}\n\nexport const Slider = React.forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(\n (\n {\n className,\n label,\n labelPosition = \"top\",\n minLabel,\n maxLabel,\n showTooltip = false,\n formatTooltip,\n disabled,\n value,\n defaultValue,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n },\n ref,\n ) => {\n const labelId = React.useId();\n const thumbCount = value?.length ?? defaultValue?.length ?? 1;\n const resolvedLabelledBy = ariaLabelledBy ?? (label ? labelId : undefined);\n const resolvedAriaLabel = !resolvedLabelledBy ? ariaLabel : undefined;\n\n const sliderTrack = (\n <SliderPrimitive.Root\n ref={ref}\n disabled={disabled}\n value={value}\n defaultValue={defaultValue}\n aria-label={resolvedAriaLabel}\n aria-labelledby={resolvedLabelledBy}\n className={cn(\n \"group/slider relative flex w-full touch-none select-none items-center\",\n disabled && \"pointer-events-none opacity-50\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-3 w-full overflow-hidden rounded-full border border-neutral-100 bg-neutral-100\">\n <SliderPrimitive.Range className=\"absolute h-full rounded-full bg-brand-green-500\" />\n </SliderPrimitive.Track>\n\n {Array.from({ length: thumbCount }, (_, i) => (\n <SliderThumb\n // biome-ignore lint/suspicious/noArrayIndexKey: thumbs are fixed-count and never reorder\n key={i}\n showTooltip={showTooltip}\n formatTooltip={formatTooltip}\n index={i}\n aria-label={resolvedAriaLabel}\n aria-labelledby={resolvedLabelledBy}\n />\n ))}\n </SliderPrimitive.Root>\n );\n\n const hasLayout = Boolean(label || minLabel || maxLabel);\n\n if (!hasLayout) return sliderTrack;\n\n return (\n <SliderLayout\n label={label}\n labelId={labelId}\n labelPosition={labelPosition}\n minLabel={minLabel}\n maxLabel={maxLabel}\n >\n {sliderTrack}\n </SliderLayout>\n );\n },\n);\n\nSlider.displayName = \"Slider\";\n"],"names":[],"mappings":";;;;;;;AA2BO,MAAM,SAAS,MAAM;AAAA,EAI1B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UAAU,MAAM,MAAA;AACtB,UAAM,aAAa,OAAO,UAAU,cAAc,UAAU;AAC5D,UAAM,qBAAqB,mBAAmB,QAAQ,UAAU;AAChE,UAAM,oBAAoB,CAAC,qBAAqB,YAAY;AAE5D,UAAM,cACJ;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAY;AAAA,QACZ,mBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,6FAC/B,UAAA,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,kDAAA,CAAkD,EAAA,CACrF;AAAA,UAEC,MAAM,KAAK,EAAE,QAAQ,cAAc,CAAC,GAAG,MACtC;AAAA,YAAC;AAAA,YAAA;AAAA,cAGC;AAAA,cACA;AAAA,cACA,OAAO;AAAA,cACP,cAAY;AAAA,cACZ,mBAAiB;AAAA,YAAA;AAAA,YALZ;AAAA,UAAA,CAOR;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAIL,UAAM,YAAY,QAAQ,SAAS,YAAY,QAAQ;AAEvD,QAAI,CAAC,UAAW,QAAO;AAEvB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QAEC,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,OAAO,cAAc;"}
1
+ {"version":3,"file":"Slider.mjs","sources":["../../../src/components/Slider/Slider.tsx"],"sourcesContent":["import * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { SliderLayout } from \"./SliderLayout\";\nimport { SliderThumb } from \"./SliderThumb\";\n\n/** Position of the slider label relative to the track. */\nexport type SliderLabelPosition = \"top\" | \"left\";\n\nexport interface SliderProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>,\n \"asChild\" | \"children\"\n > {\n /** Label text displayed alongside the slider. */\n label?: string;\n /** Position of the label relative to the slider track. @default \"top\" */\n labelPosition?: SliderLabelPosition;\n /** Text shown at the minimum end of the track. */\n minLabel?: string;\n /** Text shown at the maximum end of the track. */\n maxLabel?: string;\n /** Whether to show a tooltip with the current value above the thumb. @default false */\n showTooltip?: boolean;\n /** Custom formatter for the tooltip value (e.g. to add units or format numbers). */\n formatTooltip?: (value: number) => string;\n}\n\n/**\n * A range input for selecting one or more numeric values along a track.\n * Supports single and multi-thumb modes, optional labels, and a value tooltip.\n *\n * Built on Radix UI `Slider`.\n *\n * @example\n * ```tsx\n * <Slider\n * label=\"Volume\"\n * min={0}\n * max={100}\n * defaultValue={[50]}\n * showTooltip\n * />\n * ```\n */\nexport const Slider = React.forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(\n (\n {\n className,\n label,\n labelPosition = \"top\",\n minLabel,\n maxLabel,\n showTooltip = false,\n formatTooltip,\n disabled,\n value,\n defaultValue,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n },\n ref,\n ) => {\n const labelId = React.useId();\n const thumbCount = value?.length ?? defaultValue?.length ?? 1;\n const resolvedLabelledBy = ariaLabelledBy ?? (label ? labelId : undefined);\n const resolvedAriaLabel = !resolvedLabelledBy ? ariaLabel : undefined;\n\n const sliderTrack = (\n <SliderPrimitive.Root\n ref={ref}\n disabled={disabled}\n value={value}\n defaultValue={defaultValue}\n aria-label={resolvedAriaLabel}\n aria-labelledby={resolvedLabelledBy}\n className={cn(\n \"group/slider relative flex w-full touch-none select-none items-center\",\n disabled && \"pointer-events-none opacity-50\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-3 w-full overflow-hidden rounded-full border border-neutral-100 bg-neutral-100\">\n <SliderPrimitive.Range className=\"absolute h-full rounded-full bg-brand-green-500\" />\n </SliderPrimitive.Track>\n\n {Array.from({ length: thumbCount }, (_, i) => (\n <SliderThumb\n // biome-ignore lint/suspicious/noArrayIndexKey: thumbs are fixed-count and never reorder\n key={i}\n showTooltip={showTooltip}\n formatTooltip={formatTooltip}\n index={i}\n aria-label={resolvedAriaLabel}\n aria-labelledby={resolvedLabelledBy}\n />\n ))}\n </SliderPrimitive.Root>\n );\n\n const hasLayout = Boolean(label || minLabel || maxLabel);\n\n if (!hasLayout) return sliderTrack;\n\n return (\n <SliderLayout\n label={label}\n labelId={labelId}\n labelPosition={labelPosition}\n minLabel={minLabel}\n maxLabel={maxLabel}\n >\n {sliderTrack}\n </SliderLayout>\n );\n },\n);\n\nSlider.displayName = \"Slider\";\n"],"names":[],"mappings":";;;;;;;AA6CO,MAAM,SAAS,MAAM;AAAA,EAI1B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UAAU,MAAM,MAAA;AACtB,UAAM,aAAa,OAAO,UAAU,cAAc,UAAU;AAC5D,UAAM,qBAAqB,mBAAmB,QAAQ,UAAU;AAChE,UAAM,oBAAoB,CAAC,qBAAqB,YAAY;AAE5D,UAAM,cACJ;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAY;AAAA,QACZ,mBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,6FAC/B,UAAA,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,kDAAA,CAAkD,EAAA,CACrF;AAAA,UAEC,MAAM,KAAK,EAAE,QAAQ,cAAc,CAAC,GAAG,MACtC;AAAA,YAAC;AAAA,YAAA;AAAA,cAGC;AAAA,cACA;AAAA,cACA,OAAO;AAAA,cACP,cAAY;AAAA,cACZ,mBAAiB;AAAA,YAAA;AAAA,YALZ;AAAA,UAAA,CAOR;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAIL,UAAM,YAAY,QAAQ,SAAS,YAAY,QAAQ;AAEvD,QAAI,CAAC,UAAW,QAAO;AAEvB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QAEC,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,OAAO,cAAc;"}
@@ -39,6 +39,7 @@ function SliderThumb({
39
39
  "flex size-6 items-center justify-center rounded-full border border-neutral-100 bg-background-inverse-solid shadow-sm",
40
40
  "transition-shadow duration-150",
41
41
  "hover:ring-2 hover:ring-brand-green-500",
42
+ "not-data-disabled:active:ring-2 not-data-disabled:active:ring-brand-green-500",
42
43
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid",
43
44
  "data-disabled:cursor-not-allowed"
44
45
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"SliderThumb.mjs","sources":["../../../src/components/Slider/SliderThumb.tsx"],"sourcesContent":["import * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\ninterface SliderThumbProps {\n showTooltip: boolean;\n formatTooltip?: (value: number) => string;\n index: number;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n}\n\nexport function SliderThumb({\n showTooltip,\n formatTooltip,\n index,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n}: SliderThumbProps) {\n const thumbRef = React.useCallback(\n (el: HTMLSpanElement | null) => {\n if (!el || !showTooltip) return;\n syncTooltipText(el, formatTooltip);\n },\n [showTooltip, formatTooltip],\n );\n\n return (\n <SliderPrimitive.Thumb\n ref={thumbRef}\n data-index={index}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n onPointerDown={(e) => {\n if (showTooltip) syncTooltipText(e.currentTarget, formatTooltip);\n }}\n onPointerMove={(e) => {\n if (showTooltip) syncTooltipText(e.currentTarget, formatTooltip);\n }}\n onKeyDown={(e) => {\n if (showTooltip) {\n requestAnimationFrame(() => syncTooltipText(e.currentTarget, formatTooltip));\n }\n }}\n className={cn(\n \"flex size-6 items-center justify-center rounded-full border border-neutral-100 bg-background-inverse-solid shadow-sm\",\n \"transition-shadow duration-150\",\n \"hover:ring-2 hover:ring-brand-green-500\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid\",\n \"data-disabled:cursor-not-allowed\",\n )}\n >\n <span className=\"block size-3 rounded-full bg-brand-green-500 shadow-[inset_0px_1px_2px_0px_rgba(0,0,0,0.1)]\" />\n\n {showTooltip && (\n <span\n role=\"tooltip\"\n data-slider-tooltip\n className=\"typography-caption-semibold pointer-events-none absolute bottom-full mb-2 rounded-3xl bg-background-solid px-2 py-1 text-background-inverse-solid shadow-sm\"\n />\n )}\n </SliderPrimitive.Thumb>\n );\n}\n\nfunction syncTooltipText(thumb: HTMLElement, formatTooltip?: (value: number) => string) {\n const raw = thumb.getAttribute(\"aria-valuenow\");\n const tooltip = thumb.querySelector<HTMLSpanElement>(\"[data-slider-tooltip]\");\n if (raw == null || !tooltip) return;\n const num = Number(raw);\n tooltip.textContent = formatTooltip ? formatTooltip(num) : String(num);\n}\n"],"names":[],"mappings":";;;;;AAYO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AACrB,GAAqB;AACnB,QAAM,WAAW,MAAM;AAAA,IACrB,CAAC,OAA+B;AAC9B,UAAI,CAAC,MAAM,CAAC,YAAa;AACzB,sBAAgB,IAAI,aAAa;AAAA,IACnC;AAAA,IACA,CAAC,aAAa,aAAa;AAAA,EAAA;AAG7B,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,KAAK;AAAA,MACL,cAAY;AAAA,MACZ,cAAY;AAAA,MACZ,mBAAiB;AAAA,MACjB,eAAe,CAAC,MAAM;AACpB,YAAI,YAAa,iBAAgB,EAAE,eAAe,aAAa;AAAA,MACjE;AAAA,MACA,eAAe,CAAC,MAAM;AACpB,YAAI,YAAa,iBAAgB,EAAE,eAAe,aAAa;AAAA,MACjE;AAAA,MACA,WAAW,CAAC,MAAM;AAChB,YAAI,aAAa;AACf,gCAAsB,MAAM,gBAAgB,EAAE,eAAe,aAAa,CAAC;AAAA,QAC7E;AAAA,MACF;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,8FAAA,CAA8F;AAAA,QAE7G,eACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,uBAAmB;AAAA,YACnB,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,EAAA;AAIR;AAEA,SAAS,gBAAgB,OAAoB,eAA2C;AACtF,QAAM,MAAM,MAAM,aAAa,eAAe;AAC9C,QAAM,UAAU,MAAM,cAA+B,uBAAuB;AAC5E,MAAI,OAAO,QAAQ,CAAC,QAAS;AAC7B,QAAM,MAAM,OAAO,GAAG;AACtB,UAAQ,cAAc,gBAAgB,cAAc,GAAG,IAAI,OAAO,GAAG;AACvE;"}
1
+ {"version":3,"file":"SliderThumb.mjs","sources":["../../../src/components/Slider/SliderThumb.tsx"],"sourcesContent":["import * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\ninterface SliderThumbProps {\n showTooltip: boolean;\n formatTooltip?: (value: number) => string;\n index: number;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n}\n\nexport function SliderThumb({\n showTooltip,\n formatTooltip,\n index,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n}: SliderThumbProps) {\n const thumbRef = React.useCallback(\n (el: HTMLSpanElement | null) => {\n if (!el || !showTooltip) return;\n syncTooltipText(el, formatTooltip);\n },\n [showTooltip, formatTooltip],\n );\n\n return (\n <SliderPrimitive.Thumb\n ref={thumbRef}\n data-index={index}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n onPointerDown={(e) => {\n if (showTooltip) syncTooltipText(e.currentTarget, formatTooltip);\n }}\n onPointerMove={(e) => {\n if (showTooltip) syncTooltipText(e.currentTarget, formatTooltip);\n }}\n onKeyDown={(e) => {\n if (showTooltip) {\n requestAnimationFrame(() => syncTooltipText(e.currentTarget, formatTooltip));\n }\n }}\n className={cn(\n \"flex size-6 items-center justify-center rounded-full border border-neutral-100 bg-background-inverse-solid shadow-sm\",\n \"transition-shadow duration-150\",\n \"hover:ring-2 hover:ring-brand-green-500\",\n \"not-data-disabled:active:ring-2 not-data-disabled:active:ring-brand-green-500\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background-inverse-solid\",\n \"data-disabled:cursor-not-allowed\",\n )}\n >\n <span className=\"block size-3 rounded-full bg-brand-green-500 shadow-[inset_0px_1px_2px_0px_rgba(0,0,0,0.1)]\" />\n\n {showTooltip && (\n <span\n role=\"tooltip\"\n data-slider-tooltip\n className=\"typography-caption-semibold pointer-events-none absolute bottom-full mb-2 rounded-3xl bg-background-solid px-2 py-1 text-background-inverse-solid shadow-sm\"\n />\n )}\n </SliderPrimitive.Thumb>\n );\n}\n\nfunction syncTooltipText(thumb: HTMLElement, formatTooltip?: (value: number) => string) {\n const raw = thumb.getAttribute(\"aria-valuenow\");\n const tooltip = thumb.querySelector<HTMLSpanElement>(\"[data-slider-tooltip]\");\n if (raw == null || !tooltip) return;\n const num = Number(raw);\n tooltip.textContent = formatTooltip ? formatTooltip(num) : String(num);\n}\n"],"names":[],"mappings":";;;;;AAYO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AACrB,GAAqB;AACnB,QAAM,WAAW,MAAM;AAAA,IACrB,CAAC,OAA+B;AAC9B,UAAI,CAAC,MAAM,CAAC,YAAa;AACzB,sBAAgB,IAAI,aAAa;AAAA,IACnC;AAAA,IACA,CAAC,aAAa,aAAa;AAAA,EAAA;AAG7B,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,KAAK;AAAA,MACL,cAAY;AAAA,MACZ,cAAY;AAAA,MACZ,mBAAiB;AAAA,MACjB,eAAe,CAAC,MAAM;AACpB,YAAI,YAAa,iBAAgB,EAAE,eAAe,aAAa;AAAA,MACjE;AAAA,MACA,eAAe,CAAC,MAAM;AACpB,YAAI,YAAa,iBAAgB,EAAE,eAAe,aAAa;AAAA,MACjE;AAAA,MACA,WAAW,CAAC,MAAM;AAChB,YAAI,aAAa;AACf,gCAAsB,MAAM,gBAAgB,EAAE,eAAe,aAAa,CAAC;AAAA,QAC7E;AAAA,MACF;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,8FAAA,CAA8F;AAAA,QAE7G,eACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,uBAAmB;AAAA,YACnB,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,EAAA;AAIR;AAEA,SAAS,gBAAgB,OAAoB,eAA2C;AACtF,QAAM,MAAM,MAAM,aAAa,eAAe;AAC9C,QAAM,UAAU,MAAM,cAA+B,uBAAuB;AAC5E,MAAI,OAAO,QAAQ,CAAC,QAAS;AAC7B,QAAM,MAAM,OAAO,GAAG;AACtB,UAAQ,cAAc,gBAAgB,cAAc,GAAG,IAAI,OAAO,GAAG;AACvE;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Snackbar.mjs","sources":["../../../src/components/Snackbar/Snackbar.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CrossIcon } from \"../Icons/CrossIcon\";\n\nexport type SnackbarVariant = \"default\" | \"vipEarn\" | \"welcome\";\n\nexport interface SnackbarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Snackbar layout variant */\n variant?: SnackbarVariant;\n /** Left icon element */\n icon?: React.ReactNode;\n /** Title text */\n title?: React.ReactNode;\n /** Description text */\n description?: React.ReactNode;\n /** Whether to show the actions section */\n showActions?: boolean;\n /** Primary CTA label (renders a default Button) */\n primaryLabel?: string;\n /** Primary CTA click handler (used with primaryLabel) */\n primaryOnClick?: () => void;\n /** Custom element to render as primary CTA (overrides primaryLabel/primaryOnClick) */\n primarySlot?: React.ReactNode;\n /** Secondary CTA label (renders a default Button) */\n secondaryLabel?: string;\n /** Secondary CTA click handler (used with secondaryLabel) */\n secondaryOnClick?: () => void;\n /** Custom element to render as secondary CTA (overrides secondaryLabel/secondaryOnClick) */\n secondarySlot?: React.ReactNode;\n /** Show close button */\n closable?: boolean;\n /** Close button click handler */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close snackbar\" */\n closeLabel?: string;\n}\n\nfunction CloseButton({\n onClose,\n className,\n closeLabel = \"Close snackbar\",\n}: {\n onClose?: () => void;\n className?: string;\n closeLabel?: string;\n}) {\n return (\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n onClick={onClose}\n icon={<CrossIcon />}\n className={cn(\"shrink-0\", className)}\n aria-label={closeLabel}\n />\n );\n}\n\n/**\n * Resolves a CTA slot. If a custom slot is provided it takes precedence,\n * otherwise a default Button is rendered from label + onClick.\n */\nfunction resolveCtaSlot(\n slot: React.ReactNode | undefined,\n label: string | undefined,\n onClick: (() => void) | undefined,\n buttonProps: React.ComponentProps<typeof Button>,\n): React.ReactNode | null {\n if (slot) return slot;\n if (label) {\n return (\n <Button onClick={onClick} {...buttonProps}>\n {label}\n </Button>\n );\n }\n return null;\n}\n\nfunction VipEarnContent({\n icon,\n title,\n description,\n showActions,\n primarySlot,\n primaryLabel,\n primaryOnClick,\n}: Pick<\n SnackbarProps,\n | \"icon\"\n | \"title\"\n | \"description\"\n | \"showActions\"\n | \"primarySlot\"\n | \"primaryLabel\"\n | \"primaryOnClick\"\n>) {\n const primary = resolveCtaSlot(primarySlot, primaryLabel, primaryOnClick, {\n variant: \"text\",\n size: \"24\",\n });\n\n return (\n <>\n {icon && (\n <span className=\"flex shrink-0\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-4\">\n <div className=\"flex flex-col\">\n {title && <p className=\"typography-body-1-semibold text-body-100 leading-6\">{title}</p>}\n {description && <p className=\"typography-body-2-regular text-body-200\">{description}</p>}\n </div>\n {showActions && primary && <div className=\"self-start\">{primary}</div>}\n </div>\n </>\n );\n}\n\nfunction WelcomeContent({\n title,\n description,\n showActions,\n primarySlot,\n primaryLabel,\n primaryOnClick,\n secondarySlot,\n secondaryLabel,\n secondaryOnClick,\n}: Pick<\n SnackbarProps,\n | \"title\"\n | \"description\"\n | \"showActions\"\n | \"primarySlot\"\n | \"primaryLabel\"\n | \"primaryOnClick\"\n | \"secondarySlot\"\n | \"secondaryLabel\"\n | \"secondaryOnClick\"\n>) {\n const primary = resolveCtaSlot(primarySlot, primaryLabel, primaryOnClick, {\n variant: \"primary\",\n });\n const secondary = resolveCtaSlot(secondarySlot, secondaryLabel, secondaryOnClick, {\n variant: \"secondary\",\n });\n\n return (\n <>\n <div className=\"flex flex-col items-center gap-2 px-8 text-center text-body-100\">\n {title && <p className=\"typography-heading-4 text-body-100\">{title}</p>}\n {description && <p className=\"typography-body-2-regular text-body-200\">{description}</p>}\n </div>\n {showActions && (primary || secondary) && (\n <div className=\"flex w-full flex-col gap-4 px-8 sm:flex-row sm:*:flex-1\">\n {secondary}\n {primary}\n </div>\n )}\n </>\n );\n}\n\nfunction DefaultContent({\n children,\n showActions,\n primarySlot,\n primaryLabel,\n primaryOnClick,\n secondarySlot,\n secondaryLabel,\n secondaryOnClick,\n}: Pick<\n SnackbarProps,\n | \"children\"\n | \"showActions\"\n | \"primarySlot\"\n | \"primaryLabel\"\n | \"primaryOnClick\"\n | \"secondarySlot\"\n | \"secondaryLabel\"\n | \"secondaryOnClick\"\n>) {\n const primary = resolveCtaSlot(primarySlot, primaryLabel, primaryOnClick, {\n variant: \"primary\",\n size: \"40\",\n });\n const secondary = resolveCtaSlot(secondarySlot, secondaryLabel, secondaryOnClick, {\n variant: \"tertiary\",\n size: \"40\",\n });\n\n return (\n <>\n <div className=\"typography-body-1-medium flex min-w-0 flex-1 items-center self-stretch text-body-100\">\n {children}\n </div>\n {showActions && (primary || secondary) && (\n <div className=\"flex shrink-0 items-start gap-2\">\n {primary}\n {secondary}\n </div>\n )}\n </>\n );\n}\n\nexport const Snackbar = React.forwardRef<HTMLDivElement, SnackbarProps>(\n (\n {\n className,\n variant = \"default\",\n icon,\n title,\n description,\n showActions = true,\n primaryLabel,\n primaryOnClick,\n primarySlot,\n secondaryLabel,\n secondaryOnClick,\n secondarySlot,\n closable = false,\n onClose,\n closeLabel,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n /* biome-ignore lint/a11y/useSemanticElements: output cannot contain div children; browsers would break the wrapper. */\n <div\n ref={ref}\n role=\"status\"\n data-testid=\"snackbar\"\n className={cn(\n \"flex gap-4 rounded-2xl\",\n (variant === \"default\" || variant === \"vipEarn\") &&\n \"border border-neutral-50 bg-background-200 p-4 backdrop-blur-md\",\n variant === \"default\" && \"flex-wrap items-start\",\n variant === \"vipEarn\" && \"items-start\",\n variant === \"welcome\" &&\n \"relative flex-col items-center bg-background-inverse-solid py-6\",\n className,\n )}\n {...props}\n >\n {variant === \"vipEarn\" && (\n <VipEarnContent\n icon={icon}\n title={title}\n description={description}\n showActions={showActions}\n primarySlot={primarySlot}\n primaryLabel={primaryLabel}\n primaryOnClick={primaryOnClick}\n />\n )}\n {variant === \"welcome\" && (\n <WelcomeContent\n title={title}\n description={description}\n showActions={showActions}\n primarySlot={primarySlot}\n primaryLabel={primaryLabel}\n primaryOnClick={primaryOnClick}\n secondarySlot={secondarySlot}\n secondaryLabel={secondaryLabel}\n secondaryOnClick={secondaryOnClick}\n />\n )}\n {variant === \"default\" && (\n <DefaultContent\n showActions={showActions}\n primarySlot={primarySlot}\n primaryLabel={primaryLabel}\n primaryOnClick={primaryOnClick}\n secondarySlot={secondarySlot}\n secondaryLabel={secondaryLabel}\n secondaryOnClick={secondaryOnClick}\n >\n {children}\n </DefaultContent>\n )}\n {closable && (\n <CloseButton\n onClose={onClose}\n closeLabel={closeLabel}\n className={variant === \"welcome\" ? \"absolute top-2 right-2\" : undefined}\n />\n )}\n </div>\n );\n },\n);\n\nSnackbar.displayName = \"Snackbar\";\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Snackbar.mjs","sources":["../../../src/components/Snackbar/Snackbar.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CrossIcon } from \"../Icons/CrossIcon\";\n\n/** Layout variant of the snackbar. */\nexport type SnackbarVariant = \"default\" | \"vipEarn\" | \"welcome\";\n\nexport interface SnackbarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Layout variant of the snackbar. @default \"default\" */\n variant?: SnackbarVariant;\n /** Icon element displayed at the leading edge (used by the `vipEarn` variant). */\n icon?: React.ReactNode;\n /** Title content. */\n title?: React.ReactNode;\n /** Descriptive body text. */\n description?: React.ReactNode;\n /** Whether to show primary/secondary action buttons. @default true */\n showActions?: boolean;\n /** Primary CTA label — renders a default {@link Button}. */\n primaryLabel?: string;\n /** Click handler for the primary CTA (used together with `primaryLabel`). */\n primaryOnClick?: () => void;\n /** Custom element rendered as the primary CTA. Overrides `primaryLabel` / `primaryOnClick`. */\n primarySlot?: React.ReactNode;\n /** Secondary CTA label — renders a default {@link Button}. */\n secondaryLabel?: string;\n /** Click handler for the secondary CTA (used together with `secondaryLabel`). */\n secondaryOnClick?: () => void;\n /** Custom element rendered as the secondary CTA. Overrides `secondaryLabel` / `secondaryOnClick`. */\n secondarySlot?: React.ReactNode;\n /** Whether to show the close button. @default false */\n closable?: boolean;\n /** Callback fired when the close button is clicked. */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close snackbar\" */\n closeLabel?: string;\n}\n\nfunction CloseButton({\n onClose,\n className,\n closeLabel = \"Close snackbar\",\n}: {\n onClose?: () => void;\n className?: string;\n closeLabel?: string;\n}) {\n return (\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n onClick={onClose}\n icon={<CrossIcon />}\n className={cn(\"shrink-0\", className)}\n aria-label={closeLabel}\n />\n );\n}\n\n/**\n * Resolves a CTA slot. If a custom slot is provided it takes precedence,\n * otherwise a default Button is rendered from label + onClick.\n */\nfunction resolveCtaSlot(\n slot: React.ReactNode | undefined,\n label: string | undefined,\n onClick: (() => void) | undefined,\n buttonProps: React.ComponentProps<typeof Button>,\n): React.ReactNode | null {\n if (slot) return slot;\n if (label) {\n return (\n <Button onClick={onClick} {...buttonProps}>\n {label}\n </Button>\n );\n }\n return null;\n}\n\nfunction VipEarnContent({\n icon,\n title,\n description,\n showActions,\n primarySlot,\n primaryLabel,\n primaryOnClick,\n}: Pick<\n SnackbarProps,\n | \"icon\"\n | \"title\"\n | \"description\"\n | \"showActions\"\n | \"primarySlot\"\n | \"primaryLabel\"\n | \"primaryOnClick\"\n>) {\n const primary = resolveCtaSlot(primarySlot, primaryLabel, primaryOnClick, {\n variant: \"text\",\n size: \"24\",\n });\n\n return (\n <>\n {icon && (\n <span className=\"flex shrink-0\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-4\">\n <div className=\"flex flex-col\">\n {title && <p className=\"typography-body-1-semibold text-body-100 leading-6\">{title}</p>}\n {description && <p className=\"typography-body-2-regular text-body-200\">{description}</p>}\n </div>\n {showActions && primary && <div className=\"self-start\">{primary}</div>}\n </div>\n </>\n );\n}\n\nfunction WelcomeContent({\n title,\n description,\n showActions,\n primarySlot,\n primaryLabel,\n primaryOnClick,\n secondarySlot,\n secondaryLabel,\n secondaryOnClick,\n}: Pick<\n SnackbarProps,\n | \"title\"\n | \"description\"\n | \"showActions\"\n | \"primarySlot\"\n | \"primaryLabel\"\n | \"primaryOnClick\"\n | \"secondarySlot\"\n | \"secondaryLabel\"\n | \"secondaryOnClick\"\n>) {\n const primary = resolveCtaSlot(primarySlot, primaryLabel, primaryOnClick, {\n variant: \"primary\",\n });\n const secondary = resolveCtaSlot(secondarySlot, secondaryLabel, secondaryOnClick, {\n variant: \"secondary\",\n });\n\n return (\n <>\n <div className=\"flex flex-col items-center gap-2 px-8 text-center text-body-100\">\n {title && <p className=\"typography-heading-4 text-body-100\">{title}</p>}\n {description && <p className=\"typography-body-2-regular text-body-200\">{description}</p>}\n </div>\n {showActions && (primary || secondary) && (\n <div className=\"flex w-full flex-col gap-4 px-8 sm:flex-row sm:*:flex-1\">\n {secondary}\n {primary}\n </div>\n )}\n </>\n );\n}\n\nfunction DefaultContent({\n children,\n showActions,\n primarySlot,\n primaryLabel,\n primaryOnClick,\n secondarySlot,\n secondaryLabel,\n secondaryOnClick,\n}: Pick<\n SnackbarProps,\n | \"children\"\n | \"showActions\"\n | \"primarySlot\"\n | \"primaryLabel\"\n | \"primaryOnClick\"\n | \"secondarySlot\"\n | \"secondaryLabel\"\n | \"secondaryOnClick\"\n>) {\n const primary = resolveCtaSlot(primarySlot, primaryLabel, primaryOnClick, {\n variant: \"primary\",\n size: \"40\",\n });\n const secondary = resolveCtaSlot(secondarySlot, secondaryLabel, secondaryOnClick, {\n variant: \"tertiary\",\n size: \"40\",\n });\n\n return (\n <>\n <div className=\"typography-body-1-medium flex min-w-0 flex-1 items-center self-stretch text-body-100\">\n {children}\n </div>\n {showActions && (primary || secondary) && (\n <div className=\"flex shrink-0 items-start gap-2\">\n {primary}\n {secondary}\n </div>\n )}\n </>\n );\n}\n\n/**\n * A prominent inline message with optional title, description, action buttons,\n * and close control. Supports three layout variants: `default`, `vipEarn`, and\n * `welcome`.\n *\n * @example\n * ```tsx\n * <Snackbar\n * variant=\"default\"\n * primaryLabel=\"Undo\"\n * primaryOnClick={undo}\n * closable\n * onClose={dismiss}\n * >\n * Item deleted\n * </Snackbar>\n * ```\n */\nexport const Snackbar = React.forwardRef<HTMLDivElement, SnackbarProps>(\n (\n {\n className,\n variant = \"default\",\n icon,\n title,\n description,\n showActions = true,\n primaryLabel,\n primaryOnClick,\n primarySlot,\n secondaryLabel,\n secondaryOnClick,\n secondarySlot,\n closable = false,\n onClose,\n closeLabel,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n /* biome-ignore lint/a11y/useSemanticElements: output cannot contain div children; browsers would break the wrapper. */\n <div\n ref={ref}\n role=\"status\"\n data-testid=\"snackbar\"\n className={cn(\n \"flex gap-4 rounded-2xl\",\n (variant === \"default\" || variant === \"vipEarn\") &&\n \"border border-neutral-50 bg-background-200 p-4 backdrop-blur-md\",\n variant === \"default\" && \"flex-wrap items-start\",\n variant === \"vipEarn\" && \"items-start\",\n variant === \"welcome\" &&\n \"relative flex-col items-center bg-background-inverse-solid py-6\",\n className,\n )}\n {...props}\n >\n {variant === \"vipEarn\" && (\n <VipEarnContent\n icon={icon}\n title={title}\n description={description}\n showActions={showActions}\n primarySlot={primarySlot}\n primaryLabel={primaryLabel}\n primaryOnClick={primaryOnClick}\n />\n )}\n {variant === \"welcome\" && (\n <WelcomeContent\n title={title}\n description={description}\n showActions={showActions}\n primarySlot={primarySlot}\n primaryLabel={primaryLabel}\n primaryOnClick={primaryOnClick}\n secondarySlot={secondarySlot}\n secondaryLabel={secondaryLabel}\n secondaryOnClick={secondaryOnClick}\n />\n )}\n {variant === \"default\" && (\n <DefaultContent\n showActions={showActions}\n primarySlot={primarySlot}\n primaryLabel={primaryLabel}\n primaryOnClick={primaryOnClick}\n secondarySlot={secondarySlot}\n secondaryLabel={secondaryLabel}\n secondaryOnClick={secondaryOnClick}\n >\n {children}\n </DefaultContent>\n )}\n {closable && (\n <CloseButton\n onClose={onClose}\n closeLabel={closeLabel}\n className={variant === \"welcome\" ? \"absolute top-2 right-2\" : undefined}\n />\n )}\n </div>\n );\n },\n);\n\nSnackbar.displayName = \"Snackbar\";\n"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ const Switch = React.forwardRef(({ className, size = "default", ...props }, ref)
14
14
  "focus-visible:shadow-focus-ring focus-visible:outline-none",
15
15
  "data-[state=checked]:border-neutral-200 data-[state=checked]:bg-brand-green-500",
16
16
  "data-[state=unchecked]:bg-neutral-400",
17
+ "not-disabled:active:opacity-80",
17
18
  "disabled:cursor-not-allowed disabled:opacity-50",
18
19
  size === "default" && "h-6 w-10.5",
19
20
  size === "small" && "h-5 w-9",
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.mjs","sources":["../../../src/components/Switch/Switch.tsx"],"sourcesContent":["import * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport type SwitchSize = \"default\" | \"small\";\n\nexport interface SwitchProps\n extends Omit<React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, \"asChild\"> {\n /** Size variant of the switch */\n size?: SwitchSize;\n}\n\nexport const Switch = React.forwardRef<\n React.ComponentRef<typeof SwitchPrimitive.Root>,\n SwitchProps\n>(({ className, size = \"default\", ...props }, ref) => {\n const thumbSizeClass =\n size === \"default\"\n ? \"size-4.5 translate-x-0.75 data-[state=checked]:translate-x-4.75\"\n : \"size-4 translate-x-0.5 data-[state=checked]:translate-x-4.25\";\n\n return (\n <SwitchPrimitive.Root\n ref={ref}\n className={cn(\n \"inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors duration-150\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"data-[state=checked]:border-neutral-200 data-[state=checked]:bg-brand-green-500\",\n \"data-[state=unchecked]:bg-neutral-400\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n size === \"default\" && \"h-6 w-10.5\",\n size === \"small\" && \"h-5 w-9\",\n className,\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n className={cn(\n \"pointer-events-none rounded-full bg-body-white-solid-constant shadow-sm transition-transform duration-150 dark:bg-body-black-solid-constant\",\n thumbSizeClass,\n )}\n />\n </SwitchPrimitive.Root>\n );\n});\n\nSwitch.displayName = \"Switch\";\n"],"names":[],"mappings":";;;;;AAYO,MAAM,SAAS,MAAM,WAG1B,CAAC,EAAE,WAAW,OAAO,WAAW,GAAG,MAAA,GAAS,QAAQ;AACpD,QAAM,iBACJ,SAAS,YACL,oEACA;AAEN,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,aAAa;AAAA,QACtB,SAAS,WAAW;AAAA,QACpB;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAC,gBAAgB;AAAA,QAAhB;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN,CAAC;AAED,OAAO,cAAc;"}
1
+ {"version":3,"file":"Switch.mjs","sources":["../../../src/components/Switch/Switch.tsx"],"sourcesContent":["import * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Size variant of the switch toggle. */\nexport type SwitchSize = \"default\" | \"small\";\n\nexport interface SwitchProps\n extends Omit<React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, \"asChild\"> {\n /** Size variant of the switch. @default \"default\" */\n size?: SwitchSize;\n}\n\n/**\n * A toggle switch for boolean on/off states. Built on Radix UI `Switch`.\n *\n * For a labelled switch with helper text, see {@link SwitchField}.\n *\n * @example\n * ```tsx\n * <Switch checked={on} onCheckedChange={setOn} />\n * ```\n */\nexport const Switch = React.forwardRef<\n React.ComponentRef<typeof SwitchPrimitive.Root>,\n SwitchProps\n>(({ className, size = \"default\", ...props }, ref) => {\n const thumbSizeClass =\n size === \"default\"\n ? \"size-4.5 translate-x-0.75 data-[state=checked]:translate-x-4.75\"\n : \"size-4 translate-x-0.5 data-[state=checked]:translate-x-4.25\";\n\n return (\n <SwitchPrimitive.Root\n ref={ref}\n className={cn(\n \"inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors duration-150\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"data-[state=checked]:border-neutral-200 data-[state=checked]:bg-brand-green-500\",\n \"data-[state=unchecked]:bg-neutral-400\",\n \"not-disabled:active:opacity-80\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n size === \"default\" && \"h-6 w-10.5\",\n size === \"small\" && \"h-5 w-9\",\n className,\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n className={cn(\n \"pointer-events-none rounded-full bg-body-white-solid-constant shadow-sm transition-transform duration-150 dark:bg-body-black-solid-constant\",\n thumbSizeClass,\n )}\n />\n </SwitchPrimitive.Root>\n );\n});\n\nSwitch.displayName = \"Switch\";\n"],"names":[],"mappings":";;;;;AAuBO,MAAM,SAAS,MAAM,WAG1B,CAAC,EAAE,WAAW,OAAO,WAAW,GAAG,MAAA,GAAS,QAAQ;AACpD,QAAM,iBACJ,SAAS,YACL,oEACA;AAEN,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,aAAa;AAAA,QACtB,SAAS,WAAW;AAAA,QACpB;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAC,gBAAgB;AAAA,QAAhB;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN,CAAC;AAED,OAAO,cAAc;"}
@@ -1 +1 @@
1
- {"version":3,"file":"SwitchField.mjs","sources":["../../../src/components/SwitchField/SwitchField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Switch, type SwitchSize } from \"../Switch/Switch\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\nexport type SwitchFieldOrientation = \"right\" | \"left\";\n\nexport interface SwitchFieldProps\n extends Omit<React.ComponentPropsWithoutRef<typeof Switch>, \"size\" | \"className\"> {\n /** Position of the switch relative to the label */\n orientation?: SwitchFieldOrientation;\n /** Size variant of the switch and text */\n size?: SwitchSize;\n /** Label text displayed next to the switch */\n label?: string;\n /** Helper text displayed below the label */\n helperText?: string;\n /** Info tooltip text displayed on hover of the info icon */\n infoText?: string;\n /** Accessible label for the info tooltip trigger button. Override for i18n.\n * @default \"More information\"\n */\n infoLabel?: string;\n /** Additional className for the wrapper */\n className?: string;\n}\n\nexport const SwitchField = React.forwardRef<React.ComponentRef<typeof Switch>, SwitchFieldProps>(\n (\n {\n className,\n orientation = \"right\",\n size = \"default\",\n label,\n helperText,\n infoText,\n infoLabel = \"More information\",\n disabled,\n id: propId,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const id = propId ?? generatedId;\n const helperTextId = helperText ? `${id}-helper` : undefined;\n\n const switchElement = (\n <Switch\n ref={ref}\n id={id}\n size={size}\n disabled={disabled}\n aria-describedby={helperTextId}\n {...props}\n />\n );\n\n const labelContent = (label || helperText) && (\n <div\n className={cn(\n \"flex min-w-0 flex-1 flex-col items-start\",\n size === \"default\" ? \"gap-1\" : \"gap-0.5\",\n )}\n >\n {label && (\n <div className=\"flex items-center gap-1\">\n <label\n htmlFor={id}\n className={cn(\n \"cursor-pointer select-none text-body-100\",\n disabled && \"cursor-not-allowed text-disabled-100\",\n size === \"default\" ? \"typography-body-1-semibold\" : \"typography-body-2-semibold\",\n )}\n >\n {label}\n </label>\n {infoText && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button type=\"button\" aria-label={infoLabel} className=\"flex shrink-0 pt-0.5\">\n <InfoCircleIcon aria-hidden=\"true\" className=\"size-5 text-body-200\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{infoText}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n )}\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"text-body-200\", // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes\n disabled && \"text-disabled-100\",\n size === \"default\" ? \"typography-body-2-regular\" : \"typography-caption-regular\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n );\n\n return (\n <div className={cn(\"flex items-start gap-2\", disabled && \"cursor-not-allowed\", className)}>\n {orientation === \"left\" && switchElement}\n {labelContent}\n {orientation === \"right\" && switchElement}\n </div>\n );\n },\n);\n\nSwitchField.displayName = \"SwitchField\";\n"],"names":[],"mappings":";;;;;;;AA4BO,MAAM,cAAc,MAAM;AAAA,EAC/B,CACE;AAAA,IACE;AAAA,IACA,cAAc;AAAA,IACd,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA,IAAI;AAAA,IACJ,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,cAAc,MAAM,MAAA;AAC1B,UAAM,KAAK,UAAU;AACrB,UAAM,eAAe,aAAa,GAAG,EAAE,YAAY;AAEnD,UAAM,gBACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,oBAAkB;AAAA,QACjB,GAAG;AAAA,MAAA;AAAA,IAAA;AAIR,UAAM,gBAAgB,SAAS,eAC7B;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,SAAS,YAAY,UAAU;AAAA,QAAA;AAAA,QAGhC,UAAA;AAAA,UAAA,SACC,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAW;AAAA,kBACT;AAAA,kBACA,YAAY;AAAA,kBACZ,SAAS,YAAY,+BAA+B;AAAA,gBAAA;AAAA,gBAGrD,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,YAEF,YACC,oBAAC,iBAAA,EACC,UAAA,qBAAC,SAAA,EACC,UAAA;AAAA,cAAA,oBAAC,kBAAe,SAAO,MACrB,8BAAC,UAAA,EAAO,MAAK,UAAS,cAAY,WAAW,WAAU,wBACrD,8BAAC,gBAAA,EAAe,eAAY,QAAO,WAAU,wBAAuB,GACtE,EAAA,CACF;AAAA,cACA,oBAAC,gBAAA,EAAe,MAAK,UAAU,UAAA,SAAA,CAAS;AAAA,YAAA,EAAA,CAC1C,EAAA,CACF;AAAA,UAAA,GAEJ;AAAA,UAED,cACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAI;AAAA,cACJ,WAAW;AAAA,gBACT;AAAA;AAAA,gBACA,YAAY;AAAA,gBACZ,SAAS,YAAY,8BAA8B;AAAA,cAAA;AAAA,cAGpD,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,IAAA;AAKN,WACE,qBAAC,SAAI,WAAW,GAAG,0BAA0B,YAAY,sBAAsB,SAAS,GACrF,UAAA;AAAA,MAAA,gBAAgB,UAAU;AAAA,MAC1B;AAAA,MACA,gBAAgB,WAAW;AAAA,IAAA,GAC9B;AAAA,EAEJ;AACF;AAEA,YAAY,cAAc;"}
1
+ {"version":3,"file":"SwitchField.mjs","sources":["../../../src/components/SwitchField/SwitchField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Switch, type SwitchSize } from \"../Switch/Switch\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Side on which the switch toggle is positioned relative to the label. */\nexport type SwitchFieldOrientation = \"right\" | \"left\";\n\nexport interface SwitchFieldProps\n extends Omit<React.ComponentPropsWithoutRef<typeof Switch>, \"size\" | \"className\"> {\n /** Side on which the switch is placed relative to the label. @default \"right\" */\n orientation?: SwitchFieldOrientation;\n /** Size variant of the switch and accompanying text. @default \"default\" */\n size?: SwitchSize;\n /** Label text displayed next to the switch. */\n label?: string;\n /** Descriptive text displayed below the label. */\n helperText?: string;\n /** Tooltip text shown when hovering the info icon next to the label. */\n infoText?: string;\n /** Accessible label for the info tooltip trigger button. Override for i18n.\n * @default \"More information\"\n */\n infoLabel?: string;\n /** Additional CSS class name for the outer wrapper. */\n className?: string;\n}\n\n/**\n * A labelled switch field with optional helper text and info tooltip. Composes\n * the {@link Switch} component with a `<label>` and description.\n *\n * @example\n * ```tsx\n * <SwitchField\n * label=\"Notifications\"\n * helperText=\"Receive push notifications\"\n * checked={enabled}\n * onCheckedChange={setEnabled}\n * />\n * ```\n */\nexport const SwitchField = React.forwardRef<React.ComponentRef<typeof Switch>, SwitchFieldProps>(\n (\n {\n className,\n orientation = \"right\",\n size = \"default\",\n label,\n helperText,\n infoText,\n infoLabel = \"More information\",\n disabled,\n id: propId,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const id = propId ?? generatedId;\n const helperTextId = helperText ? `${id}-helper` : undefined;\n\n const switchElement = (\n <Switch\n ref={ref}\n id={id}\n size={size}\n disabled={disabled}\n aria-describedby={helperTextId}\n {...props}\n />\n );\n\n const labelContent = (label || helperText) && (\n <div\n className={cn(\n \"flex min-w-0 flex-1 flex-col items-start\",\n size === \"default\" ? \"gap-1\" : \"gap-0.5\",\n )}\n >\n {label && (\n <div className=\"flex items-center gap-1\">\n <label\n htmlFor={id}\n className={cn(\n \"cursor-pointer select-none text-body-100\",\n disabled && \"cursor-not-allowed text-disabled-100\",\n size === \"default\" ? \"typography-body-1-semibold\" : \"typography-body-2-semibold\",\n )}\n >\n {label}\n </label>\n {infoText && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button type=\"button\" aria-label={infoLabel} className=\"flex shrink-0 pt-0.5\">\n <InfoCircleIcon aria-hidden=\"true\" className=\"size-5 text-body-200\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{infoText}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n )}\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"text-body-200\", // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes\n disabled && \"text-disabled-100\",\n size === \"default\" ? \"typography-body-2-regular\" : \"typography-caption-regular\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n );\n\n return (\n <div className={cn(\"flex items-start gap-2\", disabled && \"cursor-not-allowed\", className)}>\n {orientation === \"left\" && switchElement}\n {labelContent}\n {orientation === \"right\" && switchElement}\n </div>\n );\n },\n);\n\nSwitchField.displayName = \"SwitchField\";\n"],"names":[],"mappings":";;;;;;;AA2CO,MAAM,cAAc,MAAM;AAAA,EAC/B,CACE;AAAA,IACE;AAAA,IACA,cAAc;AAAA,IACd,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA,IAAI;AAAA,IACJ,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,cAAc,MAAM,MAAA;AAC1B,UAAM,KAAK,UAAU;AACrB,UAAM,eAAe,aAAa,GAAG,EAAE,YAAY;AAEnD,UAAM,gBACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,oBAAkB;AAAA,QACjB,GAAG;AAAA,MAAA;AAAA,IAAA;AAIR,UAAM,gBAAgB,SAAS,eAC7B;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,SAAS,YAAY,UAAU;AAAA,QAAA;AAAA,QAGhC,UAAA;AAAA,UAAA,SACC,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAW;AAAA,kBACT;AAAA,kBACA,YAAY;AAAA,kBACZ,SAAS,YAAY,+BAA+B;AAAA,gBAAA;AAAA,gBAGrD,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,YAEF,YACC,oBAAC,iBAAA,EACC,UAAA,qBAAC,SAAA,EACC,UAAA;AAAA,cAAA,oBAAC,kBAAe,SAAO,MACrB,8BAAC,UAAA,EAAO,MAAK,UAAS,cAAY,WAAW,WAAU,wBACrD,8BAAC,gBAAA,EAAe,eAAY,QAAO,WAAU,wBAAuB,GACtE,EAAA,CACF;AAAA,cACA,oBAAC,gBAAA,EAAe,MAAK,UAAU,UAAA,SAAA,CAAS;AAAA,YAAA,EAAA,CAC1C,EAAA,CACF;AAAA,UAAA,GAEJ;AAAA,UAED,cACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAI;AAAA,cACJ,WAAW;AAAA,gBACT;AAAA;AAAA,gBACA,YAAY;AAAA,gBACZ,SAAS,YAAY,8BAA8B;AAAA,cAAA;AAAA,cAGpD,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,IAAA;AAKN,WACE,qBAAC,SAAI,WAAW,GAAG,0BAA0B,YAAY,sBAAsB,SAAS,GACrF,UAAA;AAAA,MAAA,gBAAgB,UAAU;AAAA,MAC1B;AAAA,MACA,gBAAgB,WAAW;AAAA,IAAA,GAC9B;AAAA,EAEJ;AACF;AAEA,YAAY,cAAc;"}
@@ -2,6 +2,15 @@
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../../utils/cn.mjs";
5
+ function warnMissingAccessibleName(ariaLabel, ariaLabelledBy) {
6
+ if (process.env.NODE_ENV !== "production") {
7
+ if (!ariaLabel && !ariaLabelledBy) {
8
+ console.warn(
9
+ "SwitchToggle: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop."
10
+ );
11
+ }
12
+ }
13
+ }
5
14
  const SwitchToggle = React.forwardRef(
6
15
  ({
7
16
  className,
@@ -13,19 +22,29 @@ const SwitchToggle = React.forwardRef(
13
22
  disabled = false,
14
23
  ...props
15
24
  }, ref) => {
16
- const groupName = React.useId();
25
+ warnMissingAccessibleName(props["aria-label"], props["aria-labelledby"]);
17
26
  const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0].value);
18
27
  const isControlled = controlledValue !== void 0;
19
28
  const currentValue = isControlled ? controlledValue : internalValue;
29
+ const anySelected = options.some((o) => o.value === currentValue);
20
30
  const isSecondSelected = currentValue === options[1].value;
31
+ const buttonRefs = React.useRef([]);
21
32
  const sizeClass = size === "24" ? "px-2 py-1 typography-caption-semibold" : size === "32" ? "px-3 py-1.75 typography-body-2-semibold" : "h-10 px-4 py-2.25 typography-button-small";
22
33
  const handleSelect = (optionValue) => {
23
- if (disabled) return;
34
+ if (disabled || optionValue === currentValue) return;
24
35
  if (!isControlled) {
25
36
  setInternalValue(optionValue);
26
37
  }
27
38
  onChange?.(optionValue);
28
39
  };
40
+ const handleKeyDown = (e, index) => {
41
+ const nextIndex = e.key === "ArrowRight" || e.key === "ArrowDown" ? (index + 1) % options.length : e.key === "ArrowLeft" || e.key === "ArrowUp" ? (index - 1 + options.length) % options.length : null;
42
+ if (nextIndex === null) return;
43
+ e.preventDefault();
44
+ const nextOption = options[nextIndex];
45
+ handleSelect(nextOption.value);
46
+ buttonRefs.current[nextIndex]?.focus();
47
+ };
29
48
  return /* @__PURE__ */ jsxs(
30
49
  "div",
31
50
  {
@@ -49,36 +68,34 @@ const SwitchToggle = React.forwardRef(
49
68
  )
50
69
  }
51
70
  ),
52
- options.map((option) => {
53
- const optionId = `${groupName}-${option.value}`;
54
- return /* @__PURE__ */ jsxs(
55
- "label",
56
- {
57
- htmlFor: optionId,
58
- className: cn(
59
- "relative z-10 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-body-100",
60
- "has-focus-visible:shadow-focus-ring has-focus-visible:outline-none",
61
- disabled && "pointer-events-none",
62
- sizeClass
63
- ),
64
- children: [
65
- /* @__PURE__ */ jsx(
66
- "input",
67
- {
68
- id: optionId,
69
- type: "radio",
70
- name: groupName,
71
- value: option.value,
72
- checked: currentValue === option.value,
73
- disabled,
74
- onChange: () => handleSelect(option.value),
75
- className: "sr-only"
76
- }
71
+ options.map((option, index) => {
72
+ const isSelected = currentValue === option.value;
73
+ return (
74
+ // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation
75
+ /* @__PURE__ */ jsx(
76
+ "button",
77
+ {
78
+ ref: (el) => {
79
+ buttonRefs.current[index] = el;
80
+ },
81
+ type: "button",
82
+ role: "radio",
83
+ "aria-checked": isSelected,
84
+ tabIndex: isSelected || !anySelected && index === 0 ? 0 : -1,
85
+ disabled,
86
+ onClick: () => handleSelect(option.value),
87
+ onKeyDown: (e) => handleKeyDown(e, index),
88
+ className: cn(
89
+ "relative z-10 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-body-100",
90
+ "focus-visible:shadow-focus-ring focus-visible:outline-none",
91
+ "active:rounded-full active:bg-brand-green-50",
92
+ disabled && "pointer-events-none",
93
+ sizeClass
77
94
  ),
78
- option.label
79
- ]
80
- },
81
- option.value
95
+ children: option.label
96
+ },
97
+ option.value
98
+ )
82
99
  );
83
100
  })
84
101
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"SwitchToggle.mjs","sources":["../../../src/components/SwitchToggle/SwitchToggle.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport type SwitchToggleSize = \"24\" | \"32\" | \"40\";\n\nexport interface SwitchToggleOption {\n /** Display label for the option */\n label: string;\n /** Value identifier for the option */\n value: string;\n}\n\nexport interface SwitchToggleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Size variant */\n size?: SwitchToggleSize;\n /** The two options to toggle between */\n options: [SwitchToggleOption, SwitchToggleOption];\n /** Currently selected value */\n value?: string;\n /** Default selected value (uncontrolled) */\n defaultValue?: string;\n /** Callback when the selected value changes */\n onChange?: (value: string) => void;\n /** Whether the toggle is disabled */\n disabled?: boolean;\n}\n\nexport const SwitchToggle = React.forwardRef<HTMLDivElement, SwitchToggleProps>(\n (\n {\n className,\n size = \"24\",\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n disabled = false,\n ...props\n },\n ref,\n ) => {\n const groupName = React.useId();\n // Tracks selection for uncontrolled usage; ignored when `value` prop is provided\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0].value);\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n const isSecondSelected = currentValue === options[1].value;\n\n const sizeClass =\n size === \"24\"\n ? \"px-2 py-1 typography-caption-semibold\"\n : size === \"32\"\n ? \"px-3 py-1.75 typography-body-2-semibold\"\n : \"h-10 px-4 py-2.25 typography-button-small\";\n\n const handleSelect = (optionValue: string) => {\n if (disabled) return;\n if (!isControlled) {\n setInternalValue(optionValue);\n }\n onChange?.(optionValue);\n };\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n className={cn(\n \"relative inline-grid grid-cols-2 rounded-full border border-neutral-200 p-1\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n {...props}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n \"absolute inset-y-1 left-1 w-[calc(50%-4px)] rounded-full border border-brand-green-500 bg-brand-green-50\",\n \"motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-in-out\",\n isSecondSelected && \"translate-x-full\",\n )}\n />\n {options.map((option) => {\n const optionId = `${groupName}-${option.value}`;\n return (\n <label\n key={option.value}\n htmlFor={optionId}\n className={cn(\n \"relative z-10 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-body-100\",\n \"has-focus-visible:shadow-focus-ring has-focus-visible:outline-none\",\n disabled && \"pointer-events-none\",\n sizeClass,\n )}\n >\n <input\n id={optionId}\n type=\"radio\"\n name={groupName}\n value={option.value}\n checked={currentValue === option.value}\n disabled={disabled}\n onChange={() => handleSelect(option.value)}\n className=\"sr-only\"\n />\n {option.label}\n </label>\n );\n })}\n </div>\n );\n },\n);\n\nSwitchToggle.displayName = \"SwitchToggle\";\n"],"names":[],"mappings":";;;;AA2BO,MAAM,eAAe,MAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,YAAY,MAAM,MAAA;AAExB,UAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,gBAAgB,QAAQ,CAAC,EAAE,KAAK;AACzF,UAAM,eAAe,oBAAoB;AACzC,UAAM,eAAe,eAAe,kBAAkB;AACtD,UAAM,mBAAmB,iBAAiB,QAAQ,CAAC,EAAE;AAErD,UAAM,YACJ,SAAS,OACL,0CACA,SAAS,OACP,4CACA;AAER,UAAM,eAAe,CAAC,gBAAwB;AAC5C,UAAI,SAAU;AACd,UAAI,CAAC,cAAc;AACjB,yBAAiB,WAAW;AAAA,MAC9B;AACA,iBAAW,WAAW;AAAA,IACxB;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA,oBAAoB;AAAA,cAAA;AAAA,YACtB;AAAA,UAAA;AAAA,UAED,QAAQ,IAAI,CAAC,WAAW;AACvB,kBAAM,WAAW,GAAG,SAAS,IAAI,OAAO,KAAK;AAC7C,mBACE;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,SAAS;AAAA,gBACT,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,kBACA,YAAY;AAAA,kBACZ;AAAA,gBAAA;AAAA,gBAGF,UAAA;AAAA,kBAAA;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,IAAI;AAAA,sBACJ,MAAK;AAAA,sBACL,MAAM;AAAA,sBACN,OAAO,OAAO;AAAA,sBACd,SAAS,iBAAiB,OAAO;AAAA,sBACjC;AAAA,sBACA,UAAU,MAAM,aAAa,OAAO,KAAK;AAAA,sBACzC,WAAU;AAAA,oBAAA;AAAA,kBAAA;AAAA,kBAEX,OAAO;AAAA,gBAAA;AAAA,cAAA;AAAA,cAnBH,OAAO;AAAA,YAAA;AAAA,UAsBlB,CAAC;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,aAAa,cAAc;"}
1
+ {"version":3,"file":"SwitchToggle.mjs","sources":["../../../src/components/SwitchToggle/SwitchToggle.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Height of the switch toggle in pixels. */\nexport type SwitchToggleSize = \"24\" | \"32\" | \"40\";\n\n/** Describes one side of the binary toggle. */\nexport interface SwitchToggleOption {\n /** Display label for the option. */\n label: string;\n /** Value identifier returned via `onChange`. */\n value: string;\n}\n\nexport interface SwitchToggleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Height of the toggle in pixels. @default \"24\" */\n size?: SwitchToggleSize;\n /** The two options to toggle between (exactly two required). */\n options: [SwitchToggleOption, SwitchToggleOption];\n /** Currently selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). */\n defaultValue?: string;\n /** Callback fired when the selected value changes. */\n onChange?: (value: string) => void;\n /** Whether the toggle is disabled. @default false */\n disabled?: boolean;\n}\n\nfunction warnMissingAccessibleName(ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"SwitchToggle: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A binary segmented toggle rendered as a `radiogroup`. The active option is\n * highlighted with a sliding pill indicator. Supports both controlled and\n * uncontrolled usage.\n *\n * @example\n * ```tsx\n * <SwitchToggle\n * options={[\n * { label: \"Monthly\", value: \"monthly\" },\n * { label: \"Yearly\", value: \"yearly\" },\n * ]}\n * value={billing}\n * onChange={setBilling}\n * />\n * ```\n */\nexport const SwitchToggle = React.forwardRef<HTMLDivElement, SwitchToggleProps>(\n (\n {\n className,\n size = \"24\",\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n disabled = false,\n ...props\n },\n ref,\n ) => {\n warnMissingAccessibleName(props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n // Tracks selection for uncontrolled usage; ignored when `value` prop is provided\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0].value);\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n const anySelected = options.some((o) => o.value === currentValue);\n const isSecondSelected = currentValue === options[1].value;\n const buttonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const sizeClass =\n size === \"24\"\n ? \"px-2 py-1 typography-caption-semibold\"\n : size === \"32\"\n ? \"px-3 py-1.75 typography-body-2-semibold\"\n : \"h-10 px-4 py-2.25 typography-button-small\";\n\n const handleSelect = (optionValue: string) => {\n if (disabled || optionValue === currentValue) return;\n if (!isControlled) {\n setInternalValue(optionValue);\n }\n onChange?.(optionValue);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const nextIndex =\n e.key === \"ArrowRight\" || e.key === \"ArrowDown\"\n ? (index + 1) % options.length\n : e.key === \"ArrowLeft\" || e.key === \"ArrowUp\"\n ? (index - 1 + options.length) % options.length\n : null;\n if (nextIndex === null) return;\n e.preventDefault();\n const nextOption = options[nextIndex] as (typeof options)[number];\n handleSelect(nextOption.value);\n buttonRefs.current[nextIndex]?.focus();\n };\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n className={cn(\n \"relative inline-grid grid-cols-2 rounded-full border border-neutral-200 p-1\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n {...props}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n \"absolute inset-y-1 left-1 w-[calc(50%-4px)] rounded-full border border-brand-green-500 bg-brand-green-50\",\n \"motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-in-out\",\n isSecondSelected && \"translate-x-full\",\n )}\n />\n {options.map((option, index) => {\n const isSelected = currentValue === option.value;\n return (\n // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation\n <button\n key={option.value}\n ref={(el) => {\n buttonRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n tabIndex={isSelected || (!anySelected && index === 0) ? 0 : -1}\n disabled={disabled}\n onClick={() => handleSelect(option.value)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n className={cn(\n \"relative z-10 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-body-100\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"active:rounded-full active:bg-brand-green-50\",\n disabled && \"pointer-events-none\",\n sizeClass,\n )}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n );\n },\n);\n\nSwitchToggle.displayName = \"SwitchToggle\";\n"],"names":[],"mappings":";;;;AA6BA,SAAS,0BAA0B,WAAoB,gBAAyB;AAC9E,MAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,QAAI,CAAC,aAAa,CAAC,gBAAgB;AACjC,cAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAmBO,MAAM,eAAe,MAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,GAAG;AAAA,EAAA,GAEL,QACG;AACH,8BAA0B,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC;AAGvE,UAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,gBAAgB,QAAQ,CAAC,EAAE,KAAK;AACzF,UAAM,eAAe,oBAAoB;AACzC,UAAM,eAAe,eAAe,kBAAkB;AACtD,UAAM,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,YAAY;AAChE,UAAM,mBAAmB,iBAAiB,QAAQ,CAAC,EAAE;AACrD,UAAM,aAAa,MAAM,OAAqC,EAAE;AAEhE,UAAM,YACJ,SAAS,OACL,0CACA,SAAS,OACP,4CACA;AAER,UAAM,eAAe,CAAC,gBAAwB;AAC5C,UAAI,YAAY,gBAAgB,aAAc;AAC9C,UAAI,CAAC,cAAc;AACjB,yBAAiB,WAAW;AAAA,MAC9B;AACA,iBAAW,WAAW;AAAA,IACxB;AAEA,UAAM,gBAAgB,CAAC,GAAwB,UAAkB;AAC/D,YAAM,YACJ,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,eAC/B,QAAQ,KAAK,QAAQ,SACtB,EAAE,QAAQ,eAAe,EAAE,QAAQ,aAChC,QAAQ,IAAI,QAAQ,UAAU,QAAQ,SACvC;AACR,UAAI,cAAc,KAAM;AACxB,QAAE,eAAA;AACF,YAAM,aAAa,QAAQ,SAAS;AACpC,mBAAa,WAAW,KAAK;AAC7B,iBAAW,QAAQ,SAAS,GAAG,MAAA;AAAA,IACjC;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA,oBAAoB;AAAA,cAAA;AAAA,YACtB;AAAA,UAAA;AAAA,UAED,QAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,kBAAM,aAAa,iBAAiB,OAAO;AAC3C;AAAA;AAAA,cAEE;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAEC,KAAK,CAAC,OAAO;AACX,+BAAW,QAAQ,KAAK,IAAI;AAAA,kBAC9B;AAAA,kBACA,MAAK;AAAA,kBACL,MAAK;AAAA,kBACL,gBAAc;AAAA,kBACd,UAAU,cAAe,CAAC,eAAe,UAAU,IAAK,IAAI;AAAA,kBAC5D;AAAA,kBACA,SAAS,MAAM,aAAa,OAAO,KAAK;AAAA,kBACxC,WAAW,CAAC,MAAM,cAAc,GAAG,KAAK;AAAA,kBACxC,WAAW;AAAA,oBACT;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA,YAAY;AAAA,oBACZ;AAAA,kBAAA;AAAA,kBAGD,UAAA,OAAO;AAAA,gBAAA;AAAA,gBAnBH,OAAO;AAAA,cAAA;AAAA;AAAA,UAsBlB,CAAC;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,aAAa,cAAc;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.mjs","sources":["../../../src/components/Tabs/Tabs.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport type * as React from \"react\";\n\nexport type TabsProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>;\n\nexport const Tabs = TabsPrimitive.Root;\n"],"names":[],"mappings":";;AAKO,MAAM,OAAO,cAAc;"}
1
+ {"version":3,"file":"Tabs.mjs","sources":["../../../src/components/Tabs/Tabs.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport type * as React from \"react\";\n\n/** Props for the {@link Tabs} root component. Extends Radix `Tabs.Root` props. */\nexport type TabsProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>;\n\n/**\n * Root container for a tabbed interface. Manages the active tab state and\n * coordinates {@link TabsList}, {@link TabsTrigger}, and {@link TabsContent}.\n *\n * Built on Radix UI `Tabs`.\n *\n * @example\n * ```tsx\n * <Tabs defaultValue=\"tab1\">\n * <TabsList>\n * <TabsTrigger value=\"tab1\">Tab 1</TabsTrigger>\n * <TabsTrigger value=\"tab2\">Tab 2</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"tab1\">Content 1</TabsContent>\n * <TabsContent value=\"tab2\">Content 2</TabsContent>\n * </Tabs>\n * ```\n */\nexport const Tabs = TabsPrimitive.Root;\n"],"names":[],"mappings":";;AAwBO,MAAM,OAAO,cAAc;"}
@@ -1 +1 @@
1
- {"version":3,"file":"TabsContent.mjs","sources":["../../../src/components/Tabs/TabsContent.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport type TabsContentProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>;\n\nexport const TabsContent = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Content>,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\"focus-visible:outline-none\", className)}\n {...props}\n />\n));\n\nTabsContent.displayName = \"TabsContent\";\n"],"names":[],"mappings":";;;;;AAMO,MAAM,cAAc,MAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,cAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAW,GAAG,8BAA8B,SAAS;AAAA,IACpD,GAAG;AAAA,EAAA;AACN,CACD;AAED,YAAY,cAAc;"}
1
+ {"version":3,"file":"TabsContent.mjs","sources":["../../../src/components/Tabs/TabsContent.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Props for the {@link TabsContent} panel component. */\nexport type TabsContentProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>;\n\n/** Renders the content panel for a given tab `value`. Only visible when its value matches the active tab. */\nexport const TabsContent = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Content>,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\"focus-visible:outline-none\", className)}\n {...props}\n />\n));\n\nTabsContent.displayName = \"TabsContent\";\n"],"names":[],"mappings":";;;;;AAQO,MAAM,cAAc,MAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,cAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAW,GAAG,8BAA8B,SAAS;AAAA,IACpD,GAAG;AAAA,EAAA;AACN,CACD;AAED,YAAY,cAAc;"}