@fanvue/ui 3.7.2 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/Alert/Alert.cjs +28 -5
- package/dist/cjs/components/Alert/Alert.cjs.map +1 -1
- package/dist/cjs/components/Badge/Badge.cjs +17 -5
- package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
- package/dist/cjs/components/Breadcrumb/Breadcrumb.cjs +46 -23
- package/dist/cjs/components/Breadcrumb/Breadcrumb.cjs.map +1 -1
- package/dist/cjs/components/Checkbox/Checkbox.cjs +1 -1
- package/dist/cjs/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/cjs/components/Dialog/Dialog.cjs +21 -8
- package/dist/cjs/components/Dialog/Dialog.cjs.map +1 -1
- package/dist/cjs/components/FanFollowerCount/FanFollowerCount.cjs +64 -0
- package/dist/cjs/components/FanFollowerCount/FanFollowerCount.cjs.map +1 -0
- package/dist/cjs/components/IconButton/IconButton.cjs +1 -1
- package/dist/cjs/components/IconButton/IconButton.cjs.map +1 -1
- package/dist/cjs/components/Icons/GifIcon.cjs +101 -0
- package/dist/cjs/components/Icons/GifIcon.cjs.map +1 -0
- package/dist/cjs/components/Icons/VerifiedIcon.cjs +82 -0
- package/dist/cjs/components/Icons/VerifiedIcon.cjs.map +1 -0
- package/dist/cjs/components/Link/Link.cjs +85 -0
- package/dist/cjs/components/Link/Link.cjs.map +1 -0
- package/dist/cjs/components/Pagination/Pagination.cjs +7 -7
- package/dist/cjs/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/cjs/components/Pill/Pill.cjs +3 -1
- package/dist/cjs/components/Pill/Pill.cjs.map +1 -1
- package/dist/cjs/components/ProfileOnlineStatus/ProfileOnlineStatus.cjs +44 -0
- package/dist/cjs/components/ProfileOnlineStatus/ProfileOnlineStatus.cjs.map +1 -0
- package/dist/cjs/components/ProfileStatus/ProfileStatus.cjs +51 -0
- package/dist/cjs/components/ProfileStatus/ProfileStatus.cjs.map +1 -0
- package/dist/cjs/components/Radio/Radio.cjs +65 -34
- package/dist/cjs/components/Radio/Radio.cjs.map +1 -1
- package/dist/cjs/components/RatingSummary/RatingSummary.cjs +103 -0
- package/dist/cjs/components/RatingSummary/RatingSummary.cjs.map +1 -0
- package/dist/cjs/components/ReviewCard/ReviewCard.cjs +51 -0
- package/dist/cjs/components/ReviewCard/ReviewCard.cjs.map +1 -0
- package/dist/cjs/components/Select/Select.cjs +4 -4
- package/dist/cjs/components/Select/Select.cjs.map +1 -1
- package/dist/cjs/components/TextField/TextField.cjs +4 -4
- package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
- package/dist/cjs/components/Toast/Toast.cjs +18 -32
- package/dist/cjs/components/Toast/Toast.cjs.map +1 -1
- package/dist/cjs/components/UserDisplayName/UserDisplayName.cjs +67 -0
- package/dist/cjs/components/UserDisplayName/UserDisplayName.cjs.map +1 -0
- package/dist/cjs/components/UserHandle/UserHandle.cjs +46 -0
- package/dist/cjs/components/UserHandle/UserHandle.cjs.map +1 -0
- package/dist/cjs/components/UserItem/UserItem.cjs +74 -0
- package/dist/cjs/components/UserItem/UserItem.cjs.map +1 -0
- package/dist/cjs/index.cjs +24 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/utils/getInitials.cjs +10 -0
- package/dist/cjs/utils/getInitials.cjs.map +1 -0
- package/dist/components/Alert/Alert.mjs +28 -5
- package/dist/components/Alert/Alert.mjs.map +1 -1
- package/dist/components/Badge/Badge.mjs +17 -5
- package/dist/components/Badge/Badge.mjs.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.mjs +46 -23
- package/dist/components/Breadcrumb/Breadcrumb.mjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.mjs +1 -1
- package/dist/components/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/components/Dialog/Dialog.mjs +21 -8
- package/dist/components/Dialog/Dialog.mjs.map +1 -1
- package/dist/components/FanFollowerCount/FanFollowerCount.mjs +47 -0
- package/dist/components/FanFollowerCount/FanFollowerCount.mjs.map +1 -0
- package/dist/components/IconButton/IconButton.mjs +1 -1
- package/dist/components/IconButton/IconButton.mjs.map +1 -1
- package/dist/components/Icons/GifIcon.mjs +84 -0
- package/dist/components/Icons/GifIcon.mjs.map +1 -0
- package/dist/components/Icons/VerifiedIcon.mjs +65 -0
- package/dist/components/Icons/VerifiedIcon.mjs.map +1 -0
- package/dist/components/Link/Link.mjs +68 -0
- package/dist/components/Link/Link.mjs.map +1 -0
- package/dist/components/Pagination/Pagination.mjs +7 -7
- package/dist/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/components/Pill/Pill.mjs +3 -1
- package/dist/components/Pill/Pill.mjs.map +1 -1
- package/dist/components/ProfileOnlineStatus/ProfileOnlineStatus.mjs +27 -0
- package/dist/components/ProfileOnlineStatus/ProfileOnlineStatus.mjs.map +1 -0
- package/dist/components/ProfileStatus/ProfileStatus.mjs +34 -0
- package/dist/components/ProfileStatus/ProfileStatus.mjs.map +1 -0
- package/dist/components/Radio/Radio.mjs +66 -35
- package/dist/components/Radio/Radio.mjs.map +1 -1
- package/dist/components/RatingSummary/RatingSummary.mjs +86 -0
- package/dist/components/RatingSummary/RatingSummary.mjs.map +1 -0
- package/dist/components/ReviewCard/ReviewCard.mjs +34 -0
- package/dist/components/ReviewCard/ReviewCard.mjs.map +1 -0
- package/dist/components/Select/Select.mjs +4 -4
- package/dist/components/Select/Select.mjs.map +1 -1
- package/dist/components/TextField/TextField.mjs +4 -4
- package/dist/components/TextField/TextField.mjs.map +1 -1
- package/dist/components/Toast/Toast.mjs +18 -32
- package/dist/components/Toast/Toast.mjs.map +1 -1
- package/dist/components/UserDisplayName/UserDisplayName.mjs +50 -0
- package/dist/components/UserDisplayName/UserDisplayName.mjs.map +1 -0
- package/dist/components/UserHandle/UserHandle.mjs +29 -0
- package/dist/components/UserHandle/UserHandle.mjs.map +1 -0
- package/dist/components/UserItem/UserItem.mjs +57 -0
- package/dist/components/UserItem/UserItem.mjs.map +1 -0
- package/dist/index.d.ts +404 -15
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +1 -1
- package/dist/utils/getInitials.mjs +10 -0
- package/dist/utils/getInitials.mjs.map +1 -0
- package/package.json +2 -2
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
|
-
const Radio = React.forwardRef(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const Radio = React.forwardRef(
|
|
7
|
+
({ className, size = "default", label, helperText, layout = "leading", avatar, id, ...props }, ref) => {
|
|
8
|
+
const generatedId = React.useId();
|
|
9
|
+
const inputId = id || generatedId;
|
|
10
|
+
const helperTextId = `${inputId}-helper`;
|
|
11
|
+
const hasContent = Boolean(label || helperText || avatar);
|
|
12
|
+
const centerWithAvatar = Boolean(avatar && helperText);
|
|
13
|
+
const button = /* @__PURE__ */ jsx(
|
|
12
14
|
RadioGroupPrimitive.Item,
|
|
13
15
|
{
|
|
14
16
|
ref,
|
|
@@ -17,38 +19,67 @@ const Radio = React.forwardRef(({ className, size = "default", label, helperText
|
|
|
17
19
|
"aria-describedby": helperText ? helperTextId : void 0,
|
|
18
20
|
className: cn(
|
|
19
21
|
"relative h-4 w-4 shrink-0 cursor-pointer appearance-none rounded-full border border-content-primary bg-transparent transition-colors hover:bg-brand-primary-muted focus-visible:shadow-focus-ring focus-visible:outline-none not-disabled:active:bg-brand-primary-muted disabled:cursor-not-allowed disabled:border-neutral-alphas-600 disabled:bg-transparent data-[state=checked]:border-content-primary data-[state=checked]:bg-transparent",
|
|
20
|
-
|
|
22
|
+
hasContent && !centerWithAvatar && (avatar ? "mt-2" : size === "small" ? "mt-px" : "mt-1")
|
|
21
23
|
),
|
|
22
24
|
...props,
|
|
23
25
|
children: /* @__PURE__ */ jsx(RadioGroupPrimitive.Indicator, { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-content-primary group-has-disabled:bg-neutral-alphas-600" }) })
|
|
24
26
|
}
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
),
|
|
35
|
-
children:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
);
|
|
28
|
+
const content = hasContent && /* @__PURE__ */ jsxs(
|
|
29
|
+
"div",
|
|
30
|
+
{
|
|
31
|
+
className: cn(
|
|
32
|
+
"flex min-w-0 flex-1 gap-3",
|
|
33
|
+
centerWithAvatar ? "items-center" : "items-start"
|
|
34
|
+
),
|
|
35
|
+
children: [
|
|
36
|
+
avatar && /* @__PURE__ */ jsx("span", { className: "shrink-0 group-has-disabled:opacity-60", "aria-hidden": "true", children: avatar }),
|
|
37
|
+
(label || helperText) && /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-0.5", avatar && !centerWithAvatar && "pt-1"), children: [
|
|
38
|
+
label && /* @__PURE__ */ jsx(
|
|
39
|
+
"label",
|
|
40
|
+
{
|
|
41
|
+
htmlFor: inputId,
|
|
42
|
+
className: cn(
|
|
43
|
+
"cursor-pointer select-none text-content-primary group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary",
|
|
44
|
+
size === "small" ? "typography-body-small-14px-semibold" : "typography-body-default-16px-semibold"
|
|
45
|
+
),
|
|
46
|
+
children: label
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
helperText && /* @__PURE__ */ jsx(
|
|
50
|
+
"span",
|
|
51
|
+
{
|
|
52
|
+
id: helperTextId,
|
|
53
|
+
className: cn(
|
|
54
|
+
"text-content-secondary group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary",
|
|
55
|
+
size === "small" ? "typography-body-small-14px-semibold" : "typography-description-12px-regular"
|
|
56
|
+
),
|
|
57
|
+
children: helperText
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
] })
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
return /* @__PURE__ */ jsxs(
|
|
65
|
+
"div",
|
|
66
|
+
{
|
|
67
|
+
className: cn(
|
|
68
|
+
"group gap-3",
|
|
69
|
+
// Full width only for trailing (button to far end); leading keeps intrinsic width for inline groups.
|
|
70
|
+
layout === "trailing" ? "flex w-full" : "inline-flex",
|
|
71
|
+
centerWithAvatar ? "items-center" : "items-start",
|
|
72
|
+
className
|
|
73
|
+
),
|
|
74
|
+
children: [
|
|
75
|
+
layout === "leading" && button,
|
|
76
|
+
content,
|
|
77
|
+
layout === "trailing" && button
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
52
83
|
Radio.displayName = "Radio";
|
|
53
84
|
export {
|
|
54
85
|
Radio
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Radio.mjs","sources":["../../../src/components/Radio/Radio.tsx"],"sourcesContent":["import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport interface RadioProps\n extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, \"asChild\"> {\n /** Size variant controlling label and helper text typography. @default \"default\" */\n size?: \"default\" | \"small\";\n /** Label text displayed next to the radio button. */\n label?: string;\n /** Descriptive text displayed below the label. */\n helperText?: string;\n}\n\n/**\n * A single radio option within a {@link RadioGroup}.
|
|
1
|
+
{"version":3,"file":"Radio.mjs","sources":["../../../src/components/Radio/Radio.tsx"],"sourcesContent":["import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Placement of the radio button relative to its label. */\nexport type RadioLayout = \"leading\" | \"trailing\";\n\nexport interface RadioProps\n extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, \"asChild\"> {\n /** Size variant controlling label and helper text typography. @default \"default\" */\n size?: \"default\" | \"small\";\n /** Label text displayed next to the radio button. */\n label?: string;\n /** Descriptive text displayed below the label. */\n helperText?: string;\n /**\n * Placement of the radio button. `\"leading\"` renders it before the label;\n * `\"trailing\"` pushes it to the far end of the row (for list rows). @default \"leading\"\n */\n layout?: RadioLayout;\n /**\n * Optional avatar shown alongside the label, for options that represent a\n * person or account. Pass an {@link Avatar} sized to `32`.\n */\n avatar?: React.ReactNode;\n}\n\n/**\n * A single radio option within a {@link RadioGroup}. Supports leading or\n * trailing button placement, an optional avatar, a label, and helper text.\n *\n * @example\n * ```tsx\n * <RadioGroup value={value} onValueChange={setValue}>\n * <Radio value=\"a\" label=\"Option A\" />\n * <Radio value=\"b\" label=\"Option B\" layout=\"trailing\" />\n * </RadioGroup>\n * ```\n */\nexport const Radio = React.forwardRef<\n React.ComponentRef<typeof RadioGroupPrimitive.Item>,\n RadioProps\n>(\n (\n { className, size = \"default\", label, helperText, layout = \"leading\", avatar, id, ...props },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const hasContent = Boolean(label || helperText || avatar);\n // Avatar + helper text reads better centered as a group; otherwise align to the first line.\n const centerWithAvatar = Boolean(avatar && helperText);\n\n const button = (\n <RadioGroupPrimitive.Item\n ref={ref}\n id={inputId}\n data-testid=\"radio\"\n aria-describedby={helperText ? helperTextId : undefined}\n className={cn(\n \"relative h-4 w-4 shrink-0 cursor-pointer appearance-none rounded-full border border-content-primary bg-transparent transition-colors hover:bg-brand-primary-muted focus-visible:shadow-focus-ring focus-visible:outline-none not-disabled:active:bg-brand-primary-muted disabled:cursor-not-allowed disabled:border-neutral-alphas-600 disabled:bg-transparent data-[state=checked]:border-content-primary data-[state=checked]:bg-transparent\",\n hasContent &&\n !centerWithAvatar &&\n (avatar ? \"mt-2\" : size === \"small\" ? \"mt-px\" : \"mt-1\"),\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"size-2 rounded-full bg-content-primary group-has-disabled:bg-neutral-alphas-600\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n );\n\n const content = hasContent && (\n <div\n className={cn(\n \"flex min-w-0 flex-1 gap-3\",\n centerWithAvatar ? \"items-center\" : \"items-start\",\n )}\n >\n {avatar && (\n <span className=\"shrink-0 group-has-disabled:opacity-60\" aria-hidden=\"true\">\n {avatar}\n </span>\n )}\n {(label || helperText) && (\n <div className={cn(\"flex flex-col gap-0.5\", avatar && !centerWithAvatar && \"pt-1\")}>\n {label && (\n <label\n htmlFor={inputId}\n className={cn(\n \"cursor-pointer select-none text-content-primary group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary\",\n size === \"small\"\n ? \"typography-body-small-14px-semibold\"\n : \"typography-body-default-16px-semibold\",\n )}\n >\n {label}\n </label>\n )}\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"text-content-secondary group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary\",\n size === \"small\"\n ? \"typography-body-small-14px-semibold\"\n : \"typography-description-12px-regular\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n )}\n </div>\n );\n\n return (\n <div\n className={cn(\n \"group gap-3\",\n // Full width only for trailing (button to far end); leading keeps intrinsic width for inline groups.\n layout === \"trailing\" ? \"flex w-full\" : \"inline-flex\",\n centerWithAvatar ? \"items-center\" : \"items-start\",\n className,\n )}\n >\n {layout === \"leading\" && button}\n {content}\n {layout === \"trailing\" && button}\n </div>\n );\n },\n);\n\nRadio.displayName = \"Radio\";\n"],"names":[],"mappings":";;;;;AAuCO,MAAM,QAAQ,MAAM;AAAA,EAIzB,CACE,EAAE,WAAW,OAAO,WAAW,OAAO,YAAY,SAAS,WAAW,QAAQ,IAAI,GAAG,MAAA,GACrF,QACG;AACH,UAAM,cAAc,MAAM,MAAA;AAC1B,UAAM,UAAU,MAAM;AACtB,UAAM,eAAe,GAAG,OAAO;AAC/B,UAAM,aAAa,QAAQ,SAAS,cAAc,MAAM;AAExD,UAAM,mBAAmB,QAAQ,UAAU,UAAU;AAErD,UAAM,SACJ;AAAA,MAAC,oBAAoB;AAAA,MAApB;AAAA,QACC;AAAA,QACA,IAAI;AAAA,QACJ,eAAY;AAAA,QACZ,oBAAkB,aAAa,eAAe;AAAA,QAC9C,WAAW;AAAA,UACT;AAAA,UACA,cACE,CAAC,qBACA,SAAS,SAAS,SAAS,UAAU,UAAU;AAAA,QAAA;AAAA,QAEnD,GAAG;AAAA,QAEJ,UAAA,oBAAC,oBAAoB,WAApB,EAA8B,WAAU,qDACvC,UAAA,oBAAC,QAAA,EAAK,WAAU,kFAAA,CAAkF,EAAA,CACpG;AAAA,MAAA;AAAA,IAAA;AAIJ,UAAM,UAAU,cACd;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,mBAAmB,iBAAiB;AAAA,QAAA;AAAA,QAGrC,UAAA;AAAA,UAAA,8BACE,QAAA,EAAK,WAAU,0CAAyC,eAAY,QAClE,UAAA,QACH;AAAA,WAEA,SAAS,eACT,qBAAC,OAAA,EAAI,WAAW,GAAG,yBAAyB,UAAU,CAAC,oBAAoB,MAAM,GAC9E,UAAA;AAAA,YAAA,SACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT,WAAW;AAAA,kBACT;AAAA,kBACA,SAAS,UACL,wCACA;AAAA,gBAAA;AAAA,gBAGL,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,YAGJ,cACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,IAAI;AAAA,gBACJ,WAAW;AAAA,kBACT;AAAA,kBACA,SAAS,UACL,wCACA;AAAA,gBAAA;AAAA,gBAGL,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,EAAA,CAEJ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAKN,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA;AAAA,UAEA,WAAW,aAAa,gBAAgB;AAAA,UACxC,mBAAmB,iBAAiB;AAAA,UACpC;AAAA,QAAA;AAAA,QAGD,UAAA;AAAA,UAAA,WAAW,aAAa;AAAA,UACxB;AAAA,UACA,WAAW,cAAc;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGhC;AACF;AAEA,MAAM,cAAc;"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn.mjs";
|
|
5
|
+
import { StarIcon } from "../Icons/StarIcon.mjs";
|
|
6
|
+
function defaultFormatCount(count) {
|
|
7
|
+
return `${count.toLocaleString()} ${count === 1 ? "review" : "reviews"}`;
|
|
8
|
+
}
|
|
9
|
+
function getWeightedAverage(distribution) {
|
|
10
|
+
let total = 0;
|
|
11
|
+
let weighted = 0;
|
|
12
|
+
for (const { rating, count } of distribution) {
|
|
13
|
+
total += count;
|
|
14
|
+
weighted += rating * count;
|
|
15
|
+
}
|
|
16
|
+
return total === 0 ? 0 : weighted / total;
|
|
17
|
+
}
|
|
18
|
+
const RatingSummary = React.forwardRef(
|
|
19
|
+
({
|
|
20
|
+
distribution,
|
|
21
|
+
averageRating,
|
|
22
|
+
maxRating = 5,
|
|
23
|
+
formatCount = defaultFormatCount,
|
|
24
|
+
formatAverageLabel,
|
|
25
|
+
formatRatingLabel,
|
|
26
|
+
className,
|
|
27
|
+
...props
|
|
28
|
+
}, ref) => {
|
|
29
|
+
const countByRating = /* @__PURE__ */ new Map();
|
|
30
|
+
for (const { rating, count } of distribution) {
|
|
31
|
+
countByRating.set(rating, (countByRating.get(rating) ?? 0) + count);
|
|
32
|
+
}
|
|
33
|
+
const rows = Array.from({ length: maxRating }, (_, index) => {
|
|
34
|
+
const rating = maxRating - index;
|
|
35
|
+
return { rating, count: countByRating.get(rating) ?? 0 };
|
|
36
|
+
});
|
|
37
|
+
const totalCount = rows.reduce((sum, row) => sum + row.count, 0);
|
|
38
|
+
const maxCount = rows.reduce((max, row) => Math.max(max, row.count), 0);
|
|
39
|
+
const average = averageRating ?? getWeightedAverage(rows);
|
|
40
|
+
const averageLabel = average.toFixed(1);
|
|
41
|
+
const headerLabel = formatAverageLabel ? formatAverageLabel(averageLabel, maxRating, totalCount) : `Average rating ${averageLabel} out of ${maxRating}, ${formatCount(totalCount)}`;
|
|
42
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-4", className), ...props, children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", role: "img", "aria-label": headerLabel, children: [
|
|
44
|
+
/* @__PURE__ */ jsx(StarIcon, { size: 24, filled: true, className: "shrink-0 text-brand-primary-default" }),
|
|
45
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
|
|
46
|
+
/* @__PURE__ */ jsx("span", { className: "typography-body-small-14px-semibold text-content-primary", children: averageLabel }),
|
|
47
|
+
/* @__PURE__ */ jsxs("span", { className: "typography-body-small-14px-regular text-content-tertiary", children: [
|
|
48
|
+
"(",
|
|
49
|
+
formatCount(totalCount),
|
|
50
|
+
")"
|
|
51
|
+
] })
|
|
52
|
+
] })
|
|
53
|
+
] }),
|
|
54
|
+
/* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col gap-4 p-0", "aria-label": "Rating breakdown", children: rows.map(({ rating, count }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
55
|
+
"div",
|
|
56
|
+
{
|
|
57
|
+
className: "flex items-center gap-4",
|
|
58
|
+
role: "img",
|
|
59
|
+
"aria-label": formatRatingLabel ? formatRatingLabel(rating, count) : `${rating} ${rating === 1 ? "star" : "stars"}, ${formatCount(count)}`,
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-4 flex-1 overflow-hidden rounded-full bg-neutral-alphas-50", children: /* @__PURE__ */ jsx(
|
|
62
|
+
"div",
|
|
63
|
+
{
|
|
64
|
+
className: "absolute inset-y-0 left-0 rounded-full bg-brand-primary-default transition-[width] duration-300 ease-in-out",
|
|
65
|
+
style: { width: maxCount === 0 ? "0%" : `${count / maxCount * 100}%` }
|
|
66
|
+
}
|
|
67
|
+
) }),
|
|
68
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1.5", children: [
|
|
69
|
+
/* @__PURE__ */ jsx("span", { className: "typography-body-small-14px-semibold text-content-primary", children: rating }),
|
|
70
|
+
/* @__PURE__ */ jsxs("span", { className: "typography-body-small-14px-regular text-content-tertiary", children: [
|
|
71
|
+
"(",
|
|
72
|
+
formatCount(count),
|
|
73
|
+
")"
|
|
74
|
+
] })
|
|
75
|
+
] })
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
) }, rating)) })
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
RatingSummary.displayName = "RatingSummary";
|
|
83
|
+
export {
|
|
84
|
+
RatingSummary
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=RatingSummary.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RatingSummary.mjs","sources":["../../../src/components/RatingSummary/RatingSummary.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { StarIcon } from \"../Icons/StarIcon\";\n\n/** A single rating value paired with the number of reviews that gave it. */\nexport interface RatingCount {\n /** The rating value this entry represents (e.g. `5`). */\n rating: number;\n /** Number of reviews that gave this rating. */\n count: number;\n}\n\nexport interface RatingSummaryProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /**\n * Per-rating review counts. Order is not significant — rows always render from\n * `maxRating` down to `1`, and any rating without an entry renders as zero.\n * Entries are expected to use whole-number ratings within `1`–`maxRating`;\n * any outside that range are ignored, including in the derived total and average.\n */\n distribution: RatingCount[];\n /**\n * Average rating shown in the header. Defaults to the count-weighted mean of\n * `distribution`; provide it to display a server-computed average instead.\n */\n averageRating?: number;\n /** Highest possible rating. Sets how many histogram rows render. @default 5 */\n maxRating?: number;\n /**\n * Formats a review count into its label, without surrounding parentheses.\n * Used for the visible text and as the count portion of the default\n * accessible labels.\n * @default ``(count) => `${count.toLocaleString()} review(s)` ``\n */\n formatCount?: (count: number) => string;\n /**\n * Builds the accessible label for the header. Override to localise the\n * screen-reader text. `average` is the already-formatted average string.\n * @default ``(average, max, total) => `Average rating ${average} out of ${max}, ${formatCount(total)}` ``\n */\n formatAverageLabel?: (average: string, maxRating: number, total: number) => string;\n /**\n * Builds the accessible label for each histogram row. Override to localise the\n * screen-reader text.\n * @default ``(rating, count) => `${rating} star(s), ${formatCount(count)}` ``\n */\n formatRatingLabel?: (rating: number, count: number) => string;\n}\n\nfunction defaultFormatCount(count: number): string {\n return `${count.toLocaleString()} ${count === 1 ? \"review\" : \"reviews\"}`;\n}\n\nfunction getWeightedAverage(distribution: RatingCount[]): number {\n let total = 0;\n let weighted = 0;\n for (const { rating, count } of distribution) {\n total += count;\n weighted += rating * count;\n }\n return total === 0 ? 0 : weighted / total;\n}\n\n/**\n * An aggregated rating overview: a headline average with the total review count,\n * followed by a per-rating histogram. Bar lengths are scaled relative to the\n * most-reviewed rating, so the busiest rating always fills the track.\n *\n * The brand star is decorative; the header and each histogram row expose\n * `role=\"img\"` with an accessible label (override via `formatAverageLabel` /\n * `formatRatingLabel`) so the single-star-plus-number reads clearly.\n *\n * @example\n * ```tsx\n * <RatingSummary\n * distribution={[\n * { rating: 5, count: 300 },\n * { rating: 4, count: 20 },\n * { rating: 3, count: 20 },\n * { rating: 2, count: 10 },\n * { rating: 1, count: 0 },\n * ]}\n * />\n * ```\n */\nexport const RatingSummary = React.forwardRef<HTMLDivElement, RatingSummaryProps>(\n (\n {\n distribution,\n averageRating,\n maxRating = 5,\n formatCount = defaultFormatCount,\n formatAverageLabel,\n formatRatingLabel,\n className,\n ...props\n },\n ref,\n ) => {\n const countByRating = new Map<number, number>();\n for (const { rating, count } of distribution) {\n countByRating.set(rating, (countByRating.get(rating) ?? 0) + count);\n }\n\n const rows = Array.from({ length: maxRating }, (_, index) => {\n const rating = maxRating - index;\n return { rating, count: countByRating.get(rating) ?? 0 };\n });\n\n const totalCount = rows.reduce((sum, row) => sum + row.count, 0);\n const maxCount = rows.reduce((max, row) => Math.max(max, row.count), 0);\n const average = averageRating ?? getWeightedAverage(rows);\n const averageLabel = average.toFixed(1);\n\n const headerLabel = formatAverageLabel\n ? formatAverageLabel(averageLabel, maxRating, totalCount)\n : `Average rating ${averageLabel} out of ${maxRating}, ${formatCount(totalCount)}`;\n\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-4\", className)} {...props}>\n <div className=\"flex items-center gap-1\" role=\"img\" aria-label={headerLabel}>\n <StarIcon size={24} filled className=\"shrink-0 text-brand-primary-default\" />\n <span className=\"flex items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {averageLabel}\n </span>\n <span className=\"typography-body-small-14px-regular text-content-tertiary\">\n ({formatCount(totalCount)})\n </span>\n </span>\n </div>\n\n <ul className=\"m-0 flex list-none flex-col gap-4 p-0\" aria-label=\"Rating breakdown\">\n {rows.map(({ rating, count }) => (\n <li key={rating}>\n <div\n className=\"flex items-center gap-4\"\n role=\"img\"\n aria-label={\n formatRatingLabel\n ? formatRatingLabel(rating, count)\n : `${rating} ${rating === 1 ? \"star\" : \"stars\"}, ${formatCount(count)}`\n }\n >\n <div className=\"relative h-4 flex-1 overflow-hidden rounded-full bg-neutral-alphas-50\">\n <div\n className=\"absolute inset-y-0 left-0 rounded-full bg-brand-primary-default transition-[width] duration-300 ease-in-out\"\n style={{ width: maxCount === 0 ? \"0%\" : `${(count / maxCount) * 100}%` }}\n />\n </div>\n <span className=\"flex shrink-0 items-center gap-1.5\">\n <span className=\"typography-body-small-14px-semibold text-content-primary\">\n {rating}\n </span>\n <span className=\"typography-body-small-14px-regular text-content-tertiary\">\n ({formatCount(count)})\n </span>\n </span>\n </div>\n </li>\n ))}\n </ul>\n </div>\n );\n },\n);\n\nRatingSummary.displayName = \"RatingSummary\";\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn.mjs";
|
|
5
|
+
import { StarIcon } from "../Icons/StarIcon.mjs";
|
|
6
|
+
const ReviewCard = React.forwardRef(
|
|
7
|
+
({ rating, maxRating = 5, author, title, children, className, ...props }, ref) => {
|
|
8
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-2", className), ...props, children: [
|
|
9
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
10
|
+
/* @__PURE__ */ jsx(StarIcon, { size: 24, filled: true, className: "shrink-0 text-brand-primary-default" }),
|
|
11
|
+
/* @__PURE__ */ jsx(
|
|
12
|
+
"span",
|
|
13
|
+
{
|
|
14
|
+
className: "typography-body-small-14px-semibold text-content-primary",
|
|
15
|
+
role: "img",
|
|
16
|
+
"aria-label": `Rated ${rating} out of ${maxRating}`,
|
|
17
|
+
children: rating
|
|
18
|
+
}
|
|
19
|
+
),
|
|
20
|
+
author != null && /* @__PURE__ */ jsxs("span", { className: "typography-body-small-14px-regular text-content-tertiary", children: [
|
|
21
|
+
"by ",
|
|
22
|
+
author
|
|
23
|
+
] })
|
|
24
|
+
] }),
|
|
25
|
+
title != null && /* @__PURE__ */ jsx("p", { className: "typography-body-default-16px-semibold text-content-primary", children: title }),
|
|
26
|
+
children != null && /* @__PURE__ */ jsx("p", { className: "typography-body-small-14px-regular text-content-secondary", children })
|
|
27
|
+
] });
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
ReviewCard.displayName = "ReviewCard";
|
|
31
|
+
export {
|
|
32
|
+
ReviewCard
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=ReviewCard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewCard.mjs","sources":["../../../src/components/ReviewCard/ReviewCard.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { StarIcon } from \"../Icons/StarIcon\";\n\nexport interface ReviewCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Star rating the reviewer gave, shown beside the star icon. */\n rating: number;\n /** Highest possible rating, used only in the accessible label (e.g. \"4 out of 5\"). @default 5 */\n maxRating?: number;\n /** Reviewer name or handle, rendered after \"by\". Omitted when not provided. */\n author?: React.ReactNode;\n /** Short headline for the review. */\n title?: React.ReactNode;\n /** Review body text. */\n children?: React.ReactNode;\n}\n\n/**\n * A single written review: a star rating with an optional author byline, a\n * title, and the review body. Stack several with a {@link Divider} between them\n * to build a review list.\n *\n * @example\n * ```tsx\n * <ReviewCard rating={4} author=\"@jane_doe\" title=\"A great app to start!\">\n * Easily plan and organize your content. Streamline your scheduling so you can\n * focus on what you do best.\n * </ReviewCard>\n * ```\n */\nexport const ReviewCard = React.forwardRef<HTMLDivElement, ReviewCardProps>(\n ({ rating, maxRating = 5, author, title, children, className, ...props }, ref) => {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n <div className=\"flex items-center gap-1\">\n <StarIcon size={24} filled className=\"shrink-0 text-brand-primary-default\" />\n <span\n className=\"typography-body-small-14px-semibold text-content-primary\"\n role=\"img\"\n aria-label={`Rated ${rating} out of ${maxRating}`}\n >\n {rating}\n </span>\n {author != null && (\n <span className=\"typography-body-small-14px-regular text-content-tertiary\">\n by {author}\n </span>\n )}\n </div>\n {title != null && (\n <p className=\"typography-body-default-16px-semibold text-content-primary\">{title}</p>\n )}\n {children != null && (\n <p className=\"typography-body-small-14px-regular text-content-secondary\">{children}</p>\n )}\n </div>\n );\n },\n);\n\nReviewCard.displayName = \"ReviewCard\";\n"],"names":[],"mappings":";;;;;AA8BO,MAAM,aAAa,MAAM;AAAA,EAC9B,CAAC,EAAE,QAAQ,YAAY,GAAG,QAAQ,OAAO,UAAU,WAAW,GAAG,MAAA,GAAS,QAAQ;AAChF,WACE,qBAAC,SAAI,KAAU,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,OAClE,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,QAAA,oBAAC,YAAS,MAAM,IAAI,QAAM,MAAC,WAAU,uCAAsC;AAAA,QAC3E;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,cAAY,SAAS,MAAM,WAAW,SAAS;AAAA,YAE9C,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,UAAU,QACT,qBAAC,QAAA,EAAK,WAAU,4DAA2D,UAAA;AAAA,UAAA;AAAA,UACrE;AAAA,QAAA,EAAA,CACN;AAAA,MAAA,GAEJ;AAAA,MACC,SAAS,QACR,oBAAC,KAAA,EAAE,WAAU,8DAA8D,UAAA,OAAM;AAAA,MAElF,YAAY,QACX,oBAAC,KAAA,EAAE,WAAU,6DAA6D,SAAA,CAAS;AAAA,IAAA,GAEvF;AAAA,EAEJ;AACF;AAEA,WAAW,cAAc;"}
|
|
@@ -63,7 +63,7 @@ const Select = React.forwardRef(
|
|
|
63
63
|
"label",
|
|
64
64
|
{
|
|
65
65
|
htmlFor: triggerId,
|
|
66
|
-
className: "typography-description-12px-semibold
|
|
66
|
+
className: "typography-description-12px-semibold pb-2 text-content-primary",
|
|
67
67
|
children: label
|
|
68
68
|
}
|
|
69
69
|
),
|
|
@@ -78,12 +78,12 @@ const Select = React.forwardRef(
|
|
|
78
78
|
"aria-describedby": bottomText ? helperTextId : void 0,
|
|
79
79
|
"aria-invalid": error || void 0,
|
|
80
80
|
className: cn(
|
|
81
|
-
"flex w-full cursor-pointer items-center justify-between rounded-sm border bg-
|
|
81
|
+
"flex w-full cursor-pointer items-center justify-between rounded-sm border bg-inputs-inputs-primary outline-none motion-safe:transition-colors focus-visible:shadow-focus-ring",
|
|
82
82
|
TRIGGER_HEIGHT[size],
|
|
83
83
|
TRIGGER_PADDING_X[size],
|
|
84
84
|
TRIGGER_GAP[size],
|
|
85
85
|
TRIGGER_TYPOGRAPHY[size],
|
|
86
|
-
error ? "border-error-content" : "border-
|
|
86
|
+
error ? "border-error-content" : "border-border-primary",
|
|
87
87
|
!disabled && !error && "hover:border-neutral-alphas-400 data-[state=open]:border-neutral-alphas-400",
|
|
88
88
|
disabled && "cursor-not-allowed opacity-50"
|
|
89
89
|
),
|
|
@@ -97,7 +97,7 @@ const Select = React.forwardRef(
|
|
|
97
97
|
children: leftIcon
|
|
98
98
|
}
|
|
99
99
|
),
|
|
100
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left text-content-primary [&>[data-placeholder]]:text-content-
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left text-content-primary [&>[data-placeholder]]:text-content-tertiary", children: /* @__PURE__ */ jsx(SelectPrimitive.Value, { placeholder }) })
|
|
101
101
|
] }),
|
|
102
102
|
/* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDownIcon, {}) })
|
|
103
103
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.mjs","sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["import * as SelectPrimitive from \"@radix-ui/react-select\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"@/utils/floatingContentCollisionPadding\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\n\n/** Select field height in pixels. */\nexport type SelectSize = \"48\" | \"40\" | \"32\";\n\ntype SelectContextValue = {\n size: SelectSize;\n error: boolean;\n disabled?: boolean;\n};\n\nconst SelectContext = React.createContext<SelectContextValue>({\n size: \"48\",\n error: false,\n});\n\nconst TRIGGER_HEIGHT: Record<SelectSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst TRIGGER_PADDING_X: Record<SelectSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst TRIGGER_GAP: Record<SelectSize, string> = {\n \"48\": \"gap-3\",\n \"40\": \"gap-3\",\n \"32\": \"gap-2\",\n};\n\nconst TRIGGER_TYPOGRAPHY: Record<SelectSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nexport interface SelectProps extends Omit<SelectPrimitive.SelectProps, \"dir\"> {\n /** Label text displayed above the trigger. Also used as the accessible name. */\n label?: string;\n /** Accessible name applied directly to the trigger button when no visible `label` is provided. */\n \"aria-label\"?: string;\n /** ID of an external element that labels the trigger button. */\n \"aria-labelledby\"?: string;\n /** Helper text displayed below the trigger. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the select field in pixels. @default \"48\" */\n size?: SelectSize;\n /** Whether the field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the trigger. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Placeholder shown when no value is selected. */\n placeholder?: string;\n /** Icon element displayed at the left side of the trigger. */\n leftIcon?: React.ReactNode;\n /** Whether the field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n /** Wraps the `className` of the outermost container div. */\n className?: string;\n /** HTML `id` applied to the trigger button. Auto-generated if omitted. */\n id?: string;\n}\n\n/**\n * A select field with optional label, helper/error text, and an icon slot,\n * built on Radix UI Select for full accessibility and keyboard navigation.\n *\n * Pair with {@link SelectContent} and {@link SelectItem} to provide options.\n *\n * @example\n * ```tsx\n * <Select label=\"Country\" placeholder=\"Select a country\">\n * <SelectContent>\n * <SelectItem value=\"us\">United States</SelectItem>\n * <SelectItem value=\"uk\">United Kingdom</SelectItem>\n * </SelectContent>\n * </Select>\n * ```\n */\nexport const Select = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Trigger>,\n SelectProps\n>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n placeholder,\n leftIcon,\n fullWidth = false,\n className,\n id,\n disabled,\n children,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const triggerId = id ?? generatedId;\n const helperTextId = `${triggerId}-helper`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n return (\n <SelectContext.Provider value={{ size, error, disabled }}>\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={triggerId}\n className=\"typography-description-12px-semibold px-1 pt-1 pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <SelectPrimitive.Root disabled={disabled} {...props}>\n <SelectPrimitive.Trigger\n ref={ref}\n id={triggerId}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n \"flex w-full cursor-pointer items-center justify-between rounded-sm border bg-neutral-alphas-50 outline-none motion-safe:transition-colors focus-visible:shadow-focus-ring\",\n TRIGGER_HEIGHT[size],\n TRIGGER_PADDING_X[size],\n TRIGGER_GAP[size],\n TRIGGER_TYPOGRAPHY[size],\n error ? \"border-error-content\" : \"border-transparent\",\n !disabled &&\n !error &&\n \"hover:border-neutral-alphas-400 data-[state=open]:border-neutral-alphas-400\",\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <div className=\"flex min-w-0 items-center gap-2\">\n {leftIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\"\n data-testid=\"left-icon\"\n >\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate text-left text-content-primary [&>[data-placeholder]]:text-content-secondary [&>[data-placeholder]]:opacity-40\">\n <SelectPrimitive.Value placeholder={placeholder} />\n </span>\n </div>\n\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n\n {children}\n </SelectPrimitive.Root>\n\n {bottomText && (\n <p\n id={helperTextId}\n className={cn(\n \"typography-description-12px-regular px-2 pt-1 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {bottomText}\n </p>\n )}\n </div>\n </SelectContext.Provider>\n );\n },\n);\n\nSelect.displayName = \"Select\";\n\nexport interface SelectContentProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> {}\n\n/**\n * The dropdown panel rendered inside a portal. Place {@link SelectItem} elements\n * (and optionally {@link SelectGroup} / {@link SelectLabel}) as children.\n */\nexport const SelectContent = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Content>,\n SelectContentProps\n>(\n (\n {\n className,\n children,\n position = \"popper\",\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n style,\n ...props\n },\n ref,\n ) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n position={position}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"relative w-max min-w-(--radix-select-trigger-width) max-w-(--radix-select-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-background-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n className,\n )}\n {...props}\n >\n <SelectPrimitive.Viewport className=\"max-h-[var(--radix-select-content-available-height)] overflow-y-auto p-1\">\n {children}\n </SelectPrimitive.Viewport>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n ),\n);\n\nSelectContent.displayName = \"SelectContent\";\n\nexport interface SelectItemProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> {}\n\n/**\n * An individual option inside {@link SelectContent}.\n */\nexport const SelectItem = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Item>,\n SelectItemProps\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n \"typography-body-default-16px-regular relative flex w-full cursor-pointer select-none items-center gap-2 rounded-xs py-2 pr-2 pl-3 text-content-primary outline-none\",\n \"focus:bg-neutral-alphas-100 data-disabled:pointer-events-none data-disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator className=\"ml-auto flex size-4 shrink-0 items-center justify-center\">\n <CheckIcon className=\"size-4 text-content-primary\" />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n));\n\nSelectItem.displayName = \"SelectItem\";\n\n/** Props for {@link SelectGroup}. */\nexport type SelectGroupProps = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Group>;\n\n/**\n * Groups related {@link SelectItem} elements under a {@link SelectLabel}.\n */\nexport const SelectGroup = SelectPrimitive.Group;\nSelectGroup.displayName = \"SelectGroup\";\n\nexport interface SelectLabelProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> {}\n\n/**\n * A non-interactive label shown above a {@link SelectGroup}.\n */\nexport const SelectLabel = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Label>,\n SelectLabelProps\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn(\n \"typography-description-12px-semibold px-3 py-1.5 text-content-secondary\",\n className,\n )}\n {...props}\n />\n));\n\nSelectLabel.displayName = \"SelectLabel\";\n\nexport interface SelectSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> {}\n\n/** A horizontal rule that visually separates groups in {@link SelectContent}. */\nexport const SelectSeparator = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Separator>,\n SelectSeparatorProps\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\n\nSelectSeparator.displayName = \"SelectSeparator\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Select.mjs","sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["import * as SelectPrimitive from \"@radix-ui/react-select\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"@/utils/floatingContentCollisionPadding\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\n\n/** Select field height in pixels. */\nexport type SelectSize = \"48\" | \"40\" | \"32\";\n\ntype SelectContextValue = {\n size: SelectSize;\n error: boolean;\n disabled?: boolean;\n};\n\nconst SelectContext = React.createContext<SelectContextValue>({\n size: \"48\",\n error: false,\n});\n\nconst TRIGGER_HEIGHT: Record<SelectSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst TRIGGER_PADDING_X: Record<SelectSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst TRIGGER_GAP: Record<SelectSize, string> = {\n \"48\": \"gap-3\",\n \"40\": \"gap-3\",\n \"32\": \"gap-2\",\n};\n\nconst TRIGGER_TYPOGRAPHY: Record<SelectSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nexport interface SelectProps extends Omit<SelectPrimitive.SelectProps, \"dir\"> {\n /** Label text displayed above the trigger. Also used as the accessible name. */\n label?: string;\n /** Accessible name applied directly to the trigger button when no visible `label` is provided. */\n \"aria-label\"?: string;\n /** ID of an external element that labels the trigger button. */\n \"aria-labelledby\"?: string;\n /** Helper text displayed below the trigger. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the select field in pixels. @default \"48\" */\n size?: SelectSize;\n /** Whether the field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the trigger. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Placeholder shown when no value is selected. */\n placeholder?: string;\n /** Icon element displayed at the left side of the trigger. */\n leftIcon?: React.ReactNode;\n /** Whether the field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n /** Wraps the `className` of the outermost container div. */\n className?: string;\n /** HTML `id` applied to the trigger button. Auto-generated if omitted. */\n id?: string;\n}\n\n/**\n * A select field with optional label, helper/error text, and an icon slot,\n * built on Radix UI Select for full accessibility and keyboard navigation.\n *\n * Pair with {@link SelectContent} and {@link SelectItem} to provide options.\n *\n * @example\n * ```tsx\n * <Select label=\"Country\" placeholder=\"Select a country\">\n * <SelectContent>\n * <SelectItem value=\"us\">United States</SelectItem>\n * <SelectItem value=\"uk\">United Kingdom</SelectItem>\n * </SelectContent>\n * </Select>\n * ```\n */\nexport const Select = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Trigger>,\n SelectProps\n>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n placeholder,\n leftIcon,\n fullWidth = false,\n className,\n id,\n disabled,\n children,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const triggerId = id ?? generatedId;\n const helperTextId = `${triggerId}-helper`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n return (\n <SelectContext.Provider value={{ size, error, disabled }}>\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={triggerId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <SelectPrimitive.Root disabled={disabled} {...props}>\n <SelectPrimitive.Trigger\n ref={ref}\n id={triggerId}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n \"flex w-full cursor-pointer items-center justify-between rounded-sm border bg-inputs-inputs-primary outline-none motion-safe:transition-colors focus-visible:shadow-focus-ring\",\n TRIGGER_HEIGHT[size],\n TRIGGER_PADDING_X[size],\n TRIGGER_GAP[size],\n TRIGGER_TYPOGRAPHY[size],\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled &&\n !error &&\n \"hover:border-neutral-alphas-400 data-[state=open]:border-neutral-alphas-400\",\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <div className=\"flex min-w-0 items-center gap-2\">\n {leftIcon && (\n <span\n className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\"\n data-testid=\"left-icon\"\n >\n {leftIcon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate text-left text-content-primary [&>[data-placeholder]]:text-content-tertiary\">\n <SelectPrimitive.Value placeholder={placeholder} />\n </span>\n </div>\n\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n\n {children}\n </SelectPrimitive.Root>\n\n {bottomText && (\n <p\n id={helperTextId}\n className={cn(\n \"typography-description-12px-regular px-2 pt-1 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {bottomText}\n </p>\n )}\n </div>\n </SelectContext.Provider>\n );\n },\n);\n\nSelect.displayName = \"Select\";\n\nexport interface SelectContentProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> {}\n\n/**\n * The dropdown panel rendered inside a portal. Place {@link SelectItem} elements\n * (and optionally {@link SelectGroup} / {@link SelectLabel}) as children.\n */\nexport const SelectContent = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Content>,\n SelectContentProps\n>(\n (\n {\n className,\n children,\n position = \"popper\",\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n style,\n ...props\n },\n ref,\n ) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n position={position}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"relative w-max min-w-(--radix-select-trigger-width) max-w-(--radix-select-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-background-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n className,\n )}\n {...props}\n >\n <SelectPrimitive.Viewport className=\"max-h-[var(--radix-select-content-available-height)] overflow-y-auto p-1\">\n {children}\n </SelectPrimitive.Viewport>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n ),\n);\n\nSelectContent.displayName = \"SelectContent\";\n\nexport interface SelectItemProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> {}\n\n/**\n * An individual option inside {@link SelectContent}.\n */\nexport const SelectItem = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Item>,\n SelectItemProps\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n \"typography-body-default-16px-regular relative flex w-full cursor-pointer select-none items-center gap-2 rounded-xs py-2 pr-2 pl-3 text-content-primary outline-none\",\n \"focus:bg-neutral-alphas-100 data-disabled:pointer-events-none data-disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator className=\"ml-auto flex size-4 shrink-0 items-center justify-center\">\n <CheckIcon className=\"size-4 text-content-primary\" />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n));\n\nSelectItem.displayName = \"SelectItem\";\n\n/** Props for {@link SelectGroup}. */\nexport type SelectGroupProps = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Group>;\n\n/**\n * Groups related {@link SelectItem} elements under a {@link SelectLabel}.\n */\nexport const SelectGroup = SelectPrimitive.Group;\nSelectGroup.displayName = \"SelectGroup\";\n\nexport interface SelectLabelProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> {}\n\n/**\n * A non-interactive label shown above a {@link SelectGroup}.\n */\nexport const SelectLabel = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Label>,\n SelectLabelProps\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn(\n \"typography-description-12px-semibold px-3 py-1.5 text-content-secondary\",\n className,\n )}\n {...props}\n />\n));\n\nSelectLabel.displayName = \"SelectLabel\";\n\nexport interface SelectSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> {}\n\n/** A horizontal rule that visually separates groups in {@link SelectContent}. */\nexport const SelectSeparator = React.forwardRef<\n React.ComponentRef<typeof SelectPrimitive.Separator>,\n SelectSeparatorProps\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\n\nSelectSeparator.displayName = \"SelectSeparator\";\n"],"names":[],"mappings":"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"}
|
|
@@ -31,8 +31,8 @@ const ICON_INSET = {
|
|
|
31
31
|
};
|
|
32
32
|
function getContainerClassName(size, error, disabled) {
|
|
33
33
|
return cn(
|
|
34
|
-
"relative overflow-hidden rounded-sm border bg-
|
|
35
|
-
error ? "border-error-content" : "border-
|
|
34
|
+
"relative overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors",
|
|
35
|
+
error ? "border-error-content" : "border-border-primary",
|
|
36
36
|
!disabled && !error && "hover:border-neutral-alphas-400",
|
|
37
37
|
CONTAINER_HEIGHT[size],
|
|
38
38
|
disabled && "opacity-50"
|
|
@@ -40,7 +40,7 @@ function getContainerClassName(size, error, disabled) {
|
|
|
40
40
|
}
|
|
41
41
|
function getInputClassName(size, hasLeftIcon, hasRightIcon) {
|
|
42
42
|
return cn(
|
|
43
|
-
"h-full w-full rounded-sm bg-transparent text-content-primary no-underline placeholder:text-content-
|
|
43
|
+
"h-full w-full rounded-sm bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed",
|
|
44
44
|
INPUT_SIZE_CLASSES[size],
|
|
45
45
|
hasLeftIcon ? INPUT_PL[size].withIcon : INPUT_PL[size].default,
|
|
46
46
|
hasRightIcon ? INPUT_PR[size].withIcon : INPUT_PR[size].default
|
|
@@ -104,7 +104,7 @@ const TextField = React.forwardRef(
|
|
|
104
104
|
"label",
|
|
105
105
|
{
|
|
106
106
|
htmlFor: inputId,
|
|
107
|
-
className: "typography-description-12px-semibold
|
|
107
|
+
className: "typography-description-12px-semibold pb-2 text-content-primary",
|
|
108
108
|
children: label
|
|
109
109
|
}
|
|
110
110
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextField.mjs","sources":["../../../src/components/TextField/TextField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CheckOutlineIcon } from \"@/index\";\nimport { cn } from \"../../utils/cn\";\n\n/** Text field height in pixels. */\nexport type TextFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface TextFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the text field in pixels. @default \"48\" */\n size?: TextFieldSize;\n /** Whether the text field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Whether the text field is validated. @default false */\n validated?: boolean;\n /** Icon element displayed at the left side of the input. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed at the right side of the input. */\n rightIcon?: React.ReactNode;\n /** Whether the text field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<TextFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst INPUT_SIZE_CLASSES: Record<TextFieldSize, string> = {\n \"48\": \"py-3 typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"py-2 typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"py-2 typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst INPUT_PL: Record<TextFieldSize, { default: string; withIcon: string }> = {\n \"48\": { default: \"pl-4\", withIcon: \"pl-10\" },\n \"40\": { default: \"pl-4\", withIcon: \"pl-10\" },\n \"32\": { default: \"pl-3\", withIcon: \"pl-9\" },\n};\n\nconst INPUT_PR: Record<TextFieldSize, { default: string; withIcon: string }> = {\n \"48\": { default: \"pr-4\", withIcon: \"pr-10\" },\n \"40\": { default: \"pr-4\", withIcon: \"pr-10\" },\n \"32\": { default: \"pr-3\", withIcon: \"pr-9\" },\n};\n\nconst ICON_INSET: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nfunction getContainerClassName(size: TextFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative overflow-hidden rounded-sm border bg-neutral-alphas-50 has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getInputClassName(size: TextFieldSize, hasLeftIcon: boolean, hasRightIcon: boolean) {\n return cn(\n \"h-full w-full rounded-sm bg-transparent text-content-primary no-underline placeholder:text-content-secondary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n hasLeftIcon ? INPUT_PL[size].withIcon : INPUT_PL[size].default,\n hasRightIcon ? INPUT_PR[size].withIcon : INPUT_PR[size].default,\n );\n}\n\nfunction TextFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular px-2 pt-2 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {children}\n </p>\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"TextField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A text input field with optional label, helper/error text, and icon slots.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\n * />\n * ```\n */\nexport const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n validated = false,\n leftIcon,\n rightIcon,\n className,\n id,\n disabled,\n fullWidth = false,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold px-1 pt-1 pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n <input\n ref={ref}\n id={inputId}\n disabled={disabled}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n getInputClassName(size, !!leftIcon, !!(rightIcon || validated)),\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n {leftIcon && (\n <div\n className={cn(\n \"pointer-events-none absolute inset-y-0 left-0 flex items-center text-content-secondary\",\n ICON_INSET[size],\n )}\n >\n <div className=\"flex size-4 shrink-0 items-center justify-center\">{leftIcon}</div>\n </div>\n )}\n\n {(rightIcon || validated) && (\n <div\n className={cn(\n \"absolute inset-y-0 right-0 flex items-center gap-2 text-content-secondary\",\n ICON_INSET[size],\n )}\n >\n {rightIcon && (\n <div className=\"flex size-4 shrink-0 items-center justify-center\">{rightIcon}</div>\n )}\n {validated && (\n <div className=\"pointer-events-none flex size-4 shrink-0 items-center justify-center\">\n <CheckOutlineIcon className=\"text-success-content\" />\n </div>\n )}\n </div>\n )}\n </div>\n\n {bottomText && (\n <TextFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </TextFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nTextField.displayName = \"TextField\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TextField.mjs","sources":["../../../src/components/TextField/TextField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CheckOutlineIcon } from \"@/index\";\nimport { cn } from \"../../utils/cn\";\n\n/** Text field height in pixels. */\nexport type TextFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface TextFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the text field in pixels. @default \"48\" */\n size?: TextFieldSize;\n /** Whether the text field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Whether the text field is validated. @default false */\n validated?: boolean;\n /** Icon element displayed at the left side of the input. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed at the right side of the input. */\n rightIcon?: React.ReactNode;\n /** Whether the text field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<TextFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst INPUT_SIZE_CLASSES: Record<TextFieldSize, string> = {\n \"48\": \"py-3 typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"py-2 typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"py-2 typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst INPUT_PL: Record<TextFieldSize, { default: string; withIcon: string }> = {\n \"48\": { default: \"pl-4\", withIcon: \"pl-10\" },\n \"40\": { default: \"pl-4\", withIcon: \"pl-10\" },\n \"32\": { default: \"pl-3\", withIcon: \"pl-9\" },\n};\n\nconst INPUT_PR: Record<TextFieldSize, { default: string; withIcon: string }> = {\n \"48\": { default: \"pr-4\", withIcon: \"pr-10\" },\n \"40\": { default: \"pr-4\", withIcon: \"pr-10\" },\n \"32\": { default: \"pr-3\", withIcon: \"pr-9\" },\n};\n\nconst ICON_INSET: Record<TextFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nfunction getContainerClassName(size: TextFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n disabled && \"opacity-50\",\n );\n}\n\nfunction getInputClassName(size: TextFieldSize, hasLeftIcon: boolean, hasRightIcon: boolean) {\n return cn(\n \"h-full w-full rounded-sm bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n hasLeftIcon ? INPUT_PL[size].withIcon : INPUT_PL[size].default,\n hasRightIcon ? INPUT_PR[size].withIcon : INPUT_PR[size].default,\n );\n}\n\nfunction TextFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular px-2 pt-2 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {children}\n </p>\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"TextField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A text input field with optional label, helper/error text, and icon slots.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <TextField\n * label=\"Email\"\n * placeholder=\"you@example.com\"\n * error={!!emailError}\n * errorMessage={emailError}\n * />\n * ```\n */\nexport const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n validated = false,\n leftIcon,\n rightIcon,\n className,\n id,\n disabled,\n fullWidth = false,\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <div className={getContainerClassName(size, error, disabled)}>\n <input\n ref={ref}\n id={inputId}\n disabled={disabled}\n aria-describedby={bottomText ? helperTextId : undefined}\n aria-invalid={error || undefined}\n className={cn(\n getInputClassName(size, !!leftIcon, !!(rightIcon || validated)),\n \"[&[type='search']::-webkit-search-cancel-button]:hidden [&[type='search']::-webkit-search-cancel-button]:appearance-none\",\n )}\n {...props}\n />\n\n {leftIcon && (\n <div\n className={cn(\n \"pointer-events-none absolute inset-y-0 left-0 flex items-center text-content-secondary\",\n ICON_INSET[size],\n )}\n >\n <div className=\"flex size-4 shrink-0 items-center justify-center\">{leftIcon}</div>\n </div>\n )}\n\n {(rightIcon || validated) && (\n <div\n className={cn(\n \"absolute inset-y-0 right-0 flex items-center gap-2 text-content-secondary\",\n ICON_INSET[size],\n )}\n >\n {rightIcon && (\n <div className=\"flex size-4 shrink-0 items-center justify-center\">{rightIcon}</div>\n )}\n {validated && (\n <div className=\"pointer-events-none flex size-4 shrink-0 items-center justify-center\">\n <CheckOutlineIcon className=\"text-success-content\" />\n </div>\n )}\n </div>\n )}\n </div>\n\n {bottomText && (\n <TextFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </TextFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nTextField.displayName = \"TextField\";\n"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,6 @@ import * as React from "react";
|
|
|
5
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
6
|
import { Avatar } from "../Avatar/Avatar.mjs";
|
|
7
7
|
import { Button } from "../Button/Button.mjs";
|
|
8
|
-
import { IconButton } from "../IconButton/IconButton.mjs";
|
|
9
8
|
import { CloseIcon } from "../Icons/CloseIcon.mjs";
|
|
10
9
|
import { ErrorIcon } from "../Icons/ErrorIcon.mjs";
|
|
11
10
|
import { InfoIcon } from "../Icons/InfoIcon.mjs";
|
|
@@ -27,13 +26,13 @@ ToastViewport.displayName = "ToastViewport";
|
|
|
27
26
|
const VariantIcon = ({ variant }) => {
|
|
28
27
|
switch (variant) {
|
|
29
28
|
case "info":
|
|
30
|
-
return /* @__PURE__ */ jsx(InfoIcon, { className: "
|
|
29
|
+
return /* @__PURE__ */ jsx(InfoIcon, { size: 16, filled: true, className: "text-alerts-toast-icon-info" });
|
|
31
30
|
case "warning":
|
|
32
|
-
return /* @__PURE__ */ jsx(WarningIcon, { className: "
|
|
31
|
+
return /* @__PURE__ */ jsx(WarningIcon, { size: 16, filled: true, className: "text-alerts-toast-icon-warning" });
|
|
33
32
|
case "success":
|
|
34
|
-
return /* @__PURE__ */ jsx(SuccessIcon, { className: "size-
|
|
33
|
+
return /* @__PURE__ */ jsx(SuccessIcon, { className: "size-4 text-alerts-toast-icon-success" });
|
|
35
34
|
case "error":
|
|
36
|
-
return /* @__PURE__ */ jsx(ErrorIcon, { className: "size-
|
|
35
|
+
return /* @__PURE__ */ jsx(ErrorIcon, { className: "size-4 text-alerts-toast-icon-error" });
|
|
37
36
|
}
|
|
38
37
|
};
|
|
39
38
|
const Toast = React.forwardRef(
|
|
@@ -58,43 +57,30 @@ const Toast = React.forwardRef(
|
|
|
58
57
|
ref,
|
|
59
58
|
"data-testid": "toast",
|
|
60
59
|
className: cn(
|
|
61
|
-
|
|
62
|
-
"group pointer-events-auto relative flex w-full flex-col items-start gap-3 overflow-hidden rounded-xs border-none bg-surface-primary-inverted p-4 text-content-primary-inverted shadow-lg transition-all",
|
|
63
|
-
// Dark mode
|
|
64
|
-
"dark:border-opacity-100",
|
|
65
|
-
// Animation
|
|
60
|
+
"group pointer-events-auto relative flex w-full items-start gap-6 overflow-hidden rounded-md border-none bg-surface-primary-inverted py-3 pr-3 pl-4 text-content-primary-inverted shadow-md transition-all",
|
|
66
61
|
"data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-bottom-full data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[state=closed]:animate-out data-[state=open]:animate-in data-[swipe=end]:animate-out data-[swipe=move]:transition-none",
|
|
67
|
-
// Manual CSS overrides
|
|
68
62
|
className
|
|
69
63
|
),
|
|
70
64
|
...props,
|
|
71
65
|
children: [
|
|
72
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-
|
|
73
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
74
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-start", children: [
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
variant: "secondary",
|
|
82
|
-
className: "mt-4 border-content-primary-inverted text-content-primary-inverted",
|
|
83
|
-
size: "32",
|
|
84
|
-
onClick: onActionClick,
|
|
85
|
-
children: actionLabel ?? "Action"
|
|
86
|
-
}
|
|
87
|
-
)
|
|
66
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-3 py-1 pr-1", children: [
|
|
67
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center pt-px", children: variant === "messageToast" ? avatarSrc && /* @__PURE__ */ jsx(Avatar, { src: avatarSrc, alt: avatarAlt, fallback: avatarFallback }) : /* @__PURE__ */ jsx(VariantIcon, { variant }) }),
|
|
68
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-4", children: [
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-start gap-1", children: [
|
|
70
|
+
title && /* @__PURE__ */ jsx(ToastPrimitive.Title, { className: "typography-body-small-14px-semibold", children: title }),
|
|
71
|
+
description && /* @__PURE__ */ jsx(ToastPrimitive.Description, { className: "typography-body-small-14px-regular", children: description }),
|
|
72
|
+
children
|
|
73
|
+
] }),
|
|
74
|
+
onActionClick && /* @__PURE__ */ jsx(Button, { variant: "outline", negative: true, size: "32", onClick: onActionClick, children: actionLabel ?? "Action" })
|
|
88
75
|
] })
|
|
89
76
|
] }),
|
|
90
77
|
showClose && /* @__PURE__ */ jsx(ToastPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
91
|
-
|
|
78
|
+
"button",
|
|
92
79
|
{
|
|
93
|
-
|
|
80
|
+
type: "button",
|
|
94
81
|
"aria-label": closeLabel,
|
|
95
|
-
className: "
|
|
96
|
-
|
|
97
|
-
size: "24"
|
|
82
|
+
className: "flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-xs bg-buttons-secondary-negative-default text-content-primary-inverted transition-colors hover:bg-buttons-secondary-negative-hover focus-visible:shadow-focus-ring focus-visible:outline-none",
|
|
83
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { size: 16, "aria-hidden": "true" })
|
|
98
84
|
}
|
|
99
85
|
) })
|
|
100
86
|
]
|