@fanvue/ui 3.6.0 → 3.7.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/charts.d.ts +1 -1
- package/dist/cjs/components/Chart/ChartCard.cjs +2 -2
- package/dist/cjs/components/Chart/ChartCard.cjs.map +1 -1
- package/dist/cjs/components/Chip/Chip.cjs +46 -10
- package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsList.cjs +20 -12
- package/dist/cjs/components/Tabs/TabsList.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsTrigger.cjs +8 -7
- package/dist/cjs/components/Tabs/TabsTrigger.cjs.map +1 -1
- package/dist/components/Chart/ChartCard.mjs +2 -2
- package/dist/components/Chart/ChartCard.mjs.map +1 -1
- package/dist/components/Chip/Chip.mjs +46 -10
- package/dist/components/Chip/Chip.mjs.map +1 -1
- package/dist/components/Tabs/TabsList.mjs +20 -12
- package/dist/components/Tabs/TabsList.mjs.map +1 -1
- package/dist/components/Tabs/TabsTrigger.mjs +8 -7
- package/dist/components/Tabs/TabsTrigger.mjs.map +1 -1
- package/dist/index.d.ts +6 -1
- package/package.json +2 -1
package/dist/charts.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export declare interface ChartCardProps extends Omit<React_2.HTMLAttributes<HTML
|
|
|
39
39
|
tooltipAriaLabel?: string;
|
|
40
40
|
/** Date range or period label shown below the subtitle. */
|
|
41
41
|
dateInfo?: React_2.ReactNode;
|
|
42
|
-
/** Trend indicator chip config. */
|
|
42
|
+
/** Trend indicator chip config. Only rendered when {@link subtitle} is also provided. */
|
|
43
43
|
trendChip?: {
|
|
44
44
|
/** Display label (e.g. "12.5%"). */
|
|
45
45
|
label: React_2.ReactNode;
|
|
@@ -64,13 +64,13 @@ const ChartCard = React__namespace.forwardRef(
|
|
|
64
64
|
/* @__PURE__ */ jsxRuntime.jsx(Tooltip.TooltipContent, { children: tooltip })
|
|
65
65
|
] }) })
|
|
66
66
|
] }),
|
|
67
|
-
subtitle && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
67
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
68
68
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "typography-header-heading-sm text-content-primary", children: subtitle }),
|
|
69
69
|
trendChip && /* @__PURE__ */ jsxRuntime.jsx(
|
|
70
70
|
"span",
|
|
71
71
|
{
|
|
72
72
|
className: cn.cn(
|
|
73
|
-
"typography-description-12px-semibold rounded-full px-2 py-0.5",
|
|
73
|
+
"typography-description-12px-semibold whitespace-nowrap rounded-full px-2 py-0.5",
|
|
74
74
|
TREND_CLASSES[trendChip.trend]
|
|
75
75
|
),
|
|
76
76
|
children: trendChip.label
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartCard.cjs","sources":["../../../../src/components/Chart/ChartCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card } from \"../Card/Card\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Skeleton } from \"../Skeleton/Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Props for {@link ChartCard}. */\nexport interface ChartCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Card title text. Pass translated string for i18n. */\n title: React.ReactNode;\n /** Large subtitle value (e.g. formatted price or count). */\n subtitle?: React.ReactNode;\n /** Tooltip text shown next to the title. Pass translated string for i18n. */\n tooltip?: React.ReactNode;\n /** Accessible label for the info tooltip trigger. Override for i18n. @default \"More info\" */\n tooltipAriaLabel?: string;\n /** Date range or period label shown below the subtitle. */\n dateInfo?: React.ReactNode;\n /** Trend indicator chip config. */\n trendChip?: {\n /** Display label (e.g. \"12.5%\"). */\n label: React.ReactNode;\n /** Whether the trend is positive (green) or negative (red). */\n trend: \"positive\" | \"negative\";\n };\n /** Show loading skeleton instead of content. @default false */\n loading?: boolean;\n /** Chart content rendered below the header. */\n children?: React.ReactNode;\n}\n\nconst TREND_CLASSES: Record<\"positive\" | \"negative\", string> = {\n positive: \"bg-success-surface text-success-content\",\n negative: \"bg-error-surface text-error-content\",\n};\n\n/**\n * Wraps any chart with a structured header containing title, subtitle,\n * optional trend chip, date range label, info tooltip, and a loading\n * skeleton state.\n *\n * @example\n * ```tsx\n * <ChartCard\n * title=\"Revenue\"\n * subtitle=\"$4,523\"\n * trendChip={{ label: \"+12.5%\", trend: \"positive\" }}\n * dateInfo=\"Jan 1 – Mar 17\"\n * tooltip=\"Total revenue for the selected period.\"\n * >\n * <MyLineChart />\n * </ChartCard>\n * ```\n */\nexport const ChartCard = React.forwardRef<HTMLDivElement, ChartCardProps>(\n (\n {\n className,\n title,\n subtitle,\n tooltip,\n tooltipAriaLabel = \"More info\",\n dateInfo,\n trendChip,\n loading = false,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <Card ref={ref} variant=\"outlined\" noPadding className={className} {...props}>\n <div className=\"flex flex-col gap-2 p-4\">\n {loading ? (\n <>\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-4 w-32\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-7 w-44\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-3 w-24\" />\n </>\n ) : (\n <>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {title}\n </span>\n {tooltip && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={tooltipAriaLabel}\n icon={<InfoCircleIcon className=\"size-4 text-content-tertiary\" />}\n />\n </TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n {subtitle && (\n <div className=\"flex items-center gap-2\">\n <span className=\"typography-header-heading-sm text-content-primary\">\n {subtitle}\n </span>\n {trendChip && (\n <span\n className={cn(\n \"typography-description-12px-semibold rounded-full px-2 py-0.5\",\n TREND_CLASSES[trendChip.trend],\n )}\n >\n {trendChip.label}\n </span>\n )}\n </div>\n )}\n {dateInfo && (\n <span className=\"typography-description-12px-regular text-content-tertiary\">\n {dateInfo}\n </span>\n )}\n </>\n )}\n <div className=\"mt-auto\">{children}</div>\n </div>\n </Card>\n );\n },\n);\nChartCard.displayName = \"ChartCard\";\n"],"names":["React","jsx","Card","jsxs","Fragment","Skeleton","TooltipProvider","Tooltip","TooltipTrigger","IconButton","InfoCircleIcon","TooltipContent","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,gBAAyD;AAAA,EAC7D,UAAU;AAAA,EACV,UAAU;AACZ;AAoBO,MAAM,YAAYA,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACEC,2BAAAA,IAACC,KAAAA,MAAA,EAAK,KAAU,SAAQ,YAAW,WAAS,MAAC,WAAuB,GAAG,OACrE,UAAAC,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,UACCA,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,QAAAH,+BAACI,SAAAA,YAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,WAAA,CAAW;AAAA,MAAA,EAAA,CACpE,IAEAF,2BAAAA,KAAAC,WAAAA,UAAA,EACE,UAAA;AAAA,QAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,4DACb,UAAA,OACH;AAAA,UACC,WACCA,2BAAAA,IAACK,yBAAA,EACC,UAAAH,gCAACI,QAAAA,SAAA,EACC,UAAA;AAAA,YAAAN,2BAAAA,IAACO,QAAAA,gBAAA,EAAe,SAAO,MACrB,UAAAP,2BAAAA;AAAAA,cAACQ,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,MAAMR,2BAAAA,IAACS,eAAAA,gBAAA,EAAe,WAAU,+BAAA,CAA+B;AAAA,cAAA;AAAA,YAAA,GAEnE;AAAA,YACAT,2BAAAA,IAACU,QAAAA,kBAAgB,UAAA,QAAA,CAAQ;AAAA,UAAA,EAAA,CAC3B,EAAA,CACF;AAAA,QAAA,GAEJ;AAAA,QACC,YACCR,2BAAAA,KAAC,OAAA,EAAI,WAAU,
|
|
1
|
+
{"version":3,"file":"ChartCard.cjs","sources":["../../../../src/components/Chart/ChartCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card } from \"../Card/Card\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Skeleton } from \"../Skeleton/Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Props for {@link ChartCard}. */\nexport interface ChartCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Card title text. Pass translated string for i18n. */\n title: React.ReactNode;\n /** Large subtitle value (e.g. formatted price or count). */\n subtitle?: React.ReactNode;\n /** Tooltip text shown next to the title. Pass translated string for i18n. */\n tooltip?: React.ReactNode;\n /** Accessible label for the info tooltip trigger. Override for i18n. @default \"More info\" */\n tooltipAriaLabel?: string;\n /** Date range or period label shown below the subtitle. */\n dateInfo?: React.ReactNode;\n /** Trend indicator chip config. Only rendered when {@link subtitle} is also provided. */\n trendChip?: {\n /** Display label (e.g. \"12.5%\"). */\n label: React.ReactNode;\n /** Whether the trend is positive (green) or negative (red). */\n trend: \"positive\" | \"negative\";\n };\n /** Show loading skeleton instead of content. @default false */\n loading?: boolean;\n /** Chart content rendered below the header. */\n children?: React.ReactNode;\n}\n\nconst TREND_CLASSES: Record<\"positive\" | \"negative\", string> = {\n positive: \"bg-success-surface text-success-content\",\n negative: \"bg-error-surface text-error-content\",\n};\n\n/**\n * Wraps any chart with a structured header containing title, subtitle,\n * optional trend chip, date range label, info tooltip, and a loading\n * skeleton state.\n *\n * @example\n * ```tsx\n * <ChartCard\n * title=\"Revenue\"\n * subtitle=\"$4,523\"\n * trendChip={{ label: \"+12.5%\", trend: \"positive\" }}\n * dateInfo=\"Jan 1 – Mar 17\"\n * tooltip=\"Total revenue for the selected period.\"\n * >\n * <MyLineChart />\n * </ChartCard>\n * ```\n */\nexport const ChartCard = React.forwardRef<HTMLDivElement, ChartCardProps>(\n (\n {\n className,\n title,\n subtitle,\n tooltip,\n tooltipAriaLabel = \"More info\",\n dateInfo,\n trendChip,\n loading = false,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <Card ref={ref} variant=\"outlined\" noPadding className={className} {...props}>\n <div className=\"flex flex-col gap-2 p-4\">\n {loading ? (\n <>\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-4 w-32\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-7 w-44\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-3 w-24\" />\n </>\n ) : (\n <>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {title}\n </span>\n {tooltip && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={tooltipAriaLabel}\n icon={<InfoCircleIcon className=\"size-4 text-content-tertiary\" />}\n />\n </TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n {subtitle && (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"typography-header-heading-sm text-content-primary\">\n {subtitle}\n </span>\n {trendChip && (\n <span\n className={cn(\n \"typography-description-12px-semibold whitespace-nowrap rounded-full px-2 py-0.5\",\n TREND_CLASSES[trendChip.trend],\n )}\n >\n {trendChip.label}\n </span>\n )}\n </div>\n )}\n {dateInfo && (\n <span className=\"typography-description-12px-regular text-content-tertiary\">\n {dateInfo}\n </span>\n )}\n </>\n )}\n <div className=\"mt-auto\">{children}</div>\n </div>\n </Card>\n );\n },\n);\nChartCard.displayName = \"ChartCard\";\n"],"names":["React","jsx","Card","jsxs","Fragment","Skeleton","TooltipProvider","Tooltip","TooltipTrigger","IconButton","InfoCircleIcon","TooltipContent","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,gBAAyD;AAAA,EAC7D,UAAU;AAAA,EACV,UAAU;AACZ;AAoBO,MAAM,YAAYA,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACEC,2BAAAA,IAACC,KAAAA,MAAA,EAAK,KAAU,SAAQ,YAAW,WAAS,MAAC,WAAuB,GAAG,OACrE,UAAAC,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,UACCA,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,QAAAH,+BAACI,SAAAA,YAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,uCACjEA,SAAAA,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,WAAA,CAAW;AAAA,MAAA,EAAA,CACpE,IAEAF,2BAAAA,KAAAC,WAAAA,UAAA,EACE,UAAA;AAAA,QAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,4DACb,UAAA,OACH;AAAA,UACC,WACCA,2BAAAA,IAACK,yBAAA,EACC,UAAAH,gCAACI,QAAAA,SAAA,EACC,UAAA;AAAA,YAAAN,2BAAAA,IAACO,QAAAA,gBAAA,EAAe,SAAO,MACrB,UAAAP,2BAAAA;AAAAA,cAACQ,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,MAAMR,2BAAAA,IAACS,eAAAA,gBAAA,EAAe,WAAU,+BAAA,CAA+B;AAAA,cAAA;AAAA,YAAA,GAEnE;AAAA,YACAT,2BAAAA,IAACU,QAAAA,kBAAgB,UAAA,QAAA,CAAQ;AAAA,UAAA,EAAA,CAC3B,EAAA,CACF;AAAA,QAAA,GAEJ;AAAA,QACC,YACCR,2BAAAA,KAAC,OAAA,EAAI,WAAU,qCACb,UAAA;AAAA,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,qDACb,UAAA,UACH;AAAA,UACC,aACCA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWW,GAAAA;AAAAA,gBACT;AAAA,gBACA,cAAc,UAAU,KAAK;AAAA,cAAA;AAAA,cAG9B,UAAA,UAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb,GAEJ;AAAA,QAED,YACCX,2BAAAA,IAAC,QAAA,EAAK,WAAU,6DACb,UAAA,SAAA,CACH;AAAA,MAAA,GAEJ;AAAA,MAEFA,2BAAAA,IAAC,OAAA,EAAI,WAAU,WAAW,SAAA,CAAS;AAAA,IAAA,EAAA,CACrC,EAAA,CACF;AAAA,EAEJ;AACF;AACA,UAAU,cAAc;;"}
|
|
@@ -42,32 +42,67 @@ const Chip = React__namespace.forwardRef(
|
|
|
42
42
|
const isInteractive = !!onClick && !asChild;
|
|
43
43
|
const Comp = asChild ? reactSlot.Slot : isInteractive ? "button" : "span";
|
|
44
44
|
const isDark = variant === "dark";
|
|
45
|
+
const showDottedBorder = !isDark && dotted && !selected && !asChild;
|
|
46
|
+
const dottedBorder = showDottedBorder ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
47
|
+
"svg",
|
|
48
|
+
{
|
|
49
|
+
"aria-hidden": "true",
|
|
50
|
+
className: cn.cn(
|
|
51
|
+
"pointer-events-none absolute inset-0 size-full overflow-visible",
|
|
52
|
+
disabled ? "text-buttons-chip-disabled" : "text-buttons-chip-dotted-default",
|
|
53
|
+
isInteractive && !disabled && "group-hover:text-buttons-chip-dotted-hover-stroke group-active:text-buttons-chip-dotted-hover-stroke"
|
|
54
|
+
),
|
|
55
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
56
|
+
"rect",
|
|
57
|
+
{
|
|
58
|
+
x: "0.5",
|
|
59
|
+
y: "0.5",
|
|
60
|
+
rx: variant === "square" ? 7.5 : size === "40" ? 19.5 : 15.5,
|
|
61
|
+
fill: "none",
|
|
62
|
+
stroke: "currentColor",
|
|
63
|
+
strokeWidth: "1",
|
|
64
|
+
strokeDasharray: variant === "square" && size === "40" ? "8 8" : "6 6",
|
|
65
|
+
style: { width: "calc(100% - 1px)", height: "calc(100% - 1px)" }
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
) : null;
|
|
45
70
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
46
71
|
Comp,
|
|
47
72
|
{
|
|
48
73
|
ref,
|
|
49
74
|
"data-testid": "chip",
|
|
50
75
|
className: cn.cn(
|
|
51
|
-
"
|
|
76
|
+
"relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150",
|
|
52
77
|
// Shape
|
|
53
78
|
variant === "square" ? "rounded-xs" : "rounded-full",
|
|
54
|
-
// Size
|
|
55
|
-
size === "32" && "h-8 py-1",
|
|
56
|
-
size === "40" && "h-10 py-2.5",
|
|
79
|
+
// Size (32px uses 12px text, 40px uses 14px text per the design spec)
|
|
80
|
+
size === "32" && "typography-description-12px-semibold h-8 py-1",
|
|
81
|
+
size === "40" && "typography-body-small-14px-semibold h-10 py-2.5",
|
|
57
82
|
// Variant colors
|
|
58
83
|
isDark && "bg-neutral-alphas-600 text-content-always-white",
|
|
59
|
-
!isDark && selected && "bg-
|
|
60
|
-
|
|
61
|
-
!isDark &&
|
|
84
|
+
!isDark && selected && !dotted && "bg-buttons-chip-active text-content-primary-inverted",
|
|
85
|
+
// Active + dotted is a subtle filled state with a solid (non-dashed) border.
|
|
86
|
+
!isDark && selected && dotted && "border border-border-primary border-solid bg-inputs-inputs-primary text-content-primary",
|
|
87
|
+
!isDark && !selected && !dotted && "bg-buttons-chip-default text-content-primary",
|
|
88
|
+
// Dotted (non-selected): the dashed border is drawn via SVG (`dottedBorder`).
|
|
89
|
+
// `group` lets that SVG react to hover/active. `asChild` keeps a CSS dashed
|
|
90
|
+
// border fallback since the SVG is only rendered in the default path.
|
|
91
|
+
!isDark && !selected && dotted && "bg-transparent text-content-primary",
|
|
92
|
+
!isDark && !selected && dotted && !asChild && "group",
|
|
93
|
+
asChild && !isDark && !selected && dotted && (disabled ? "border border-buttons-chip-disabled border-dashed" : "border border-buttons-chip-dotted-default border-dashed"),
|
|
62
94
|
// Interactive
|
|
63
95
|
isInteractive && !disabled && "cursor-pointer",
|
|
64
|
-
isInteractive && !disabled && !isDark && !selected && !dotted && "hover:bg-
|
|
65
|
-
isInteractive && !disabled && !isDark && !selected && dotted && "hover:
|
|
96
|
+
isInteractive && !disabled && !isDark && !selected && !dotted && "hover:bg-buttons-chip-hover",
|
|
97
|
+
isInteractive && !disabled && !isDark && !selected && dotted && "hover:bg-neutral-alphas-50 active:bg-neutral-alphas-50",
|
|
66
98
|
// Focus
|
|
67
99
|
"focus-visible:shadow-focus-ring focus-visible:outline-none",
|
|
68
100
|
// Disabled
|
|
69
101
|
disabled && isDark && "pointer-events-none opacity-50",
|
|
70
|
-
disabled && !isDark && "pointer-events-none text-
|
|
102
|
+
disabled && !isDark && "pointer-events-none text-content-disabled",
|
|
103
|
+
// Solid (non-dotted) disabled chips get a muted fill; dotted ones stay
|
|
104
|
+
// transparent with their dashed border (drawn via SVG).
|
|
105
|
+
disabled && !isDark && !dotted && "bg-buttons-chip-disabled",
|
|
71
106
|
className
|
|
72
107
|
),
|
|
73
108
|
...isInteractive && {
|
|
@@ -80,6 +115,7 @@ const Chip = React__namespace.forwardRef(
|
|
|
80
115
|
...selected && { "data-selected": "" },
|
|
81
116
|
...props,
|
|
82
117
|
children: asChild ? /* @__PURE__ */ jsxRuntime.jsx(reactSlot.Slottable, { children }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
118
|
+
dottedBorder,
|
|
83
119
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-0.5 overflow-hidden px-3", children: [
|
|
84
120
|
leftDot && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 shrink-0 rounded-full bg-current", "aria-hidden": "true" }),
|
|
85
121
|
leftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center justify-center", "aria-hidden": "true", children: leftIcon }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chip.cjs","sources":["../../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\n leftDot?: boolean;\n /**\n * Whether the chip uses a dashed border for add/create affordances.\n * Has no effect when `variant=\"dark\"` or `selected` is `true`.\n * @default false\n */\n dotted?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n dotted = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-description-12px-semibold relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-xs\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-alphas-600 text-content-always-white\",\n !isDark && selected && \"bg-brand-primary-muted text-content-primary\",\n !isDark && !selected && !dotted && \"bg-neutral-alphas-50 text-content-primary\",\n !isDark &&\n !selected &&\n dotted &&\n \"border border-buttons-chip-dotted-default border-dashed bg-transparent text-content-primary\",\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n !dotted &&\n \"hover:bg-brand-primary-muted active:bg-brand-primary-muted\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n dotted &&\n \"hover:border-buttons-chip-dotted-hover-stroke hover:bg-neutral-alphas-50 active:border-buttons-chip-dotted-hover-stroke active:bg-neutral-alphas-50\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-alphas-400\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <>\n <span className=\"flex min-w-0 items-center gap-0.5 overflow-hidden px-3\">\n {leftDot && (\n <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />\n )}\n {leftIcon && (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{children}</span>\n {rightIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </span>\n {notificationLabel && (\n <span className=\"typography-description-12px-semibold absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-content-primary px-1 text-content-primary-inverted\">\n {notificationLabel}\n </span>\n )}\n </>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":["React","Slot","jsx","cn","Slottable","jsxs","Fragment"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgDO,MAAM,OAAOA,iBAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAUC,UAAAA,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAWC,GAAAA;AAAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU;AAAA,UACnC,CAAC,UACC,CAAC,YACD,UACA;AAAA;AAAA,UAEF,iBAAiB,CAAC,YAAY;AAAA,UAC9B,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,CAAC,UACD;AAAA,UACF,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,UACA;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA,UACCD,2BAAAA,IAACE,qBAAA,EAAW,SAAA,CAAS,IAErBC,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,UAAAD,2BAAAA,KAAC,QAAA,EAAK,WAAU,0DACb,UAAA;AAAA,YAAA,WACCH,2BAAAA,IAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,YAE9E,YACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA4C,eAAY,QACrE,UAAA,UACH;AAAA,YAEFA,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,YAC5C,aACCA,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEX,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,GAEJ;AAAA,UACC,qBACCA,2BAAAA,IAAC,QAAA,EAAK,WAAU,iLACb,UAAA,kBAAA,CACH;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;;"}
|
|
1
|
+
{"version":3,"file":"Chip.cjs","sources":["../../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\n leftDot?: boolean;\n /**\n * Whether the chip uses a dashed border for add/create affordances.\n * When `selected`, it becomes a subtle filled state with a solid border.\n * Has no effect when `variant=\"dark\"`.\n * @default false\n */\n dotted?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n dotted = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n // The dashed border is drawn as an SVG so the dash length matches the design\n // spec (8/8 for the 40px square, 6/6 otherwise). CSS `border-dashed` only\n // renders browser-default dash lengths, which are too short. The stroke colour\n // is driven by `currentColor`, and `group-hover`/`group-active` react to the\n // chip's interactive states. Rendered only in the default (non-`asChild`) path.\n const showDottedBorder = !isDark && dotted && !selected && !asChild;\n const dottedBorder = showDottedBorder ? (\n <svg\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 size-full overflow-visible\",\n disabled ? \"text-buttons-chip-disabled\" : \"text-buttons-chip-dotted-default\",\n isInteractive &&\n !disabled &&\n \"group-hover:text-buttons-chip-dotted-hover-stroke group-active:text-buttons-chip-dotted-hover-stroke\",\n )}\n >\n <rect\n x=\"0.5\"\n y=\"0.5\"\n rx={variant === \"square\" ? 7.5 : size === \"40\" ? 19.5 : 15.5}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeDasharray={variant === \"square\" && size === \"40\" ? \"8 8\" : \"6 6\"}\n style={{ width: \"calc(100% - 1px)\", height: \"calc(100% - 1px)\" }}\n />\n </svg>\n ) : null;\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-xs\" : \"rounded-full\",\n // Size (32px uses 12px text, 40px uses 14px text per the design spec)\n size === \"32\" && \"typography-description-12px-semibold h-8 py-1\",\n size === \"40\" && \"typography-body-small-14px-semibold h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-alphas-600 text-content-always-white\",\n !isDark && selected && !dotted && \"bg-buttons-chip-active text-content-primary-inverted\",\n // Active + dotted is a subtle filled state with a solid (non-dashed) border.\n !isDark &&\n selected &&\n dotted &&\n \"border border-border-primary border-solid bg-inputs-inputs-primary text-content-primary\",\n !isDark && !selected && !dotted && \"bg-buttons-chip-default text-content-primary\",\n // Dotted (non-selected): the dashed border is drawn via SVG (`dottedBorder`).\n // `group` lets that SVG react to hover/active. `asChild` keeps a CSS dashed\n // border fallback since the SVG is only rendered in the default path.\n !isDark && !selected && dotted && \"bg-transparent text-content-primary\",\n !isDark && !selected && dotted && !asChild && \"group\",\n asChild &&\n !isDark &&\n !selected &&\n dotted &&\n (disabled\n ? \"border border-buttons-chip-disabled border-dashed\"\n : \"border border-buttons-chip-dotted-default border-dashed\"),\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n !dotted &&\n \"hover:bg-buttons-chip-hover\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n dotted &&\n \"hover:bg-neutral-alphas-50 active:bg-neutral-alphas-50\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-content-disabled\",\n // Solid (non-dotted) disabled chips get a muted fill; dotted ones stay\n // transparent with their dashed border (drawn via SVG).\n disabled && !isDark && !dotted && \"bg-buttons-chip-disabled\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <>\n {dottedBorder}\n <span className=\"flex min-w-0 items-center gap-0.5 overflow-hidden px-3\">\n {leftDot && (\n <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />\n )}\n {leftIcon && (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{children}</span>\n {rightIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </span>\n {notificationLabel && (\n <span className=\"typography-description-12px-semibold absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-content-primary px-1 text-content-primary-inverted\">\n {notificationLabel}\n </span>\n )}\n </>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":["React","Slot","jsx","cn","Slottable","jsxs","Fragment"],"mappings":"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"}
|
|
@@ -30,14 +30,16 @@ const alignLeftClasses = {
|
|
|
30
30
|
lg: "[&>[role=tab]]:lg:flex-initial",
|
|
31
31
|
xl: "[&>[role=tab]]:xl:flex-initial"
|
|
32
32
|
};
|
|
33
|
-
function getLayoutClass(fullWidth, alignLeft) {
|
|
33
|
+
function getLayoutClass(variant, fullWidth, alignLeft) {
|
|
34
|
+
if (variant === "hug") return "inline-flex";
|
|
35
|
+
if (variant === "fill") return "flex w-full [&>[role=tab]]:flex-1";
|
|
34
36
|
if (!fullWidth) return "inline-flex";
|
|
35
37
|
const base = "flex w-full [&>[role=tab]]:flex-1";
|
|
36
38
|
if (alignLeft === true) return `${base} ${alignLeftClasses.always}`;
|
|
37
39
|
if (typeof alignLeft === "string") return `${base} ${alignLeftClasses[alignLeft]}`;
|
|
38
40
|
return base;
|
|
39
41
|
}
|
|
40
|
-
const TabsList = React__namespace.forwardRef(({ className, children, fullWidth = true, alignLeft, ...props }, ref) => {
|
|
42
|
+
const TabsList = React__namespace.forwardRef(({ className, children, fullWidth = true, alignLeft, variant, ...props }, ref) => {
|
|
41
43
|
const innerRef = React__namespace.useRef(null);
|
|
42
44
|
const indicatorRef = React__namespace.useRef(null);
|
|
43
45
|
React__namespace.useImperativeHandle(ref, () => innerRef.current);
|
|
@@ -53,15 +55,21 @@ const TabsList = React__namespace.forwardRef(({ className, children, fullWidth =
|
|
|
53
55
|
const isVertical = list.dataset.orientation === "vertical";
|
|
54
56
|
indicator.style.opacity = "1";
|
|
55
57
|
if (isVertical) {
|
|
56
|
-
indicator.style.
|
|
57
|
-
indicator.style.
|
|
58
|
+
indicator.style.background = "linear-gradient(180deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)";
|
|
59
|
+
indicator.style.inset = `0 -1px auto auto`;
|
|
60
|
+
indicator.style.width = "1px";
|
|
58
61
|
indicator.style.height = `${activeTab.offsetHeight}px`;
|
|
59
62
|
indicator.style.transform = `translateY(${activeTab.offsetTop}px)`;
|
|
60
63
|
} else {
|
|
61
|
-
indicator.style.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
indicator.style.background = "linear-gradient(90deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)";
|
|
65
|
+
const textSpan = activeTab.querySelector("span");
|
|
66
|
+
const textWidth = (textSpan ?? activeTab).getBoundingClientRect().width;
|
|
67
|
+
const tabCenter = activeTab.offsetLeft + activeTab.offsetWidth / 2;
|
|
68
|
+
const indicatorLeft = tabCenter - textWidth / 2;
|
|
69
|
+
indicator.style.inset = "auto auto 0 0";
|
|
70
|
+
indicator.style.height = "1px";
|
|
71
|
+
indicator.style.width = `${textWidth}px`;
|
|
72
|
+
indicator.style.transform = `translateX(${indicatorLeft}px)`;
|
|
65
73
|
}
|
|
66
74
|
}, []);
|
|
67
75
|
React__namespace.useLayoutEffect(() => {
|
|
@@ -99,9 +107,9 @@ const TabsList = React__namespace.forwardRef(({ className, children, fullWidth =
|
|
|
99
107
|
ref: innerRef,
|
|
100
108
|
className: cn.cn(
|
|
101
109
|
"relative",
|
|
102
|
-
getLayoutClass(fullWidth, alignLeft),
|
|
103
|
-
"data-[orientation=horizontal]:items-center data-[orientation=horizontal]:shadow-[inset_0_-1px_0_0_var(--color-
|
|
104
|
-
"data-[orientation=vertical]:flex-col data-[orientation=vertical]:shadow-[inset_-1px_0_0_0_var(--color-
|
|
110
|
+
getLayoutClass(variant, fullWidth, alignLeft),
|
|
111
|
+
"data-[orientation=horizontal]:items-center data-[orientation=horizontal]:shadow-[inset_0_-1px_0_0_var(--color-border-primary)]",
|
|
112
|
+
"data-[orientation=vertical]:flex-col data-[orientation=vertical]:shadow-[inset_-1px_0_0_0_var(--color-border-primary)]",
|
|
105
113
|
className
|
|
106
114
|
),
|
|
107
115
|
...props,
|
|
@@ -112,7 +120,7 @@ const TabsList = React__namespace.forwardRef(({ className, children, fullWidth =
|
|
|
112
120
|
{
|
|
113
121
|
ref: indicatorRef,
|
|
114
122
|
"aria-hidden": true,
|
|
115
|
-
className: "pointer-events-none absolute rounded-full
|
|
123
|
+
className: "pointer-events-none absolute z-10 rounded-full motion-safe:transition-[transform,width,height] motion-safe:duration-200 motion-safe:ease-in-out",
|
|
116
124
|
style: { opacity: 0 }
|
|
117
125
|
}
|
|
118
126
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabsList.cjs","sources":["../../../../src/components/Tabs/TabsList.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Breakpoint values for responsive props. */\ntype Breakpoint = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nconst alignLeftClasses: Record<Breakpoint | \"always\", string> = {\n always: \"[&>[role=tab]]:flex-initial\",\n sm: \"[&>[role=tab]]:sm:flex-initial\",\n md: \"[&>[role=tab]]:md:flex-initial\",\n lg: \"[&>[role=tab]]:lg:flex-initial\",\n xl: \"[&>[role=tab]]:xl:flex-initial\",\n};\n\nfunction getLayoutClass(fullWidth: boolean
|
|
1
|
+
{"version":3,"file":"TabsList.cjs","sources":["../../../../src/components/Tabs/TabsList.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Breakpoint values for responsive props. */\ntype Breakpoint = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nconst alignLeftClasses: Record<Breakpoint | \"always\", string> = {\n always: \"[&>[role=tab]]:flex-initial\",\n sm: \"[&>[role=tab]]:sm:flex-initial\",\n md: \"[&>[role=tab]]:md:flex-initial\",\n lg: \"[&>[role=tab]]:lg:flex-initial\",\n xl: \"[&>[role=tab]]:xl:flex-initial\",\n};\n\nfunction getLayoutClass(\n variant: \"fill\" | \"hug\" | undefined,\n fullWidth: boolean,\n alignLeft?: boolean | Breakpoint,\n): string {\n if (variant === \"hug\") return \"inline-flex\";\n if (variant === \"fill\") return \"flex w-full [&>[role=tab]]:flex-1\";\n\n if (!fullWidth) return \"inline-flex\";\n\n const base = \"flex w-full [&>[role=tab]]:flex-1\";\n if (alignLeft === true) return `${base} ${alignLeftClasses.always}`;\n if (typeof alignLeft === \"string\") return `${base} ${alignLeftClasses[alignLeft]}`;\n return base;\n}\n\n/** Props for the {@link TabsList} component. */\nexport type TabsListProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {\n /** When `true` (the default), the tab list spans the full width of its container and each tab grows equally. Set to `false` for inline sizing. */\n fullWidth?: boolean;\n /**\n * Controls tab alignment within a full-width container.\n * - `false` (default): tabs spread evenly\n * - `true`: tabs left-aligned, sized to content\n * - `\"md\"` (breakpoint): spread on mobile, left-aligned at breakpoint and up\n */\n alignLeft?: boolean | Breakpoint;\n /** Explicit layout variant. `\"hug\"` sizes the list to its content; `\"fill\"` stretches it full-width with equal-width tabs. Takes precedence over `fullWidth`/`alignLeft`. */\n variant?: \"fill\" | \"hug\";\n};\n\n/** Container for {@link TabsTrigger} elements. Renders a sliding active-tab indicator that animates between tabs. */\nexport const TabsList = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.List>,\n TabsListProps\n>(({ className, children, fullWidth = true, alignLeft, variant, ...props }, ref) => {\n const innerRef = React.useRef<HTMLDivElement>(null);\n const indicatorRef = React.useRef<HTMLSpanElement>(null);\n\n React.useImperativeHandle(ref, () => innerRef.current as HTMLDivElement);\n\n const updateIndicator = React.useCallback(() => {\n const list = innerRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector<HTMLElement>('[data-state=\"active\"]');\n if (!activeTab) {\n indicator.style.opacity = \"0\";\n return;\n }\n\n const isVertical = list.dataset.orientation === \"vertical\";\n\n indicator.style.opacity = \"1\";\n\n if (isVertical) {\n indicator.style.background =\n \"linear-gradient(180deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)\";\n indicator.style.inset = `0 -1px auto auto`;\n indicator.style.width = \"1px\";\n indicator.style.height = `${activeTab.offsetHeight}px`;\n indicator.style.transform = `translateY(${activeTab.offsetTop}px)`;\n } else {\n indicator.style.background =\n \"linear-gradient(90deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)\";\n const textSpan = activeTab.querySelector(\"span\");\n const textWidth = (textSpan ?? activeTab).getBoundingClientRect().width;\n const tabCenter = activeTab.offsetLeft + activeTab.offsetWidth / 2;\n const indicatorLeft = tabCenter - textWidth / 2;\n indicator.style.inset = \"auto auto 0 0\";\n indicator.style.height = \"1px\";\n indicator.style.width = `${textWidth}px`;\n indicator.style.transform = `translateX(${indicatorLeft}px)`;\n }\n }, []);\n\n React.useLayoutEffect(() => {\n const list = innerRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n indicator.style.transitionDuration = \"0s\";\n updateIndicator();\n indicator.getBoundingClientRect();\n indicator.style.transitionDuration = \"\";\n\n const mutationObserver = new MutationObserver(updateIndicator);\n mutationObserver.observe(list, {\n attributes: true,\n attributeFilter: [\"data-state\"],\n childList: true,\n subtree: true,\n });\n\n const resizeObserver = new ResizeObserver(updateIndicator);\n resizeObserver.observe(list);\n\n let cancelled = false;\n if (document.fonts?.status !== \"loaded\") {\n document.fonts?.ready.then(() => {\n if (!cancelled) updateIndicator();\n });\n }\n\n return () => {\n cancelled = true;\n mutationObserver.disconnect();\n resizeObserver.disconnect();\n };\n }, [updateIndicator]);\n\n return (\n <TabsPrimitive.List\n ref={innerRef}\n className={cn(\n \"relative\",\n getLayoutClass(variant, fullWidth, alignLeft),\n \"data-[orientation=horizontal]:items-center data-[orientation=horizontal]:shadow-[inset_0_-1px_0_0_var(--color-border-primary)]\",\n \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:shadow-[inset_-1px_0_0_0_var(--color-border-primary)]\",\n className,\n )}\n {...props}\n >\n {children}\n <span\n ref={indicatorRef}\n aria-hidden\n className=\"pointer-events-none absolute z-10 rounded-full motion-safe:transition-[transform,width,height] motion-safe:duration-200 motion-safe:ease-in-out\"\n style={{ opacity: 0 }}\n />\n </TabsPrimitive.List>\n );\n});\n\nTabsList.displayName = \"TabsList\";\n"],"names":["React","jsxs","TabsPrimitive","cn","jsx"],"mappings":"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"}
|
|
@@ -29,18 +29,19 @@ const TabsTrigger = React__namespace.forwardRef(({ className, children, ...props
|
|
|
29
29
|
ref,
|
|
30
30
|
className: cn.cn(
|
|
31
31
|
"inline-flex min-w-0 items-center justify-center",
|
|
32
|
-
"
|
|
33
|
-
"typography-body-default-16px-semibold cursor-pointer text-content-primary",
|
|
32
|
+
"typography-body-default-16px-semibold cursor-pointer",
|
|
34
33
|
"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
|
|
35
34
|
"data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3",
|
|
36
35
|
"data-[orientation=vertical]:justify-start data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3",
|
|
37
|
-
"data-[state=active]:
|
|
38
|
-
"data-[state=inactive]:
|
|
39
|
-
"data-[state=active]:
|
|
40
|
-
"data-[state=inactive]:
|
|
36
|
+
"data-[state=active]:text-content-primary",
|
|
37
|
+
"data-[state=inactive]:text-content-tertiary",
|
|
38
|
+
"data-[state=active]:hover:text-content-primary/70",
|
|
39
|
+
"data-[state=inactive]:hover:text-content-primary",
|
|
40
|
+
"data-[state=active]:active:text-content-primary/70",
|
|
41
|
+
"data-[state=inactive]:active:text-content-primary",
|
|
41
42
|
"data-disabled:pointer-events-none",
|
|
42
43
|
"data-disabled:data-[state=active]:text-content-tertiary",
|
|
43
|
-
"data-disabled:data-[state=inactive]:text-neutral-alphas-
|
|
44
|
+
"data-disabled:data-[state=inactive]:text-neutral-alphas-200",
|
|
44
45
|
"focus-visible:shadow-focus-ring focus-visible:outline-none",
|
|
45
46
|
className
|
|
46
47
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabsTrigger.cjs","sources":["../../../../src/components/Tabs/TabsTrigger.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 TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, children, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex min-w-0 items-center justify-center\",\n \"
|
|
1
|
+
{"version":3,"file":"TabsTrigger.cjs","sources":["../../../../src/components/Tabs/TabsTrigger.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 TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, children, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex min-w-0 items-center justify-center\",\n \"typography-body-default-16px-semibold cursor-pointer\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3\",\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3\",\n \"data-[state=active]:text-content-primary\",\n \"data-[state=inactive]:text-content-tertiary\",\n \"data-[state=active]:hover:text-content-primary/70\",\n \"data-[state=inactive]:hover:text-content-primary\",\n \"data-[state=active]:active:text-content-primary/70\",\n \"data-[state=inactive]:active:text-content-primary\",\n \"data-disabled:pointer-events-none\",\n \"data-disabled:data-[state=active]:text-content-tertiary\",\n \"data-disabled:data-[state=inactive]:text-neutral-alphas-200\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n className,\n )}\n {...props}\n >\n <span className=\"min-w-0 truncate\">{children}</span>\n </TabsPrimitive.Trigger>\n));\n\nTabsTrigger.displayName = \"TabsTrigger\";\n"],"names":["React","jsx","TabsPrimitive","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAQO,MAAM,cAAcA,iBAAM,WAG/B,CAAC,EAAE,WAAW,UAAU,GAAG,SAAS,QACpCC,2BAAAA;AAAAA,EAACC,yBAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAWC,GAAAA;AAAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,IAEJ,UAAAF,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,EAAA;AAC/C,CACD;AAED,YAAY,cAAc;;"}
|
|
@@ -45,13 +45,13 @@ const ChartCard = React.forwardRef(
|
|
|
45
45
|
/* @__PURE__ */ jsx(TooltipContent, { children: tooltip })
|
|
46
46
|
] }) })
|
|
47
47
|
] }),
|
|
48
|
-
subtitle && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
48
|
+
subtitle && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
49
49
|
/* @__PURE__ */ jsx("span", { className: "typography-header-heading-sm text-content-primary", children: subtitle }),
|
|
50
50
|
trendChip && /* @__PURE__ */ jsx(
|
|
51
51
|
"span",
|
|
52
52
|
{
|
|
53
53
|
className: cn(
|
|
54
|
-
"typography-description-12px-semibold rounded-full px-2 py-0.5",
|
|
54
|
+
"typography-description-12px-semibold whitespace-nowrap rounded-full px-2 py-0.5",
|
|
55
55
|
TREND_CLASSES[trendChip.trend]
|
|
56
56
|
),
|
|
57
57
|
children: trendChip.label
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartCard.mjs","sources":["../../../src/components/Chart/ChartCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card } from \"../Card/Card\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Skeleton } from \"../Skeleton/Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Props for {@link ChartCard}. */\nexport interface ChartCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Card title text. Pass translated string for i18n. */\n title: React.ReactNode;\n /** Large subtitle value (e.g. formatted price or count). */\n subtitle?: React.ReactNode;\n /** Tooltip text shown next to the title. Pass translated string for i18n. */\n tooltip?: React.ReactNode;\n /** Accessible label for the info tooltip trigger. Override for i18n. @default \"More info\" */\n tooltipAriaLabel?: string;\n /** Date range or period label shown below the subtitle. */\n dateInfo?: React.ReactNode;\n /** Trend indicator chip config. */\n trendChip?: {\n /** Display label (e.g. \"12.5%\"). */\n label: React.ReactNode;\n /** Whether the trend is positive (green) or negative (red). */\n trend: \"positive\" | \"negative\";\n };\n /** Show loading skeleton instead of content. @default false */\n loading?: boolean;\n /** Chart content rendered below the header. */\n children?: React.ReactNode;\n}\n\nconst TREND_CLASSES: Record<\"positive\" | \"negative\", string> = {\n positive: \"bg-success-surface text-success-content\",\n negative: \"bg-error-surface text-error-content\",\n};\n\n/**\n * Wraps any chart with a structured header containing title, subtitle,\n * optional trend chip, date range label, info tooltip, and a loading\n * skeleton state.\n *\n * @example\n * ```tsx\n * <ChartCard\n * title=\"Revenue\"\n * subtitle=\"$4,523\"\n * trendChip={{ label: \"+12.5%\", trend: \"positive\" }}\n * dateInfo=\"Jan 1 – Mar 17\"\n * tooltip=\"Total revenue for the selected period.\"\n * >\n * <MyLineChart />\n * </ChartCard>\n * ```\n */\nexport const ChartCard = React.forwardRef<HTMLDivElement, ChartCardProps>(\n (\n {\n className,\n title,\n subtitle,\n tooltip,\n tooltipAriaLabel = \"More info\",\n dateInfo,\n trendChip,\n loading = false,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <Card ref={ref} variant=\"outlined\" noPadding className={className} {...props}>\n <div className=\"flex flex-col gap-2 p-4\">\n {loading ? (\n <>\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-4 w-32\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-7 w-44\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-3 w-24\" />\n </>\n ) : (\n <>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {title}\n </span>\n {tooltip && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={tooltipAriaLabel}\n icon={<InfoCircleIcon className=\"size-4 text-content-tertiary\" />}\n />\n </TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n {subtitle && (\n <div className=\"flex items-center gap-2\">\n <span className=\"typography-header-heading-sm text-content-primary\">\n {subtitle}\n </span>\n {trendChip && (\n <span\n className={cn(\n \"typography-description-12px-semibold rounded-full px-2 py-0.5\",\n TREND_CLASSES[trendChip.trend],\n )}\n >\n {trendChip.label}\n </span>\n )}\n </div>\n )}\n {dateInfo && (\n <span className=\"typography-description-12px-regular text-content-tertiary\">\n {dateInfo}\n </span>\n )}\n </>\n )}\n <div className=\"mt-auto\">{children}</div>\n </div>\n </Card>\n );\n },\n);\nChartCard.displayName = \"ChartCard\";\n"],"names":[],"mappings":";;;;;;;;;AAiCA,MAAM,gBAAyD;AAAA,EAC7D,UAAU;AAAA,EACV,UAAU;AACZ;AAoBO,MAAM,YAAY,MAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACE,oBAAC,MAAA,EAAK,KAAU,SAAQ,YAAW,WAAS,MAAC,WAAuB,GAAG,OACrE,UAAA,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,UACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,YAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,4BACjE,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,4BACjE,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,WAAA,CAAW;AAAA,MAAA,EAAA,CACpE,IAEA,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,4DACb,UAAA,OACH;AAAA,UACC,WACC,oBAAC,iBAAA,EACC,UAAA,qBAAC,SAAA,EACC,UAAA;AAAA,YAAA,oBAAC,gBAAA,EAAe,SAAO,MACrB,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,MAAM,oBAAC,gBAAA,EAAe,WAAU,+BAAA,CAA+B;AAAA,cAAA;AAAA,YAAA,GAEnE;AAAA,YACA,oBAAC,kBAAgB,UAAA,QAAA,CAAQ;AAAA,UAAA,EAAA,CAC3B,EAAA,CACF;AAAA,QAAA,GAEJ;AAAA,QACC,YACC,qBAAC,OAAA,EAAI,WAAU,
|
|
1
|
+
{"version":3,"file":"ChartCard.mjs","sources":["../../../src/components/Chart/ChartCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card } from \"../Card/Card\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { InfoCircleIcon } from \"../Icons/InfoCircleIcon\";\nimport { Skeleton } from \"../Skeleton/Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../Tooltip/Tooltip\";\n\n/** Props for {@link ChartCard}. */\nexport interface ChartCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Card title text. Pass translated string for i18n. */\n title: React.ReactNode;\n /** Large subtitle value (e.g. formatted price or count). */\n subtitle?: React.ReactNode;\n /** Tooltip text shown next to the title. Pass translated string for i18n. */\n tooltip?: React.ReactNode;\n /** Accessible label for the info tooltip trigger. Override for i18n. @default \"More info\" */\n tooltipAriaLabel?: string;\n /** Date range or period label shown below the subtitle. */\n dateInfo?: React.ReactNode;\n /** Trend indicator chip config. Only rendered when {@link subtitle} is also provided. */\n trendChip?: {\n /** Display label (e.g. \"12.5%\"). */\n label: React.ReactNode;\n /** Whether the trend is positive (green) or negative (red). */\n trend: \"positive\" | \"negative\";\n };\n /** Show loading skeleton instead of content. @default false */\n loading?: boolean;\n /** Chart content rendered below the header. */\n children?: React.ReactNode;\n}\n\nconst TREND_CLASSES: Record<\"positive\" | \"negative\", string> = {\n positive: \"bg-success-surface text-success-content\",\n negative: \"bg-error-surface text-error-content\",\n};\n\n/**\n * Wraps any chart with a structured header containing title, subtitle,\n * optional trend chip, date range label, info tooltip, and a loading\n * skeleton state.\n *\n * @example\n * ```tsx\n * <ChartCard\n * title=\"Revenue\"\n * subtitle=\"$4,523\"\n * trendChip={{ label: \"+12.5%\", trend: \"positive\" }}\n * dateInfo=\"Jan 1 – Mar 17\"\n * tooltip=\"Total revenue for the selected period.\"\n * >\n * <MyLineChart />\n * </ChartCard>\n * ```\n */\nexport const ChartCard = React.forwardRef<HTMLDivElement, ChartCardProps>(\n (\n {\n className,\n title,\n subtitle,\n tooltip,\n tooltipAriaLabel = \"More info\",\n dateInfo,\n trendChip,\n loading = false,\n children,\n ...props\n },\n ref,\n ) => {\n return (\n <Card ref={ref} variant=\"outlined\" noPadding className={className} {...props}>\n <div className=\"flex flex-col gap-2 p-4\">\n {loading ? (\n <>\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-4 w-32\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-7 w-44\" />\n <Skeleton animation=\"wave\" variant=\"rounded\" className=\"h-3 w-24\" />\n </>\n ) : (\n <>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {title}\n </span>\n {tooltip && (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={tooltipAriaLabel}\n icon={<InfoCircleIcon className=\"size-4 text-content-tertiary\" />}\n />\n </TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )}\n </div>\n {subtitle && (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"typography-header-heading-sm text-content-primary\">\n {subtitle}\n </span>\n {trendChip && (\n <span\n className={cn(\n \"typography-description-12px-semibold whitespace-nowrap rounded-full px-2 py-0.5\",\n TREND_CLASSES[trendChip.trend],\n )}\n >\n {trendChip.label}\n </span>\n )}\n </div>\n )}\n {dateInfo && (\n <span className=\"typography-description-12px-regular text-content-tertiary\">\n {dateInfo}\n </span>\n )}\n </>\n )}\n <div className=\"mt-auto\">{children}</div>\n </div>\n </Card>\n );\n },\n);\nChartCard.displayName = \"ChartCard\";\n"],"names":[],"mappings":";;;;;;;;;AAiCA,MAAM,gBAAyD;AAAA,EAC7D,UAAU;AAAA,EACV,UAAU;AACZ;AAoBO,MAAM,YAAY,MAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,WACE,oBAAC,MAAA,EAAK,KAAU,SAAQ,YAAW,WAAS,MAAC,WAAuB,GAAG,OACrE,UAAA,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,UACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,YAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,4BACjE,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,YAAW;AAAA,4BACjE,UAAA,EAAS,WAAU,QAAO,SAAQ,WAAU,WAAU,WAAA,CAAW;AAAA,MAAA,EAAA,CACpE,IAEA,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,4DACb,UAAA,OACH;AAAA,UACC,WACC,oBAAC,iBAAA,EACC,UAAA,qBAAC,SAAA,EACC,UAAA;AAAA,YAAA,oBAAC,gBAAA,EAAe,SAAO,MACrB,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,MAAM,oBAAC,gBAAA,EAAe,WAAU,+BAAA,CAA+B;AAAA,cAAA;AAAA,YAAA,GAEnE;AAAA,YACA,oBAAC,kBAAgB,UAAA,QAAA,CAAQ;AAAA,UAAA,EAAA,CAC3B,EAAA,CACF;AAAA,QAAA,GAEJ;AAAA,QACC,YACC,qBAAC,OAAA,EAAI,WAAU,qCACb,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,qDACb,UAAA,UACH;AAAA,UACC,aACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,cAAc,UAAU,KAAK;AAAA,cAAA;AAAA,cAG9B,UAAA,UAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACb,GAEJ;AAAA,QAED,YACC,oBAAC,QAAA,EAAK,WAAU,6DACb,UAAA,SAAA,CACH;AAAA,MAAA,GAEJ;AAAA,MAEF,oBAAC,OAAA,EAAI,WAAU,WAAW,SAAA,CAAS;AAAA,IAAA,EAAA,CACrC,EAAA,CACF;AAAA,EAEJ;AACF;AACA,UAAU,cAAc;"}
|
|
@@ -23,32 +23,67 @@ const Chip = React.forwardRef(
|
|
|
23
23
|
const isInteractive = !!onClick && !asChild;
|
|
24
24
|
const Comp = asChild ? Slot : isInteractive ? "button" : "span";
|
|
25
25
|
const isDark = variant === "dark";
|
|
26
|
+
const showDottedBorder = !isDark && dotted && !selected && !asChild;
|
|
27
|
+
const dottedBorder = showDottedBorder ? /* @__PURE__ */ jsx(
|
|
28
|
+
"svg",
|
|
29
|
+
{
|
|
30
|
+
"aria-hidden": "true",
|
|
31
|
+
className: cn(
|
|
32
|
+
"pointer-events-none absolute inset-0 size-full overflow-visible",
|
|
33
|
+
disabled ? "text-buttons-chip-disabled" : "text-buttons-chip-dotted-default",
|
|
34
|
+
isInteractive && !disabled && "group-hover:text-buttons-chip-dotted-hover-stroke group-active:text-buttons-chip-dotted-hover-stroke"
|
|
35
|
+
),
|
|
36
|
+
children: /* @__PURE__ */ jsx(
|
|
37
|
+
"rect",
|
|
38
|
+
{
|
|
39
|
+
x: "0.5",
|
|
40
|
+
y: "0.5",
|
|
41
|
+
rx: variant === "square" ? 7.5 : size === "40" ? 19.5 : 15.5,
|
|
42
|
+
fill: "none",
|
|
43
|
+
stroke: "currentColor",
|
|
44
|
+
strokeWidth: "1",
|
|
45
|
+
strokeDasharray: variant === "square" && size === "40" ? "8 8" : "6 6",
|
|
46
|
+
style: { width: "calc(100% - 1px)", height: "calc(100% - 1px)" }
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
) : null;
|
|
26
51
|
return /* @__PURE__ */ jsx(
|
|
27
52
|
Comp,
|
|
28
53
|
{
|
|
29
54
|
ref,
|
|
30
55
|
"data-testid": "chip",
|
|
31
56
|
className: cn(
|
|
32
|
-
"
|
|
57
|
+
"relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150",
|
|
33
58
|
// Shape
|
|
34
59
|
variant === "square" ? "rounded-xs" : "rounded-full",
|
|
35
|
-
// Size
|
|
36
|
-
size === "32" && "h-8 py-1",
|
|
37
|
-
size === "40" && "h-10 py-2.5",
|
|
60
|
+
// Size (32px uses 12px text, 40px uses 14px text per the design spec)
|
|
61
|
+
size === "32" && "typography-description-12px-semibold h-8 py-1",
|
|
62
|
+
size === "40" && "typography-body-small-14px-semibold h-10 py-2.5",
|
|
38
63
|
// Variant colors
|
|
39
64
|
isDark && "bg-neutral-alphas-600 text-content-always-white",
|
|
40
|
-
!isDark && selected && "bg-
|
|
41
|
-
|
|
42
|
-
!isDark &&
|
|
65
|
+
!isDark && selected && !dotted && "bg-buttons-chip-active text-content-primary-inverted",
|
|
66
|
+
// Active + dotted is a subtle filled state with a solid (non-dashed) border.
|
|
67
|
+
!isDark && selected && dotted && "border border-border-primary border-solid bg-inputs-inputs-primary text-content-primary",
|
|
68
|
+
!isDark && !selected && !dotted && "bg-buttons-chip-default text-content-primary",
|
|
69
|
+
// Dotted (non-selected): the dashed border is drawn via SVG (`dottedBorder`).
|
|
70
|
+
// `group` lets that SVG react to hover/active. `asChild` keeps a CSS dashed
|
|
71
|
+
// border fallback since the SVG is only rendered in the default path.
|
|
72
|
+
!isDark && !selected && dotted && "bg-transparent text-content-primary",
|
|
73
|
+
!isDark && !selected && dotted && !asChild && "group",
|
|
74
|
+
asChild && !isDark && !selected && dotted && (disabled ? "border border-buttons-chip-disabled border-dashed" : "border border-buttons-chip-dotted-default border-dashed"),
|
|
43
75
|
// Interactive
|
|
44
76
|
isInteractive && !disabled && "cursor-pointer",
|
|
45
|
-
isInteractive && !disabled && !isDark && !selected && !dotted && "hover:bg-
|
|
46
|
-
isInteractive && !disabled && !isDark && !selected && dotted && "hover:
|
|
77
|
+
isInteractive && !disabled && !isDark && !selected && !dotted && "hover:bg-buttons-chip-hover",
|
|
78
|
+
isInteractive && !disabled && !isDark && !selected && dotted && "hover:bg-neutral-alphas-50 active:bg-neutral-alphas-50",
|
|
47
79
|
// Focus
|
|
48
80
|
"focus-visible:shadow-focus-ring focus-visible:outline-none",
|
|
49
81
|
// Disabled
|
|
50
82
|
disabled && isDark && "pointer-events-none opacity-50",
|
|
51
|
-
disabled && !isDark && "pointer-events-none text-
|
|
83
|
+
disabled && !isDark && "pointer-events-none text-content-disabled",
|
|
84
|
+
// Solid (non-dotted) disabled chips get a muted fill; dotted ones stay
|
|
85
|
+
// transparent with their dashed border (drawn via SVG).
|
|
86
|
+
disabled && !isDark && !dotted && "bg-buttons-chip-disabled",
|
|
52
87
|
className
|
|
53
88
|
),
|
|
54
89
|
...isInteractive && {
|
|
@@ -61,6 +96,7 @@ const Chip = React.forwardRef(
|
|
|
61
96
|
...selected && { "data-selected": "" },
|
|
62
97
|
...props,
|
|
63
98
|
children: asChild ? /* @__PURE__ */ jsx(Slottable, { children }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
99
|
+
dottedBorder,
|
|
64
100
|
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-0.5 overflow-hidden px-3", children: [
|
|
65
101
|
leftDot && /* @__PURE__ */ jsx("span", { className: "size-2 shrink-0 rounded-full bg-current", "aria-hidden": "true" }),
|
|
66
102
|
leftIcon && /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center justify-center", "aria-hidden": "true", children: leftIcon }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chip.mjs","sources":["../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\n leftDot?: boolean;\n /**\n * Whether the chip uses a dashed border for add/create affordances.\n * Has no effect when `variant=\"dark\"` or `selected` is `true`.\n * @default false\n */\n dotted?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n dotted = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"typography-description-12px-semibold relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-xs\" : \"rounded-full\",\n // Size\n size === \"32\" && \"h-8 py-1\",\n size === \"40\" && \"h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-alphas-600 text-content-always-white\",\n !isDark && selected && \"bg-brand-primary-muted text-content-primary\",\n !isDark && !selected && !dotted && \"bg-neutral-alphas-50 text-content-primary\",\n !isDark &&\n !selected &&\n dotted &&\n \"border border-buttons-chip-dotted-default border-dashed bg-transparent text-content-primary\",\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n !dotted &&\n \"hover:bg-brand-primary-muted active:bg-brand-primary-muted\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n dotted &&\n \"hover:border-buttons-chip-dotted-hover-stroke hover:bg-neutral-alphas-50 active:border-buttons-chip-dotted-hover-stroke active:bg-neutral-alphas-50\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-neutral-alphas-400\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <>\n <span className=\"flex min-w-0 items-center gap-0.5 overflow-hidden px-3\">\n {leftDot && (\n <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />\n )}\n {leftIcon && (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{children}</span>\n {rightIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </span>\n {notificationLabel && (\n <span className=\"typography-description-12px-semibold absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-content-primary px-1 text-content-primary-inverted\">\n {notificationLabel}\n </span>\n )}\n </>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":[],"mappings":";;;;;AAgDO,MAAM,OAAO,MAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAU,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAE3B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY;AAAA,UACvB,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU;AAAA,UACnC,CAAC,UACC,CAAC,YACD,UACA;AAAA;AAAA,UAEF,iBAAiB,CAAC,YAAY;AAAA,UAC9B,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,CAAC,UACD;AAAA,UACF,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,UACA;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA,UACvB;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA,UACC,oBAAC,WAAA,EAAW,SAAA,CAAS,IAErB,qBAAA,UAAA,EACE,UAAA;AAAA,UAAA,qBAAC,QAAA,EAAK,WAAU,0DACb,UAAA;AAAA,YAAA,WACC,oBAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,YAE9E,YACC,oBAAC,QAAA,EAAK,WAAU,6CAA4C,eAAY,QACrE,UAAA,UACH;AAAA,YAEF,oBAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,YAC5C,aACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEX,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,GAEJ;AAAA,UACC,qBACC,oBAAC,QAAA,EAAK,WAAU,iLACb,UAAA,kBAAA,CACH;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;"}
|
|
1
|
+
{"version":3,"file":"Chip.mjs","sources":["../../../src/components/Chip/Chip.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Visual variant of the chip. */\nexport type ChipVariant = \"rounded\" | \"square\" | \"dark\";\n/** Height of the chip in pixels. */\nexport type ChipSize = \"32\" | \"40\";\n\nexport interface ChipProps extends React.HTMLAttributes<HTMLElement> {\n /** Visual variant of the chip. @default \"rounded\" */\n variant?: ChipVariant;\n /** Height of the chip in pixels. @default \"32\" */\n size?: ChipSize;\n /** Whether the chip is in a selected (pressed) state. @default false */\n selected?: boolean;\n /** Whether the chip is disabled. @default false */\n disabled?: boolean;\n /** Whether to show a coloured status dot at the leading edge. @default false */\n leftDot?: boolean;\n /**\n * Whether the chip uses a dashed border for add/create affordances.\n * When `selected`, it becomes a subtle filled state with a solid border.\n * Has no effect when `variant=\"dark\"`.\n * @default false\n */\n dotted?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Notification badge content (e.g. `\"99+\"`). Passed as a string for i18n support. */\n notificationLabel?: string;\n /** Click handler — when provided, the chip renders as a `<button>` for accessibility. */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /** Merge props onto a child element instead of rendering a wrapper. @default false */\n asChild?: boolean;\n}\n\n/**\n * A compact element for filters, tags, or toggleable actions. When an `onClick`\n * handler is provided, the chip renders as an interactive `<button>` with\n * `aria-pressed` support.\n *\n * @example\n * ```tsx\n * <Chip selected onClick={toggle}>Music</Chip>\n * ```\n */\nexport const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(\n (\n {\n className,\n variant = \"rounded\",\n size = \"32\",\n selected = false,\n disabled = false,\n leftDot = false,\n dotted = false,\n leftIcon,\n rightIcon,\n notificationLabel,\n onClick,\n asChild = false,\n children,\n ...props\n },\n ref,\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variant-heavy UI component\n ) => {\n const isInteractive = !!onClick && !asChild;\n const Comp = asChild ? Slot : isInteractive ? \"button\" : \"span\";\n const isDark = variant === \"dark\";\n\n // The dashed border is drawn as an SVG so the dash length matches the design\n // spec (8/8 for the 40px square, 6/6 otherwise). CSS `border-dashed` only\n // renders browser-default dash lengths, which are too short. The stroke colour\n // is driven by `currentColor`, and `group-hover`/`group-active` react to the\n // chip's interactive states. Rendered only in the default (non-`asChild`) path.\n const showDottedBorder = !isDark && dotted && !selected && !asChild;\n const dottedBorder = showDottedBorder ? (\n <svg\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 size-full overflow-visible\",\n disabled ? \"text-buttons-chip-disabled\" : \"text-buttons-chip-dotted-default\",\n isInteractive &&\n !disabled &&\n \"group-hover:text-buttons-chip-dotted-hover-stroke group-active:text-buttons-chip-dotted-hover-stroke\",\n )}\n >\n <rect\n x=\"0.5\"\n y=\"0.5\"\n rx={variant === \"square\" ? 7.5 : size === \"40\" ? 19.5 : 15.5}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeDasharray={variant === \"square\" && size === \"40\" ? \"8 8\" : \"6 6\"}\n style={{ width: \"calc(100% - 1px)\", height: \"calc(100% - 1px)\" }}\n />\n </svg>\n ) : null;\n\n return (\n <Comp\n ref={ref}\n data-testid=\"chip\"\n className={cn(\n \"relative inline-flex min-w-0 items-center justify-center whitespace-nowrap motion-safe:transition-colors motion-safe:duration-150\",\n // Shape\n variant === \"square\" ? \"rounded-xs\" : \"rounded-full\",\n // Size (32px uses 12px text, 40px uses 14px text per the design spec)\n size === \"32\" && \"typography-description-12px-semibold h-8 py-1\",\n size === \"40\" && \"typography-body-small-14px-semibold h-10 py-2.5\",\n // Variant colors\n isDark && \"bg-neutral-alphas-600 text-content-always-white\",\n !isDark && selected && !dotted && \"bg-buttons-chip-active text-content-primary-inverted\",\n // Active + dotted is a subtle filled state with a solid (non-dashed) border.\n !isDark &&\n selected &&\n dotted &&\n \"border border-border-primary border-solid bg-inputs-inputs-primary text-content-primary\",\n !isDark && !selected && !dotted && \"bg-buttons-chip-default text-content-primary\",\n // Dotted (non-selected): the dashed border is drawn via SVG (`dottedBorder`).\n // `group` lets that SVG react to hover/active. `asChild` keeps a CSS dashed\n // border fallback since the SVG is only rendered in the default path.\n !isDark && !selected && dotted && \"bg-transparent text-content-primary\",\n !isDark && !selected && dotted && !asChild && \"group\",\n asChild &&\n !isDark &&\n !selected &&\n dotted &&\n (disabled\n ? \"border border-buttons-chip-disabled border-dashed\"\n : \"border border-buttons-chip-dotted-default border-dashed\"),\n // Interactive\n isInteractive && !disabled && \"cursor-pointer\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n !dotted &&\n \"hover:bg-buttons-chip-hover\",\n isInteractive &&\n !disabled &&\n !isDark &&\n !selected &&\n dotted &&\n \"hover:bg-neutral-alphas-50 active:bg-neutral-alphas-50\",\n // Focus\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n // Disabled\n disabled && isDark && \"pointer-events-none opacity-50\",\n disabled && !isDark && \"pointer-events-none text-content-disabled\",\n // Solid (non-dotted) disabled chips get a muted fill; dotted ones stay\n // transparent with their dashed border (drawn via SVG).\n disabled && !isDark && !dotted && \"bg-buttons-chip-disabled\",\n className,\n )}\n {...(isInteractive && {\n type: \"button\" as const,\n disabled,\n \"aria-pressed\": selected,\n onClick,\n })}\n {...(!isInteractive && disabled && { \"aria-disabled\": true })}\n {...(selected && { \"data-selected\": \"\" })}\n {...props}\n >\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <>\n {dottedBorder}\n <span className=\"flex min-w-0 items-center gap-0.5 overflow-hidden px-3\">\n {leftDot && (\n <span className=\"size-2 shrink-0 rounded-full bg-current\" aria-hidden=\"true\" />\n )}\n {leftIcon && (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{children}</span>\n {rightIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center\"\n aria-hidden=\"true\"\n >\n {rightIcon}\n </span>\n )}\n </span>\n {notificationLabel && (\n <span className=\"typography-description-12px-semibold absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-content-primary px-1 text-content-primary-inverted\">\n {notificationLabel}\n </span>\n )}\n </>\n )}\n </Comp>\n );\n },\n);\n\nChip.displayName = \"Chip\";\n"],"names":[],"mappings":";;;;;AAiDO,MAAM,OAAO,MAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QAEG;AACH,UAAM,gBAAgB,CAAC,CAAC,WAAW,CAAC;AACpC,UAAM,OAAO,UAAU,OAAO,gBAAgB,WAAW;AACzD,UAAM,SAAS,YAAY;AAO3B,UAAM,mBAAmB,CAAC,UAAU,UAAU,CAAC,YAAY,CAAC;AAC5D,UAAM,eAAe,mBACnB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA,WAAW,+BAA+B;AAAA,UAC1C,iBACE,CAAC,YACD;AAAA,QAAA;AAAA,QAGJ,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,GAAE;AAAA,YACF,GAAE;AAAA,YACF,IAAI,YAAY,WAAW,MAAM,SAAS,OAAO,OAAO;AAAA,YACxD,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,iBAAiB,YAAY,YAAY,SAAS,OAAO,QAAQ;AAAA,YACjE,OAAO,EAAE,OAAO,oBAAoB,QAAQ,mBAAA;AAAA,UAAmB;AAAA,QAAA;AAAA,MACjE;AAAA,IAAA,IAEA;AAEJ,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA;AAAA,UAEA,YAAY,WAAW,eAAe;AAAA;AAAA,UAEtC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA;AAAA,UAEjB,UAAU;AAAA,UACV,CAAC,UAAU,YAAY,CAAC,UAAU;AAAA;AAAA,UAElC,CAAC,UACC,YACA,UACA;AAAA,UACF,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU;AAAA;AAAA;AAAA;AAAA,UAInC,CAAC,UAAU,CAAC,YAAY,UAAU;AAAA,UAClC,CAAC,UAAU,CAAC,YAAY,UAAU,CAAC,WAAW;AAAA,UAC9C,WACE,CAAC,UACD,CAAC,YACD,WACC,WACG,sDACA;AAAA;AAAA,UAEN,iBAAiB,CAAC,YAAY;AAAA,UAC9B,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,CAAC,UACD;AAAA,UACF,iBACE,CAAC,YACD,CAAC,UACD,CAAC,YACD,UACA;AAAA;AAAA,UAEF;AAAA;AAAA,UAEA,YAAY,UAAU;AAAA,UACtB,YAAY,CAAC,UAAU;AAAA;AAAA;AAAA,UAGvB,YAAY,CAAC,UAAU,CAAC,UAAU;AAAA,UAClC;AAAA,QAAA;AAAA,QAED,GAAI,iBAAiB;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAAA,QAED,GAAI,CAAC,iBAAiB,YAAY,EAAE,iBAAiB,KAAA;AAAA,QACrD,GAAI,YAAY,EAAE,iBAAiB,GAAA;AAAA,QACnC,GAAG;AAAA,QAEH,UAAA,UACC,oBAAC,WAAA,EAAW,SAAA,CAAS,IAErB,qBAAA,UAAA,EACG,UAAA;AAAA,UAAA;AAAA,UACD,qBAAC,QAAA,EAAK,WAAU,0DACb,UAAA;AAAA,YAAA,WACC,oBAAC,QAAA,EAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,YAE9E,YACC,oBAAC,QAAA,EAAK,WAAU,6CAA4C,eAAY,QACrE,UAAA,UACH;AAAA,YAEF,oBAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,YAC5C,aACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEX,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,GAEJ;AAAA,UACC,qBACC,oBAAC,QAAA,EAAK,WAAU,iLACb,UAAA,kBAAA,CACH;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,KAAK,cAAc;"}
|
|
@@ -10,14 +10,16 @@ const alignLeftClasses = {
|
|
|
10
10
|
lg: "[&>[role=tab]]:lg:flex-initial",
|
|
11
11
|
xl: "[&>[role=tab]]:xl:flex-initial"
|
|
12
12
|
};
|
|
13
|
-
function getLayoutClass(fullWidth, alignLeft) {
|
|
13
|
+
function getLayoutClass(variant, fullWidth, alignLeft) {
|
|
14
|
+
if (variant === "hug") return "inline-flex";
|
|
15
|
+
if (variant === "fill") return "flex w-full [&>[role=tab]]:flex-1";
|
|
14
16
|
if (!fullWidth) return "inline-flex";
|
|
15
17
|
const base = "flex w-full [&>[role=tab]]:flex-1";
|
|
16
18
|
if (alignLeft === true) return `${base} ${alignLeftClasses.always}`;
|
|
17
19
|
if (typeof alignLeft === "string") return `${base} ${alignLeftClasses[alignLeft]}`;
|
|
18
20
|
return base;
|
|
19
21
|
}
|
|
20
|
-
const TabsList = React.forwardRef(({ className, children, fullWidth = true, alignLeft, ...props }, ref) => {
|
|
22
|
+
const TabsList = React.forwardRef(({ className, children, fullWidth = true, alignLeft, variant, ...props }, ref) => {
|
|
21
23
|
const innerRef = React.useRef(null);
|
|
22
24
|
const indicatorRef = React.useRef(null);
|
|
23
25
|
React.useImperativeHandle(ref, () => innerRef.current);
|
|
@@ -33,15 +35,21 @@ const TabsList = React.forwardRef(({ className, children, fullWidth = true, alig
|
|
|
33
35
|
const isVertical = list.dataset.orientation === "vertical";
|
|
34
36
|
indicator.style.opacity = "1";
|
|
35
37
|
if (isVertical) {
|
|
36
|
-
indicator.style.
|
|
37
|
-
indicator.style.
|
|
38
|
+
indicator.style.background = "linear-gradient(180deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)";
|
|
39
|
+
indicator.style.inset = `0 -1px auto auto`;
|
|
40
|
+
indicator.style.width = "1px";
|
|
38
41
|
indicator.style.height = `${activeTab.offsetHeight}px`;
|
|
39
42
|
indicator.style.transform = `translateY(${activeTab.offsetTop}px)`;
|
|
40
43
|
} else {
|
|
41
|
-
indicator.style.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
indicator.style.background = "linear-gradient(90deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)";
|
|
45
|
+
const textSpan = activeTab.querySelector("span");
|
|
46
|
+
const textWidth = (textSpan ?? activeTab).getBoundingClientRect().width;
|
|
47
|
+
const tabCenter = activeTab.offsetLeft + activeTab.offsetWidth / 2;
|
|
48
|
+
const indicatorLeft = tabCenter - textWidth / 2;
|
|
49
|
+
indicator.style.inset = "auto auto 0 0";
|
|
50
|
+
indicator.style.height = "1px";
|
|
51
|
+
indicator.style.width = `${textWidth}px`;
|
|
52
|
+
indicator.style.transform = `translateX(${indicatorLeft}px)`;
|
|
45
53
|
}
|
|
46
54
|
}, []);
|
|
47
55
|
React.useLayoutEffect(() => {
|
|
@@ -79,9 +87,9 @@ const TabsList = React.forwardRef(({ className, children, fullWidth = true, alig
|
|
|
79
87
|
ref: innerRef,
|
|
80
88
|
className: cn(
|
|
81
89
|
"relative",
|
|
82
|
-
getLayoutClass(fullWidth, alignLeft),
|
|
83
|
-
"data-[orientation=horizontal]:items-center data-[orientation=horizontal]:shadow-[inset_0_-1px_0_0_var(--color-
|
|
84
|
-
"data-[orientation=vertical]:flex-col data-[orientation=vertical]:shadow-[inset_-1px_0_0_0_var(--color-
|
|
90
|
+
getLayoutClass(variant, fullWidth, alignLeft),
|
|
91
|
+
"data-[orientation=horizontal]:items-center data-[orientation=horizontal]:shadow-[inset_0_-1px_0_0_var(--color-border-primary)]",
|
|
92
|
+
"data-[orientation=vertical]:flex-col data-[orientation=vertical]:shadow-[inset_-1px_0_0_0_var(--color-border-primary)]",
|
|
85
93
|
className
|
|
86
94
|
),
|
|
87
95
|
...props,
|
|
@@ -92,7 +100,7 @@ const TabsList = React.forwardRef(({ className, children, fullWidth = true, alig
|
|
|
92
100
|
{
|
|
93
101
|
ref: indicatorRef,
|
|
94
102
|
"aria-hidden": true,
|
|
95
|
-
className: "pointer-events-none absolute rounded-full
|
|
103
|
+
className: "pointer-events-none absolute z-10 rounded-full motion-safe:transition-[transform,width,height] motion-safe:duration-200 motion-safe:ease-in-out",
|
|
96
104
|
style: { opacity: 0 }
|
|
97
105
|
}
|
|
98
106
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabsList.mjs","sources":["../../../src/components/Tabs/TabsList.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Breakpoint values for responsive props. */\ntype Breakpoint = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nconst alignLeftClasses: Record<Breakpoint | \"always\", string> = {\n always: \"[&>[role=tab]]:flex-initial\",\n sm: \"[&>[role=tab]]:sm:flex-initial\",\n md: \"[&>[role=tab]]:md:flex-initial\",\n lg: \"[&>[role=tab]]:lg:flex-initial\",\n xl: \"[&>[role=tab]]:xl:flex-initial\",\n};\n\nfunction getLayoutClass(fullWidth: boolean
|
|
1
|
+
{"version":3,"file":"TabsList.mjs","sources":["../../../src/components/Tabs/TabsList.tsx"],"sourcesContent":["import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Breakpoint values for responsive props. */\ntype Breakpoint = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nconst alignLeftClasses: Record<Breakpoint | \"always\", string> = {\n always: \"[&>[role=tab]]:flex-initial\",\n sm: \"[&>[role=tab]]:sm:flex-initial\",\n md: \"[&>[role=tab]]:md:flex-initial\",\n lg: \"[&>[role=tab]]:lg:flex-initial\",\n xl: \"[&>[role=tab]]:xl:flex-initial\",\n};\n\nfunction getLayoutClass(\n variant: \"fill\" | \"hug\" | undefined,\n fullWidth: boolean,\n alignLeft?: boolean | Breakpoint,\n): string {\n if (variant === \"hug\") return \"inline-flex\";\n if (variant === \"fill\") return \"flex w-full [&>[role=tab]]:flex-1\";\n\n if (!fullWidth) return \"inline-flex\";\n\n const base = \"flex w-full [&>[role=tab]]:flex-1\";\n if (alignLeft === true) return `${base} ${alignLeftClasses.always}`;\n if (typeof alignLeft === \"string\") return `${base} ${alignLeftClasses[alignLeft]}`;\n return base;\n}\n\n/** Props for the {@link TabsList} component. */\nexport type TabsListProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {\n /** When `true` (the default), the tab list spans the full width of its container and each tab grows equally. Set to `false` for inline sizing. */\n fullWidth?: boolean;\n /**\n * Controls tab alignment within a full-width container.\n * - `false` (default): tabs spread evenly\n * - `true`: tabs left-aligned, sized to content\n * - `\"md\"` (breakpoint): spread on mobile, left-aligned at breakpoint and up\n */\n alignLeft?: boolean | Breakpoint;\n /** Explicit layout variant. `\"hug\"` sizes the list to its content; `\"fill\"` stretches it full-width with equal-width tabs. Takes precedence over `fullWidth`/`alignLeft`. */\n variant?: \"fill\" | \"hug\";\n};\n\n/** Container for {@link TabsTrigger} elements. Renders a sliding active-tab indicator that animates between tabs. */\nexport const TabsList = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.List>,\n TabsListProps\n>(({ className, children, fullWidth = true, alignLeft, variant, ...props }, ref) => {\n const innerRef = React.useRef<HTMLDivElement>(null);\n const indicatorRef = React.useRef<HTMLSpanElement>(null);\n\n React.useImperativeHandle(ref, () => innerRef.current as HTMLDivElement);\n\n const updateIndicator = React.useCallback(() => {\n const list = innerRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector<HTMLElement>('[data-state=\"active\"]');\n if (!activeTab) {\n indicator.style.opacity = \"0\";\n return;\n }\n\n const isVertical = list.dataset.orientation === \"vertical\";\n\n indicator.style.opacity = \"1\";\n\n if (isVertical) {\n indicator.style.background =\n \"linear-gradient(180deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)\";\n indicator.style.inset = `0 -1px auto auto`;\n indicator.style.width = \"1px\";\n indicator.style.height = `${activeTab.offsetHeight}px`;\n indicator.style.transform = `translateY(${activeTab.offsetTop}px)`;\n } else {\n indicator.style.background =\n \"linear-gradient(90deg, var(--color-buttons-tertiary-default) 0%, var(--color-tab-active) 50%, var(--color-buttons-tertiary-default) 100%)\";\n const textSpan = activeTab.querySelector(\"span\");\n const textWidth = (textSpan ?? activeTab).getBoundingClientRect().width;\n const tabCenter = activeTab.offsetLeft + activeTab.offsetWidth / 2;\n const indicatorLeft = tabCenter - textWidth / 2;\n indicator.style.inset = \"auto auto 0 0\";\n indicator.style.height = \"1px\";\n indicator.style.width = `${textWidth}px`;\n indicator.style.transform = `translateX(${indicatorLeft}px)`;\n }\n }, []);\n\n React.useLayoutEffect(() => {\n const list = innerRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n indicator.style.transitionDuration = \"0s\";\n updateIndicator();\n indicator.getBoundingClientRect();\n indicator.style.transitionDuration = \"\";\n\n const mutationObserver = new MutationObserver(updateIndicator);\n mutationObserver.observe(list, {\n attributes: true,\n attributeFilter: [\"data-state\"],\n childList: true,\n subtree: true,\n });\n\n const resizeObserver = new ResizeObserver(updateIndicator);\n resizeObserver.observe(list);\n\n let cancelled = false;\n if (document.fonts?.status !== \"loaded\") {\n document.fonts?.ready.then(() => {\n if (!cancelled) updateIndicator();\n });\n }\n\n return () => {\n cancelled = true;\n mutationObserver.disconnect();\n resizeObserver.disconnect();\n };\n }, [updateIndicator]);\n\n return (\n <TabsPrimitive.List\n ref={innerRef}\n className={cn(\n \"relative\",\n getLayoutClass(variant, fullWidth, alignLeft),\n \"data-[orientation=horizontal]:items-center data-[orientation=horizontal]:shadow-[inset_0_-1px_0_0_var(--color-border-primary)]\",\n \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:shadow-[inset_-1px_0_0_0_var(--color-border-primary)]\",\n className,\n )}\n {...props}\n >\n {children}\n <span\n ref={indicatorRef}\n aria-hidden\n className=\"pointer-events-none absolute z-10 rounded-full motion-safe:transition-[transform,width,height] motion-safe:duration-200 motion-safe:ease-in-out\"\n style={{ opacity: 0 }}\n />\n </TabsPrimitive.List>\n );\n});\n\nTabsList.displayName = \"TabsList\";\n"],"names":[],"mappings":"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"}
|
|
@@ -9,18 +9,19 @@ const TabsTrigger = React.forwardRef(({ className, children, ...props }, ref) =>
|
|
|
9
9
|
ref,
|
|
10
10
|
className: cn(
|
|
11
11
|
"inline-flex min-w-0 items-center justify-center",
|
|
12
|
-
"
|
|
13
|
-
"typography-body-default-16px-semibold cursor-pointer text-content-primary",
|
|
12
|
+
"typography-body-default-16px-semibold cursor-pointer",
|
|
14
13
|
"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
|
|
15
14
|
"data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3",
|
|
16
15
|
"data-[orientation=vertical]:justify-start data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3",
|
|
17
|
-
"data-[state=active]:
|
|
18
|
-
"data-[state=inactive]:
|
|
19
|
-
"data-[state=active]:
|
|
20
|
-
"data-[state=inactive]:
|
|
16
|
+
"data-[state=active]:text-content-primary",
|
|
17
|
+
"data-[state=inactive]:text-content-tertiary",
|
|
18
|
+
"data-[state=active]:hover:text-content-primary/70",
|
|
19
|
+
"data-[state=inactive]:hover:text-content-primary",
|
|
20
|
+
"data-[state=active]:active:text-content-primary/70",
|
|
21
|
+
"data-[state=inactive]:active:text-content-primary",
|
|
21
22
|
"data-disabled:pointer-events-none",
|
|
22
23
|
"data-disabled:data-[state=active]:text-content-tertiary",
|
|
23
|
-
"data-disabled:data-[state=inactive]:text-neutral-alphas-
|
|
24
|
+
"data-disabled:data-[state=inactive]:text-neutral-alphas-200",
|
|
24
25
|
"focus-visible:shadow-focus-ring focus-visible:outline-none",
|
|
25
26
|
className
|
|
26
27
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabsTrigger.mjs","sources":["../../../src/components/Tabs/TabsTrigger.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 TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, children, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex min-w-0 items-center justify-center\",\n \"
|
|
1
|
+
{"version":3,"file":"TabsTrigger.mjs","sources":["../../../src/components/Tabs/TabsTrigger.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 TabsTrigger} button component. */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/** An interactive tab button that activates its associated {@link TabsContent} panel when clicked. */\nexport const TabsTrigger = React.forwardRef<\n React.ComponentRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, children, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex min-w-0 items-center justify-center\",\n \"typography-body-default-16px-semibold cursor-pointer\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"data-[orientation=horizontal]:px-4 data-[orientation=horizontal]:py-3\",\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:px-4 data-[orientation=vertical]:py-3\",\n \"data-[state=active]:text-content-primary\",\n \"data-[state=inactive]:text-content-tertiary\",\n \"data-[state=active]:hover:text-content-primary/70\",\n \"data-[state=inactive]:hover:text-content-primary\",\n \"data-[state=active]:active:text-content-primary/70\",\n \"data-[state=inactive]:active:text-content-primary\",\n \"data-disabled:pointer-events-none\",\n \"data-disabled:data-[state=active]:text-content-tertiary\",\n \"data-disabled:data-[state=inactive]:text-neutral-alphas-200\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n className,\n )}\n {...props}\n >\n <span className=\"min-w-0 truncate\">{children}</span>\n </TabsPrimitive.Trigger>\n));\n\nTabsTrigger.displayName = \"TabsTrigger\";\n"],"names":[],"mappings":";;;;;AAQO,MAAM,cAAc,MAAM,WAG/B,CAAC,EAAE,WAAW,UAAU,GAAG,SAAS,QACpC;AAAA,EAAC,cAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,IAEJ,UAAA,oBAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,EAAA;AAC/C,CACD;AAED,YAAY,cAAc;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1318,7 +1318,8 @@ export declare interface ChipProps extends React_2.HTMLAttributes<HTMLElement> {
|
|
|
1318
1318
|
leftDot?: boolean;
|
|
1319
1319
|
/**
|
|
1320
1320
|
* Whether the chip uses a dashed border for add/create affordances.
|
|
1321
|
-
*
|
|
1321
|
+
* When `selected`, it becomes a subtle filled state with a solid border.
|
|
1322
|
+
* Has no effect when `variant="dark"`.
|
|
1322
1323
|
* @default false
|
|
1323
1324
|
*/
|
|
1324
1325
|
dotted?: boolean;
|
|
@@ -4508,6 +4509,8 @@ export declare const TabsList: React_2.ForwardRefExoticComponent<Omit<TabsPrimit
|
|
|
4508
4509
|
* - `"md"` (breakpoint): spread on mobile, left-aligned at breakpoint and up
|
|
4509
4510
|
*/
|
|
4510
4511
|
alignLeft?: boolean | Breakpoint;
|
|
4512
|
+
/** Explicit layout variant. `"hug"` sizes the list to its content; `"fill"` stretches it full-width with equal-width tabs. Takes precedence over `fullWidth`/`alignLeft`. */
|
|
4513
|
+
variant?: "fill" | "hug";
|
|
4511
4514
|
} & React_2.RefAttributes<HTMLDivElement>>;
|
|
4512
4515
|
|
|
4513
4516
|
/** Props for the {@link TabsList} component. */
|
|
@@ -4521,6 +4524,8 @@ export declare type TabsListProps = React_2.ComponentPropsWithoutRef<typeof Tabs
|
|
|
4521
4524
|
* - `"md"` (breakpoint): spread on mobile, left-aligned at breakpoint and up
|
|
4522
4525
|
*/
|
|
4523
4526
|
alignLeft?: boolean | Breakpoint;
|
|
4527
|
+
/** Explicit layout variant. `"hug"` sizes the list to its content; `"fill"` stretches it full-width with equal-width tabs. Takes precedence over `fullWidth`/`alignLeft`. */
|
|
4528
|
+
variant?: "fill" | "hug";
|
|
4524
4529
|
};
|
|
4525
4530
|
|
|
4526
4531
|
/** Props for the {@link Tabs} root component. Extends Radix `Tabs.Root` props. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanvue/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "React component library built with Tailwind CSS for Fanvue ecosystem",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -171,6 +171,7 @@
|
|
|
171
171
|
"ws": ">=8.20.1",
|
|
172
172
|
"qs": ">=6.15.2",
|
|
173
173
|
"esbuild": ">=0.28.1",
|
|
174
|
+
"vite": "^7.3.5",
|
|
174
175
|
"js-yaml": ">=4.2.0",
|
|
175
176
|
"@babel/core": ">=7.29.6"
|
|
176
177
|
}
|