@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.
- package/dist/cjs/components/Alert/Alert.cjs +4 -4
- package/dist/cjs/components/Alert/Alert.cjs.map +1 -1
- package/dist/cjs/components/Avatar/Avatar.cjs.map +1 -1
- package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
- package/dist/cjs/components/Button/Button.cjs +9 -9
- package/dist/cjs/components/Button/Button.cjs.map +1 -1
- package/dist/cjs/components/Checkbox/Checkbox.cjs +6 -7
- package/dist/cjs/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/cjs/components/Chip/Chip.cjs +2 -2
- package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
- package/dist/cjs/components/Count/Count.cjs.map +1 -1
- package/dist/cjs/components/DatePicker/DatePicker.cjs +30 -7
- package/dist/cjs/components/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/cjs/components/Divider/Divider.cjs.map +1 -1
- package/dist/cjs/components/IconButton/IconButton.cjs +10 -10
- package/dist/cjs/components/IconButton/IconButton.cjs.map +1 -1
- package/dist/cjs/components/Icons/ArrowRightIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/ArrowUpRightIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/CheckCircleIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/ChevronLeftIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/ChevronRightIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/CloseIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/CrossIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/CrownIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/ErrorCircleIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/ErrorIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/EyeClosedIcon.cjs +49 -0
- package/dist/cjs/components/Icons/EyeClosedIcon.cjs.map +1 -0
- package/dist/cjs/components/Icons/EyeIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/FireIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/HomeIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/InfoCircleIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/InfoIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/MicrophoneIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/PlusIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/SpinnerIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/StopIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/SuccessIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/WarningIcon.cjs.map +1 -1
- package/dist/cjs/components/Icons/WarningTriangleIcon.cjs.map +1 -1
- package/dist/cjs/components/Logo/Logo.cjs +11 -11
- package/dist/cjs/components/Logo/Logo.cjs.map +1 -1
- package/dist/cjs/components/Pagination/Pagination.cjs +3 -3
- package/dist/cjs/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/cjs/components/PasswordField/PasswordField.cjs +59 -0
- package/dist/cjs/components/PasswordField/PasswordField.cjs.map +1 -0
- package/dist/cjs/components/Pill/Pill.cjs.map +1 -1
- package/dist/cjs/components/ProgressBar/ProgressBar.cjs.map +1 -1
- package/dist/cjs/components/Radio/Radio.cjs +1 -1
- package/dist/cjs/components/Radio/Radio.cjs.map +1 -1
- package/dist/cjs/components/RadioGroup/RadioGroup.cjs.map +1 -1
- package/dist/cjs/components/Slider/Slider.cjs.map +1 -1
- package/dist/cjs/components/Slider/SliderThumb.cjs +1 -0
- package/dist/cjs/components/Slider/SliderThumb.cjs.map +1 -1
- package/dist/cjs/components/Snackbar/Snackbar.cjs.map +1 -1
- package/dist/cjs/components/Switch/Switch.cjs +1 -0
- package/dist/cjs/components/Switch/Switch.cjs.map +1 -1
- package/dist/cjs/components/SwitchField/SwitchField.cjs.map +1 -1
- package/dist/cjs/components/SwitchToggle/SwitchToggle.cjs +48 -31
- package/dist/cjs/components/SwitchToggle/SwitchToggle.cjs.map +1 -1
- package/dist/cjs/components/Tabs/Tabs.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsContent.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsList.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsTrigger.cjs +2 -0
- package/dist/cjs/components/Tabs/TabsTrigger.cjs.map +1 -1
- package/dist/cjs/components/TextField/TextField.cjs +1 -1
- package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
- package/dist/cjs/components/Toast/Toast.cjs +1 -1
- package/dist/cjs/components/Toast/Toast.cjs.map +1 -1
- package/dist/cjs/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/components/Alert/Alert.mjs +4 -4
- package/dist/components/Alert/Alert.mjs.map +1 -1
- package/dist/components/Avatar/Avatar.mjs.map +1 -1
- package/dist/components/Badge/Badge.mjs.map +1 -1
- package/dist/components/Button/Button.mjs +9 -9
- package/dist/components/Button/Button.mjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.mjs +6 -7
- package/dist/components/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/components/Chip/Chip.mjs +2 -2
- package/dist/components/Chip/Chip.mjs.map +1 -1
- package/dist/components/Count/Count.mjs.map +1 -1
- package/dist/components/DatePicker/DatePicker.mjs +30 -7
- package/dist/components/DatePicker/DatePicker.mjs.map +1 -1
- package/dist/components/Divider/Divider.mjs.map +1 -1
- package/dist/components/IconButton/IconButton.mjs +10 -10
- package/dist/components/IconButton/IconButton.mjs.map +1 -1
- package/dist/components/Icons/ArrowRightIcon.mjs.map +1 -1
- package/dist/components/Icons/ArrowUpRightIcon.mjs.map +1 -1
- package/dist/components/Icons/CheckCircleIcon.mjs.map +1 -1
- package/dist/components/Icons/ChevronLeftIcon.mjs.map +1 -1
- package/dist/components/Icons/ChevronRightIcon.mjs.map +1 -1
- package/dist/components/Icons/CloseIcon.mjs.map +1 -1
- package/dist/components/Icons/CrossIcon.mjs.map +1 -1
- package/dist/components/Icons/CrownIcon.mjs.map +1 -1
- package/dist/components/Icons/ErrorCircleIcon.mjs.map +1 -1
- package/dist/components/Icons/ErrorIcon.mjs.map +1 -1
- package/dist/components/Icons/EyeClosedIcon.mjs +32 -0
- package/dist/components/Icons/EyeClosedIcon.mjs.map +1 -0
- package/dist/components/Icons/EyeIcon.mjs.map +1 -1
- package/dist/components/Icons/FireIcon.mjs.map +1 -1
- package/dist/components/Icons/HomeIcon.mjs.map +1 -1
- package/dist/components/Icons/InfoCircleIcon.mjs.map +1 -1
- package/dist/components/Icons/InfoIcon.mjs.map +1 -1
- package/dist/components/Icons/MicrophoneIcon.mjs.map +1 -1
- package/dist/components/Icons/PlusIcon.mjs.map +1 -1
- package/dist/components/Icons/SpinnerIcon.mjs.map +1 -1
- package/dist/components/Icons/StopIcon.mjs.map +1 -1
- package/dist/components/Icons/SuccessIcon.mjs.map +1 -1
- package/dist/components/Icons/WarningIcon.mjs.map +1 -1
- package/dist/components/Icons/WarningTriangleIcon.mjs.map +1 -1
- package/dist/components/Logo/Logo.mjs +11 -11
- package/dist/components/Logo/Logo.mjs.map +1 -1
- package/dist/components/Pagination/Pagination.mjs +3 -3
- package/dist/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/components/PasswordField/PasswordField.mjs +42 -0
- package/dist/components/PasswordField/PasswordField.mjs.map +1 -0
- package/dist/components/Pill/Pill.mjs.map +1 -1
- package/dist/components/ProgressBar/ProgressBar.mjs.map +1 -1
- package/dist/components/Radio/Radio.mjs +1 -1
- package/dist/components/Radio/Radio.mjs.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.mjs.map +1 -1
- package/dist/components/Slider/Slider.mjs.map +1 -1
- package/dist/components/Slider/SliderThumb.mjs +1 -0
- package/dist/components/Slider/SliderThumb.mjs.map +1 -1
- package/dist/components/Snackbar/Snackbar.mjs.map +1 -1
- package/dist/components/Switch/Switch.mjs +1 -0
- package/dist/components/Switch/Switch.mjs.map +1 -1
- package/dist/components/SwitchField/SwitchField.mjs.map +1 -1
- package/dist/components/SwitchToggle/SwitchToggle.mjs +48 -31
- package/dist/components/SwitchToggle/SwitchToggle.mjs.map +1 -1
- package/dist/components/Tabs/Tabs.mjs.map +1 -1
- package/dist/components/Tabs/TabsContent.mjs.map +1 -1
- package/dist/components/Tabs/TabsList.mjs.map +1 -1
- package/dist/components/Tabs/TabsTrigger.mjs +2 -0
- package/dist/components/Tabs/TabsTrigger.mjs.map +1 -1
- package/dist/components/TextField/TextField.mjs +1 -1
- package/dist/components/TextField/TextField.mjs.map +1 -1
- package/dist/components/Toast/Toast.mjs +1 -1
- package/dist/components/Toast/Toast.mjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.mjs.map +1 -1
- package/dist/index.d.ts +535 -142
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/dist/styles/theme.css +9 -0
- 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
|
|
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 /**
|
|
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 …\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 /**
|
|
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
|
|
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 0–100. */\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
|
|
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":";;;;
|
|
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 /**
|
|
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":";;;;;
|
|
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 /**
|
|
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
|
-
|
|
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
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
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":";;
|
|
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":";;;;;
|
|
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;"}
|