@comitor/ui 0.3.3 → 0.5.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/README.md +12 -8
- package/dist/components/composite/avatar-group.d.ts +13 -1
- package/dist/components/composite/avatar-group.js +19 -4
- package/dist/components/composite/avatar-group.js.map +1 -1
- package/dist/components/composite/combobox.js +169 -146
- package/dist/components/composite/combobox.js.map +1 -1
- package/dist/components/composite/data-table.d.ts +24 -1
- package/dist/components/composite/data-table.js +7 -1
- package/dist/components/composite/data-table.js.map +1 -1
- package/dist/components/composite/date-picker.js +46 -17
- package/dist/components/composite/date-picker.js.map +1 -1
- package/dist/components/composite/form-section.js +1 -1
- package/dist/components/composite/form-section.js.map +1 -1
- package/dist/components/composite/route-tabs.d.ts +75 -0
- package/dist/components/composite/route-tabs.js +50 -0
- package/dist/components/composite/route-tabs.js.map +1 -0
- package/dist/components/composite/stat-card.js +14 -10
- package/dist/components/composite/stat-card.js.map +1 -1
- package/dist/components/ui/calendar.d.ts +29 -24
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/ui/checkbox.js +2 -2
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/command.js +1 -1
- package/dist/components/ui/command.js.map +1 -1
- package/dist/components/ui/context-menu.js +4 -4
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/dropdown-menu.js +4 -4
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/empty.js +1 -1
- package/dist/components/ui/empty.js.map +1 -1
- package/dist/components/ui/field.js +1 -1
- package/dist/components/ui/field.js.map +1 -1
- package/dist/components/ui/item.js +1 -1
- package/dist/components/ui/item.js.map +1 -1
- package/dist/components/ui/menubar.js +4 -4
- package/dist/components/ui/menubar.js.map +1 -1
- package/dist/components/ui/radio-group.js +1 -1
- package/dist/components/ui/radio-group.js.map +1 -1
- package/dist/components/ui/select.d.ts +35 -12
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/switch.js +2 -2
- package/dist/components/ui/switch.js.map +1 -1
- package/dist/components/ui/table.js +2 -2
- package/dist/components/ui/table.js.map +1 -1
- package/dist/components/ui/tabs-variants.d.ts +21 -0
- package/dist/components/ui/tabs-variants.js +15 -0
- package/dist/components/ui/tabs-variants.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +2 -10
- package/dist/components/ui/tabs.js +3 -14
- package/dist/components/ui/tabs.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/shell/app-header.js +47 -41
- package/dist/shell/app-header.js.map +1 -1
- package/dist/shell/mobile-menu.d.ts +6 -1
- package/dist/shell/mobile-menu.js +5 -5
- package/dist/shell/mobile-menu.js.map +1 -1
- package/dist/shell/notifications-button.js +31 -9
- package/dist/shell/notifications-button.js.map +1 -1
- package/dist/shell/sidebar.js +1 -1
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/types.d.ts +13 -0
- package/dist/shell/types.js +1 -0
- package/dist/shell/types.js.map +1 -1
- package/dist/shell/user-menu.d.ts +14 -2
- package/dist/shell/user-menu.js +3 -3
- package/dist/shell/user-menu.js.map +1 -1
- package/dist/shell/workspace-switcher.js +48 -34
- package/dist/shell/workspace-switcher.js.map +1 -1
- package/package.json +1 -1
- package/styles.css +95 -16
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
|
+
import { TabsVariant } from '../ui/tabs-variants.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tab điều hướng — mỗi mục là một ROUTE THẬT, không phải một panel trong cùng trang.
|
|
7
|
+
*
|
|
8
|
+
* ─ VÌ SAO KHÔNG DÙNG \`Tabs\` CHO VIỆC NÀY ─
|
|
9
|
+
* \`Tabs\` (Radix) dựng \`role="tablist"\` + \`role="tab"\` + \`aria-controls\` trỏ tới một
|
|
10
|
+
* \`role="tabpanel"\` CÙNG TRANG. Khi mỗi mục là một URL riêng thì:
|
|
11
|
+
* • không có tabpanel nào để trỏ tới — \`aria-controls\` thành id treo, một lỗi a11y thật;
|
|
12
|
+
* • \`role="tab"\` nói với trình đọc màn hình rằng bấm vào sẽ đổi panel tại chỗ, trong khi thực
|
|
13
|
+
* tế là điều hướng sang trang khác;
|
|
14
|
+
* • roving focus của tablist nuốt phím mũi tên, thứ mà một danh sách link không nên làm.
|
|
15
|
+
* Đây là \`<nav>\` chứa link thật, trạng thái đi bằng \`aria-current="page"\`.
|
|
16
|
+
*
|
|
17
|
+
* Nhìn thì KHÔNG khác \`Tabs\`: cả hai đọc chung một bảng lớp (\`ui/tabs-variants.ts\`), nên đổi
|
|
18
|
+
* kiểu tab ở một chỗ là cả hai đi theo.
|
|
19
|
+
*
|
|
20
|
+
* ─ LINK ─
|
|
21
|
+
* Mặc định render \`<a>\` (tải lại cả trang). Trên Next phải truyền \`LinkComponent={Link}\` —
|
|
22
|
+
* cùng quy ước với \`Breadcrumb\` và \`AppShell\`; gói cố ý không \`import "next/link"\` để còn chạy
|
|
23
|
+
* được ngoài Next.
|
|
24
|
+
*
|
|
25
|
+
* ```tsx
|
|
26
|
+
* "use client";
|
|
27
|
+
* const pathname = usePathname();
|
|
28
|
+
* <RouteTabs
|
|
29
|
+
* variant="pills"
|
|
30
|
+
* pathname={pathname}
|
|
31
|
+
* LinkComponent={Link}
|
|
32
|
+
* items={[
|
|
33
|
+
* { href: "/cai-dat", label: "Chung" },
|
|
34
|
+
* { href: "/cai-dat/thong-bao", label: "Thông báo" }
|
|
35
|
+
* ]}
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
/** Props tối thiểu mà component link phải nhận — \`next/link\` thoả sẵn. */
|
|
40
|
+
interface RouteTabsLinkProps {
|
|
41
|
+
href: string;
|
|
42
|
+
className?: string;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
"aria-current"?: "page" | undefined;
|
|
45
|
+
"data-state"?: "active" | "inactive";
|
|
46
|
+
}
|
|
47
|
+
type RouteTabsLinkComponent = ComponentType<RouteTabsLinkProps>;
|
|
48
|
+
interface RouteTabItem {
|
|
49
|
+
href: string;
|
|
50
|
+
label: ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* Mặc định khớp CHÍNH XÁC \`pathname === href\`.
|
|
53
|
+
*
|
|
54
|
+
* \`prefix\`: khớp cả route con (\`/cai-dat/thanh-vien/abc\` vẫn sáng ở \`/cai-dat/thanh-vien\`).
|
|
55
|
+
* ⚠ Mục có href là đường dẫn GỐC của nhóm (\`/cai-dat\`) mà để \`prefix\` thì nó sáng ở MỌI tab —
|
|
56
|
+
* đúng lỗi mà tab dạng \`?query\` từng mắc. Để mặc định cho mục gốc.
|
|
57
|
+
*/
|
|
58
|
+
match?: "exact" | "prefix";
|
|
59
|
+
}
|
|
60
|
+
interface RouteTabsProps {
|
|
61
|
+
items: RouteTabItem[];
|
|
62
|
+
/** Đường dẫn hiện tại — \`usePathname()\` trên Next. */
|
|
63
|
+
pathname: string;
|
|
64
|
+
variant?: TabsVariant;
|
|
65
|
+
/** Component link của app. Bỏ trống → \`<a>\` thô, tải lại cả trang. */
|
|
66
|
+
LinkComponent?: RouteTabsLinkComponent;
|
|
67
|
+
/** Nhãn của vùng điều hướng, cho trình đọc màn hình. */
|
|
68
|
+
label?: string;
|
|
69
|
+
className?: string;
|
|
70
|
+
/** Lớp thêm cho từng link. */
|
|
71
|
+
itemClassName?: string;
|
|
72
|
+
}
|
|
73
|
+
declare function RouteTabs({ items, pathname, variant, LinkComponent, label, className, itemClassName }: RouteTabsProps): React.JSX.Element;
|
|
74
|
+
|
|
75
|
+
export { type RouteTabItem, RouteTabs, type RouteTabsLinkComponent, type RouteTabsLinkProps, type RouteTabsProps };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
import { TABS_TRIGGER_BASE, tabsListVariants, tabsTriggerVariants } from "../ui/tabs-variants.js";
|
|
4
|
+
function DefaultLink({ href, className, children, ...rest }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("a", { href, className, ...rest, children });
|
|
6
|
+
}
|
|
7
|
+
function isItemActive(item, pathname) {
|
|
8
|
+
if (item.match === "prefix") return pathname === item.href || pathname.startsWith(`${item.href}/`);
|
|
9
|
+
return pathname === item.href;
|
|
10
|
+
}
|
|
11
|
+
function RouteTabs({
|
|
12
|
+
items,
|
|
13
|
+
pathname,
|
|
14
|
+
variant = "default",
|
|
15
|
+
LinkComponent = DefaultLink,
|
|
16
|
+
label = "M\u1EE5c c\u1EE7a trang",
|
|
17
|
+
className,
|
|
18
|
+
itemClassName
|
|
19
|
+
}) {
|
|
20
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": label, "data-slot": "route-tabs", children: /* @__PURE__ */ jsx(
|
|
21
|
+
"div",
|
|
22
|
+
{
|
|
23
|
+
"data-slot": "route-tabs-list",
|
|
24
|
+
className: cn("flex gap-0 overflow-x-auto", tabsListVariants[variant], className),
|
|
25
|
+
children: items.map((item) => {
|
|
26
|
+
const active = isItemActive(item, pathname);
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
LinkComponent,
|
|
29
|
+
{
|
|
30
|
+
href: item.href,
|
|
31
|
+
"data-state": active ? "active" : "inactive",
|
|
32
|
+
"aria-current": active ? "page" : void 0,
|
|
33
|
+
className: cn(
|
|
34
|
+
tabsTriggerVariants[variant],
|
|
35
|
+
TABS_TRIGGER_BASE,
|
|
36
|
+
"shrink-0 whitespace-nowrap text-center",
|
|
37
|
+
itemClassName
|
|
38
|
+
),
|
|
39
|
+
children: item.label
|
|
40
|
+
},
|
|
41
|
+
item.href
|
|
42
|
+
);
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
) });
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
RouteTabs
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=route-tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/route-tabs.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { TABS_TRIGGER_BASE, type TabsVariant, tabsListVariants, tabsTriggerVariants } from \"../ui/tabs-variants.js\";\n\n/**\n * Tab điều hướng — mỗi mục là một ROUTE THẬT, không phải một panel trong cùng trang.\n *\n * ─ VÌ SAO KHÔNG DÙNG \\`Tabs\\` CHO VIỆC NÀY ─\n * \\`Tabs\\` (Radix) dựng \\`role=\"tablist\"\\` + \\`role=\"tab\"\\` + \\`aria-controls\\` trỏ tới một\n * \\`role=\"tabpanel\"\\` CÙNG TRANG. Khi mỗi mục là một URL riêng thì:\n * • không có tabpanel nào để trỏ tới — \\`aria-controls\\` thành id treo, một lỗi a11y thật;\n * • \\`role=\"tab\"\\` nói với trình đọc màn hình rằng bấm vào sẽ đổi panel tại chỗ, trong khi thực\n * tế là điều hướng sang trang khác;\n * • roving focus của tablist nuốt phím mũi tên, thứ mà một danh sách link không nên làm.\n * Đây là \\`<nav>\\` chứa link thật, trạng thái đi bằng \\`aria-current=\"page\"\\`.\n *\n * Nhìn thì KHÔNG khác \\`Tabs\\`: cả hai đọc chung một bảng lớp (\\`ui/tabs-variants.ts\\`), nên đổi\n * kiểu tab ở một chỗ là cả hai đi theo.\n *\n * ─ LINK ─\n * Mặc định render \\`<a>\\` (tải lại cả trang). Trên Next phải truyền \\`LinkComponent={Link}\\` —\n * cùng quy ước với \\`Breadcrumb\\` và \\`AppShell\\`; gói cố ý không \\`import \"next/link\"\\` để còn chạy\n * được ngoài Next.\n *\n * ```tsx\n * \"use client\";\n * const pathname = usePathname();\n * <RouteTabs\n * variant=\"pills\"\n * pathname={pathname}\n * LinkComponent={Link}\n * items={[\n * { href: \"/cai-dat\", label: \"Chung\" },\n * { href: \"/cai-dat/thong-bao\", label: \"Thông báo\" }\n * ]}\n * />\n * ```\n */\n\n/** Props tối thiểu mà component link phải nhận — \\`next/link\\` thoả sẵn. */\nexport interface RouteTabsLinkProps {\n href: string;\n className?: string;\n children: ReactNode;\n \"aria-current\"?: \"page\" | undefined;\n \"data-state\"?: \"active\" | \"inactive\";\n}\n\nexport type RouteTabsLinkComponent = ComponentType<RouteTabsLinkProps>;\n\nexport interface RouteTabItem {\n href: string;\n label: ReactNode;\n /**\n * Mặc định khớp CHÍNH XÁC \\`pathname === href\\`.\n *\n * \\`prefix\\`: khớp cả route con (\\`/cai-dat/thanh-vien/abc\\` vẫn sáng ở \\`/cai-dat/thanh-vien\\`).\n * ⚠ Mục có href là đường dẫn GỐC của nhóm (\\`/cai-dat\\`) mà để \\`prefix\\` thì nó sáng ở MỌI tab —\n * đúng lỗi mà tab dạng \\`?query\\` từng mắc. Để mặc định cho mục gốc.\n */\n match?: \"exact\" | \"prefix\";\n}\n\nexport interface RouteTabsProps {\n items: RouteTabItem[];\n /** Đường dẫn hiện tại — \\`usePathname()\\` trên Next. */\n pathname: string;\n variant?: TabsVariant;\n /** Component link của app. Bỏ trống → \\`<a>\\` thô, tải lại cả trang. */\n LinkComponent?: RouteTabsLinkComponent;\n /** Nhãn của vùng điều hướng, cho trình đọc màn hình. */\n label?: string;\n className?: string;\n /** Lớp thêm cho từng link. */\n itemClassName?: string;\n}\n\nfunction DefaultLink({ href, className, children, ...rest }: RouteTabsLinkProps) {\n return (\n <a href={href} className={className} {...rest}>\n {children}\n </a>\n );\n}\n\nfunction isItemActive(item: RouteTabItem, pathname: string): boolean {\n if (item.match === \"prefix\") return pathname === item.href || pathname.startsWith(`${item.href}/`);\n return pathname === item.href;\n}\n\nexport function RouteTabs({\n items,\n pathname,\n variant = \"default\",\n LinkComponent = DefaultLink,\n label = \"Mục của trang\",\n className,\n itemClassName\n}: RouteTabsProps) {\n return (\n <nav aria-label={label} data-slot=\"route-tabs\">\n {/*\n `overflow-x-auto` để danh sách CUỘN NGANG khi hẹp, thay vì xuống dòng — nhãn tiếng Việt\n thường hai từ nên rất dễ vỡ dòng và làm pill cao gấp đôi. `whitespace-nowrap` ở từng mục\n là nửa còn lại của cùng một quyết định.\n */}\n <div\n data-slot=\"route-tabs-list\"\n className={cn(\"flex gap-0 overflow-x-auto\", tabsListVariants[variant], className)}\n >\n {items.map((item) => {\n const active = isItemActive(item, pathname);\n return (\n <LinkComponent\n key={item.href}\n href={item.href}\n // `data-state` để dùng lại nguyên bảng lớp của Tabs (chúng nhắm `data-[state=active]`).\n data-state={active ? \"active\" : \"inactive\"}\n aria-current={active ? \"page\" : undefined}\n className={cn(\n tabsTriggerVariants[variant],\n TABS_TRIGGER_BASE,\n \"shrink-0 whitespace-nowrap text-center\",\n itemClassName\n )}\n >\n {item.label}\n </LinkComponent>\n );\n })}\n </div>\n </nav>\n );\n}\n"],"mappings":"AA+EI;AA9EJ,SAAS,UAAU;AACnB,SAAS,mBAAqC,kBAAkB,2BAA2B;AA2E3F,SAAS,YAAY,EAAE,MAAM,WAAW,UAAU,GAAG,KAAK,GAAuB;AAC/E,SACE,oBAAC,OAAE,MAAY,WAAuB,GAAG,MACtC,UACH;AAEJ;AAEA,SAAS,aAAa,MAAoB,UAA2B;AACnE,MAAI,KAAK,UAAU,SAAU,QAAO,aAAa,KAAK,QAAQ,SAAS,WAAW,GAAG,KAAK,IAAI,GAAG;AACjG,SAAO,aAAa,KAAK;AAC3B;AAEO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR;AAAA,EACA;AACF,GAAmB;AACjB,SACE,oBAAC,SAAI,cAAY,OAAO,aAAU,cAMhC;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,8BAA8B,iBAAiB,OAAO,GAAG,SAAS;AAAA,MAE/E,gBAAM,IAAI,CAAC,SAAS;AACnB,cAAM,SAAS,aAAa,MAAM,QAAQ;AAC1C,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAM,KAAK;AAAA,YAEX,cAAY,SAAS,WAAW;AAAA,YAChC,gBAAc,SAAS,SAAS;AAAA,YAChC,WAAW;AAAA,cACT,oBAAoB,OAAO;AAAA,cAC3B;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,YAEC,eAAK;AAAA;AAAA,UAZD,KAAK;AAAA,QAaZ;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH,GACF;AAEJ;","names":[]}
|
|
@@ -3,7 +3,7 @@ import { cva } from "class-variance-authority";
|
|
|
3
3
|
import { ArrowDownRight, ArrowUpRight, Minus } from "lucide-react";
|
|
4
4
|
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import { Skeleton } from "../ui/skeleton.js";
|
|
6
|
-
const statCardIconVariants = cva("flex size-
|
|
6
|
+
const statCardIconVariants = cva("flex size-7 shrink-0 items-center justify-center rounded-lg", {
|
|
7
7
|
variants: {
|
|
8
8
|
tone: {
|
|
9
9
|
default: "bg-muted text-muted-foreground",
|
|
@@ -55,20 +55,24 @@ function StatCard({
|
|
|
55
55
|
"div",
|
|
56
56
|
{
|
|
57
57
|
"data-slot": "stat-card",
|
|
58
|
-
className: cn("rounded-xl border border-border bg-card p-
|
|
58
|
+
className: cn("rounded-xl border border-border bg-card p-5", className),
|
|
59
59
|
...props,
|
|
60
60
|
children: [
|
|
61
61
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
62
62
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
63
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-
|
|
64
|
-
loading ? /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-7 w-24" }) :
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/* @__PURE__ */ jsx("
|
|
68
|
-
|
|
69
|
-
] })
|
|
63
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs font-medium text-muted-foreground", children: label }),
|
|
64
|
+
loading ? /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-7 w-24" }) : (
|
|
65
|
+
/* `font-bold` theo bản duyệt. `tabular-nums` là khoản gói thêm và giữ lại: chữ số
|
|
66
|
+
đều bề ngang thì một hàng bốn thẻ không nhảy khi số đổi. */
|
|
67
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-2xl font-bold tabular-nums leading-tight text-foreground", children: value })
|
|
68
|
+
)
|
|
70
69
|
] }),
|
|
71
|
-
Icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: statCardIconVariants({ tone }), children: /* @__PURE__ */ jsx(Icon, { className: "size-
|
|
70
|
+
Icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: statCardIconVariants({ tone }), children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" }) })
|
|
71
|
+
] }),
|
|
72
|
+
delta && !loading && DeltaIcon && /* @__PURE__ */ jsxs("p", { className: cn("mt-1 flex flex-wrap items-center gap-x-1 text-xs font-medium", deltaToneClass(delta)), children: [
|
|
73
|
+
/* @__PURE__ */ jsx(DeltaIcon, { "aria-hidden": "true", className: "size-3.5 shrink-0" }),
|
|
74
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap tabular-nums", children: delta.value }),
|
|
75
|
+
delta.label && /* @__PURE__ */ jsx("span", { className: "font-normal text-muted-foreground", children: delta.label })
|
|
72
76
|
] }),
|
|
73
77
|
sparkline && /* @__PURE__ */ jsx("div", { className: "mt-3 h-10", children: sparkline }),
|
|
74
78
|
footer && /* @__PURE__ */ jsx("div", { className: "mt-3 border-t border-border pt-2 text-xs text-muted-foreground", children: footer })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/stat-card.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { ArrowDownRight, ArrowUpRight, Minus } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Skeleton } from \"../ui/skeleton.js\";\nimport type { IconComponent } from \"./empty-state.js\";\n\n/**\n * Ô icon của StatCard, tô theo tone token (không hex).\n *\n * Icon là thành phần PHI VĂN BẢN → ngưỡng WCAG 1.4.11 là 3:1 so với nền tint mà nó đứng lên.\n * Màu icon vì thế phải là bậc `--x-ink`, không phải token tô nền `--x`: `text-success` trên\n * `bg-success/15` chỉ được **2,69:1 ✗** ở light (dark thì 7,44:1 — mỗi theme hỏng một nửa là\n * dấu hiệu dùng sai vai trò token). Đo lại sau khi đổi, trên tint đã trộn với `--card`:\n *\n * success 5,60:1 (L) · 7,44:1 (D) · info 5,54:1 (L) · 7,12:1 (D)\n * warning 5,93:1 (L) · 7,19:1 (D) · destructive 5,76:1 (L) · 5,71:1 (D)\n * default 4,89:1 (L) · 4,86:1 (D) · primary 5,77:1 (L) · 7,26:1 (D)\n * app 6,14:1 (L) · 9,61:1 (D)\n *\n * `warning` bỏ cặp `text-gold-500 dark:text-warning`: `--warning-ink` đúng bằng `--gold-500` ở\n * light và `--yellow` ở dark, nên một class là đủ.\n */\nexport const statCardIconVariants = cva(\"flex size-
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/stat-card.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { ArrowDownRight, ArrowUpRight, Minus } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Skeleton } from \"../ui/skeleton.js\";\nimport type { IconComponent } from \"./empty-state.js\";\n\n/**\n * Ô icon của StatCard, tô theo tone token (không hex).\n *\n * Icon là thành phần PHI VĂN BẢN → ngưỡng WCAG 1.4.11 là 3:1 so với nền tint mà nó đứng lên.\n * Màu icon vì thế phải là bậc `--x-ink`, không phải token tô nền `--x`: `text-success` trên\n * `bg-success/15` chỉ được **2,69:1 ✗** ở light (dark thì 7,44:1 — mỗi theme hỏng một nửa là\n * dấu hiệu dùng sai vai trò token). Đo lại sau khi đổi, trên tint đã trộn với `--card`:\n *\n * success 5,60:1 (L) · 7,44:1 (D) · info 5,54:1 (L) · 7,12:1 (D)\n * warning 5,93:1 (L) · 7,19:1 (D) · destructive 5,76:1 (L) · 5,71:1 (D)\n * default 4,89:1 (L) · 4,86:1 (D) · primary 5,77:1 (L) · 7,26:1 (D)\n * app 6,14:1 (L) · 9,61:1 (D)\n *\n * `warning` bỏ cặp `text-gold-500 dark:text-warning`: `--warning-ink` đúng bằng `--gold-500` ở\n * light và `--yellow` ở dark, nên một class là đủ.\n */\n/*\n * Hình học của ô icon lấy theo mẫu StatCards ở mục **Patterns** của comitor-ds:\n * `p-1.5` quanh icon 14px + `rounded-lg` — tức ô ~28px, KHÔNG phải 36px với `rounded-md`.\n */\nexport const statCardIconVariants = cva(\"flex size-7 shrink-0 items-center justify-center rounded-lg\", {\n variants: {\n tone: {\n default: \"bg-muted text-muted-foreground\",\n // Đi theo `--primary`, không phải thang `gold-*` chôn cứng. Bản cũ\n // `bg-gold-100 text-gold-600 dark:bg-gold-500/25 dark:text-gold-100` đạt tương phản\n // (7,77:1 L · 11,85:1 D) nhưng đóng băng màu: app đổi brand thì ô icon vẫn vàng, và phải\n // viết hai lớp `dark:` vì thang gold không có bản dark. Token đạt 5,77:1 (L) · 7,26:1 (D).\n primary: \"bg-primary/20 text-primary-ink\",\n success: \"bg-success/15 text-success-ink\",\n warning: \"bg-warning/20 text-warning-ink\",\n destructive: \"bg-destructive/15 text-destructive-ink\",\n info: \"bg-info/15 text-info-ink\",\n // Icon app đứng trên tint của CHÍNH nó → phải là bậc ink, đúng như năm tone trên.\n // `text-app-accent` (gold #E8B824) trên `bg-app-accent/10` (trộn ra #FDF8E9) chỉ 1,75:1 ✗.\n // `text-foreground` như bản trước thì ĐỌC được (17,81:1) nhưng bỏ mất màu app — tức là bỏ\n // mất đúng thứ tone này sinh ra để nói. `text-app-accent-ink`: 6,14:1 (L) · 9,61:1 (D) ✓\n // — hợp đồng §5, cùng lối chữa với AppLauncher.\n app: \"bg-app-accent/10 text-app-accent-ink\"\n }\n },\n defaultVariants: {\n tone: \"default\"\n }\n});\n\nexport type StatCardTone = NonNullable<VariantProps<typeof statCardIconVariants>[\"tone\"]>;\n\nexport type StatCardDeltaDirection = \"up\" | \"down\" | \"flat\";\n\nexport interface StatCardDelta {\n /** Nội dung hiển thị, vd \"+12,5%\" hoặc \"3 việc\" */\n value: ReactNode;\n direction: StatCardDeltaDirection;\n /**\n * Tăng là TỐT hay XẤU — mặc định \"positive-up\" (tăng = tốt).\n *\n * Cần thiết vì có chỉ số ngược chiều: số ticket quá hạn tăng là xấu, tỷ lệ rời bỏ giảm là tốt.\n * Tô màu theo hướng mũi tên một cách máy móc sẽ nói dối người đọc.\n */\n polarity?: \"positive-up\" | \"positive-down\" | \"neutral\";\n /** Chú thích sau con số, vd \"so với tuần trước\" */\n label?: ReactNode;\n}\n\nexport interface StatCardProps extends Omit<ComponentProps<\"div\">, \"title\"> {\n /** Nhãn chỉ số, vd \"Việc đang mở\" */\n label: ReactNode;\n value: ReactNode;\n icon?: IconComponent;\n tone?: StatCardTone;\n delta?: StatCardDelta;\n /** Slot biểu đồ mini (sparkline) — package không ép thư viện biểu đồ nào */\n sparkline?: ReactNode;\n /** Dòng chú thích dưới cùng */\n footer?: ReactNode;\n /** Đang tải → khung xương thay cho số liệu */\n loading?: boolean;\n}\n\nconst deltaIcons: Record<StatCardDeltaDirection, IconComponent> = {\n up: ArrowUpRight,\n down: ArrowDownRight,\n flat: Minus\n};\n\n/**\n * Màu của delta = hướng × cực tính, không phải chỉ hướng.\n *\n * Dòng biến động là CHỮ (12px, `text-xs`) đứng trên `bg-card` → ngưỡng AA 4,5:1, và phải đạt ở\n * cả hai theme. Token tô nền không làm được việc đó: `text-success` chỉ 3,13:1 ở light còn\n * `text-destructive` chỉ 3,53:1 ở dark — mỗi theme hỏng một nửa. Bậc ink đạt cả hai:\n * success 6,52:1 (L) / 10,19:1 (D) · destructive 7,15:1 (L) / 6,42:1 (D) · muted-foreground\n * 5,23:1 (L) / 5,35:1 (D).\n */\nfunction deltaToneClass({ direction, polarity = \"positive-up\" }: StatCardDelta): string {\n if (direction === \"flat\" || polarity === \"neutral\") return \"text-muted-foreground\";\n const isGood = polarity === \"positive-up\" ? direction === \"up\" : direction === \"down\";\n return isGood ? \"text-success-ink\" : \"text-destructive-ink\";\n}\n\n/**\n * Thẻ KPI: nhãn + số liệu lớn + biến động + sparkline tuỳ chọn.\n *\n * Số liệu dùng `tabular-nums` để cột số không nhảy khi cập nhật realtime. Tĩnh → RSC-safe.\n */\nexport function StatCard({\n label,\n value,\n icon: Icon,\n tone = \"default\",\n delta,\n sparkline,\n footer,\n loading = false,\n className,\n ...props\n}: StatCardProps) {\n const DeltaIcon = delta ? deltaIcons[delta.direction] : undefined;\n\n return (\n <div\n data-slot=\"stat-card\"\n /*\n * `p-5`, và KHÔNG có đổ bóng — đúng mẫu StatCards ở mục Patterns của comitor-ds\n * (`rounded-xl border border-border bg-card p-5`). Bản trước dùng `p-4 shadow-card`; trên\n * một hàng bốn thẻ thì bóng đó nổi rõ và làm lưới thẻ nặng hơn bản duyệt.\n */\n className={cn(\"rounded-xl border border-border bg-card p-5\", className)}\n {...props}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n {/* `text-xs font-medium` theo bản duyệt, không phải `text-sm` thường. */}\n <p className=\"truncate text-xs font-medium text-muted-foreground\">{label}</p>\n {loading ? (\n <Skeleton className=\"mt-2 h-7 w-24\" />\n ) : (\n /* `font-bold` theo bản duyệt. `tabular-nums` là khoản gói thêm và giữ lại: chữ số\n đều bề ngang thì một hàng bốn thẻ không nhảy khi số đổi. */\n <p className=\"mt-1 text-2xl font-bold tabular-nums leading-tight text-foreground\">{value}</p>\n )}\n </div>\n {Icon && (\n <span aria-hidden=\"true\" className={statCardIconVariants({ tone })}>\n <Icon className=\"size-3.5\" />\n </span>\n )}\n </div>\n\n {/*\n * Dòng biến động nằm NGOÀI hàng hai cột ở trên, và đó là chủ ý.\n *\n * Ô icon chỉ cao 28px trong khi nhãn + số liệu đã cao ~46px, nên dòng này luôn rơi xuống\n * DƯỚI icon — nhưng hồi nó còn nằm trong cột trái thì nó vẫn bị bó theo bề rộng của cột đó\n * (147px trên thẻ 229px) và bỏ không phần dưới ô icon. Hệ quả nhìn thấy được: \"↗ +3 việc so\n * với tuần trước\" cần 171px nên nó vỡ đôi, mà vỡ theo kiểu tệ nhất — hai `<span>` là flex\n * item nên mỗi cái tự co về min-content rồi xuống dòng BÊN TRONG, ra thành hai cột lệch\n * \"+3 / việc\" và \"so với tuần / trước\". Đưa ra ngoài là dòng này được cả 189px, vừa một dòng.\n *\n * `flex-wrap` + `whitespace-nowrap` ở phần số là lưới an toàn cho thẻ hẹp hơn nữa: hết chỗ\n * thì chú thích xuống hàng NGUYÊN CỤM, không còn chuyện tách \"+3\" khỏi \"việc\".\n */}\n {delta && !loading && DeltaIcon && (\n <p className={cn(\"mt-1 flex flex-wrap items-center gap-x-1 text-xs font-medium\", deltaToneClass(delta))}>\n <DeltaIcon aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n <span className=\"whitespace-nowrap tabular-nums\">{delta.value}</span>\n {delta.label && <span className=\"font-normal text-muted-foreground\">{delta.label}</span>}\n </p>\n )}\n\n {sparkline && <div className=\"mt-3 h-10\">{sparkline}</div>}\n {footer && <div className=\"mt-3 border-t border-border pt-2 text-xs text-muted-foreground\">{footer}</div>}\n </div>\n );\n}\n"],"mappings":"AA2IQ,SAEE,KAFF;AA3IR,SAAS,WAA8B;AACvC,SAAS,gBAAgB,cAAc,aAAa;AAEpD,SAAS,UAAU;AACnB,SAAS,gBAAgB;AAuBlB,MAAM,uBAAuB,IAAI,+DAA+D;AAAA,EACrG,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMN,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA,IACf,MAAM;AAAA,EACR;AACF,CAAC;AAoCD,MAAM,aAA4D;AAAA,EAChE,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AACR;AAWA,SAAS,eAAe,EAAE,WAAW,WAAW,cAAc,GAA0B;AACtF,MAAI,cAAc,UAAU,aAAa,UAAW,QAAO;AAC3D,QAAM,SAAS,aAAa,gBAAgB,cAAc,OAAO,cAAc;AAC/E,SAAO,SAAS,qBAAqB;AACvC;AAOO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,YAAY,QAAQ,WAAW,MAAM,SAAS,IAAI;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MAMV,WAAW,GAAG,+CAA+C,SAAS;AAAA,MACrE,GAAG;AAAA,MAEJ;AAAA,6BAAC,SAAI,WAAU,0CACb;AAAA,+BAAC,SAAI,WAAU,kBAEb;AAAA,gCAAC,OAAE,WAAU,sDAAsD,iBAAM;AAAA,YACxE,UACC,oBAAC,YAAS,WAAU,iBAAgB;AAAA;AAAA;AAAA,cAIpC,oBAAC,OAAE,WAAU,sEAAsE,iBAAM;AAAA;AAAA,aAE7F;AAAA,UACC,QACC,oBAAC,UAAK,eAAY,QAAO,WAAW,qBAAqB,EAAE,KAAK,CAAC,GAC/D,8BAAC,QAAK,WAAU,YAAW,GAC7B;AAAA,WAEJ;AAAA,QAeC,SAAS,CAAC,WAAW,aACpB,qBAAC,OAAE,WAAW,GAAG,gEAAgE,eAAe,KAAK,CAAC,GACpG;AAAA,8BAAC,aAAU,eAAY,QAAO,WAAU,qBAAoB;AAAA,UAC5D,oBAAC,UAAK,WAAU,kCAAkC,gBAAM,OAAM;AAAA,UAC7D,MAAM,SAAS,oBAAC,UAAK,WAAU,qCAAqC,gBAAM,OAAM;AAAA,WACnF;AAAA,QAGD,aAAa,oBAAC,SAAI,WAAU,aAAa,qBAAU;AAAA,QACnD,UAAU,oBAAC,SAAI,WAAU,kEAAkE,kBAAO;AAAA;AAAA;AAAA,EACrG;AAEJ;","names":[]}
|
|
@@ -15,17 +15,24 @@ import 'class-variance-authority';
|
|
|
15
15
|
* (mặc định `vi-VN`) thay vì `"default"` — `"default"` phụ thuộc locale của môi
|
|
16
16
|
* trường chạy nên server và trình duyệt có thể ra chuỗi khác nhau → hydration mismatch.
|
|
17
17
|
*
|
|
18
|
-
* ─ Ô NGÀY ĐANG CHỌN: `bg-
|
|
19
|
-
* Chữ của ô ngày KHÔNG đổi màu khi được chọn (nghỉ và chọn đều là bậc mực), nên "đang chọn"
|
|
20
|
-
*
|
|
21
|
-
* nền trắng chỉ **1,86:1** ✗. Đổi sang bậc ink của brand thì đạt cả hai phía, cả hai theme:
|
|
18
|
+
* ─ Ô NGÀY ĐANG CHỌN: `bg-control-on` + `text-control-check` ─
|
|
19
|
+
* Chữ của ô ngày KHÔNG đổi màu khi được chọn (nghỉ và chọn đều là bậc mực), nên "đang chọn" chỉ
|
|
20
|
+
* còn được kể bằng NỀN ô — thông tin thị giác, ngưỡng 3:1.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Ô ngày đang chọn là một control ở trạng thái BẬT, y như ô Checkbox đã chọn, nên nó phải đi
|
|
23
|
+
* bằng CẶP token của họ control: nền `--control-on`, chữ `--control-check`. `--control-check`
|
|
24
|
+
* theo định nghĩa là "dấu đứng trên --control-on" — ghép nó với một nền khác là hai token trôi
|
|
25
|
+
* độc lập, và sẽ có ngày lệch nhau.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
27
|
+
* ⚠ Bản trước ghép `bg-primary-ink` + `text-control-check`. Ở MỌI bảng màu trừ một, hai token đó
|
|
28
|
+
* bằng nhau (`--primary-ink: var(--primary)` = `--control-on`) nên cặp sai vẫn cho ra đúng màu —
|
|
29
|
+
* lỗi ngủ yên. Đến khi bảng tương phản cao sáng riêng `--control-on` lên #B48700 mà `--primary-ink`
|
|
30
|
+
* ở lại gold-500 (nó còn phải làm CHỮ, ngưỡng 4,5:1) thì mới lộ: chữ mực trên #7A5800 = 2,52:1 ✗.
|
|
31
|
+
* Cổng tương phản bắt được; mắt thường thì không.
|
|
32
|
+
*
|
|
33
|
+
* nền ô ⇄ nền lịch : 1,86:1 (bảng duyệt — khoản đã chấp nhận, cùng nhóm với rãnh Switch BẬT)
|
|
34
|
+
* 3,28:1 (bảng tương phản cao) ✓
|
|
35
|
+
* chữ mực trên nền ô : 10,19:1 (bảng duyệt) · 5,77:1 (tương phản cao) ✓
|
|
29
36
|
*
|
|
30
37
|
* ⚠ CÒN TỒN, cố ý chưa động: `today` và `range_middle` dùng `bg-accent` — 1,09:1 (light) /
|
|
31
38
|
* 1,17:1 (dark) so với nền lịch. Đưa dải giữa lên 3:1 thì cả tuần biến thành một khối đặc,
|
|
@@ -41,26 +48,24 @@ declare function Calendar({ className, classNames, showOutsideDays, captionLayou
|
|
|
41
48
|
/**
|
|
42
49
|
* Ô ngày.
|
|
43
50
|
*
|
|
44
|
-
* ─ `[&>span]:opacity-
|
|
51
|
+
* ─ `[&>span]:opacity-90` — MỨC MỜ TỐI ĐA CÒN ĐẠT AA ─
|
|
45
52
|
* `<span>` là nội dung phụ app truyền vào ô ngày (số việc, chấm sự kiện…), cỡ `text-xs` = 12px
|
|
46
53
|
* nên chịu ngưỡng CHỮ 4,5:1. Nút ngày VẪN bấm được và vẫn nhận focus → `opacity-*` chồng lên nó
|
|
47
|
-
* KHÔNG được miễn trừ theo 1.4.3. Ca tệ nhất là ngày ĐÃ CHỌN ở theme sáng
|
|
48
|
-
*
|
|
54
|
+
* KHÔNG được miễn trừ theo 1.4.3. Ca tệ nhất là ngày ĐÃ CHỌN ở theme sáng của bảng TƯƠNG PHẢN
|
|
55
|
+
* CAO — chữ mực trên `bg-control-on` #B48700:
|
|
49
56
|
*
|
|
50
|
-
* opacity-
|
|
51
|
-
* opacity-
|
|
52
|
-
* opacity-
|
|
57
|
+
* opacity-80 → 4,43:1 ✗ ← mức cũ, KHÔNG còn đạt
|
|
58
|
+
* opacity-85 → 4,79:1 ✓
|
|
59
|
+
* opacity-90 → 5,15:1 ✓ ← đang dùng, còn biên cho lần chỉnh gold sau
|
|
53
60
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* Chính việc hạ nền xuống #7A5800 + chữ trắng đã làm ca tệ nhất chặt lại và kéo 70% xuống dưới
|
|
59
|
-
* ngưỡng. (Ở dark hai bản trùng số vì `--primary-ink: var(--primary)`.)
|
|
61
|
+
* ⚠ Bộ số cũ ở đây (70% → 4,12:1 · 80% → 4,85:1) đo trên MỘT CẶP KHÁC: nền #7A5800 với chữ
|
|
62
|
+
* TRẮNG. Từ khi bảng tương phản cao bỏ gold-500 (nâu-ô-liu, xem khối trên) và tô control bằng
|
|
63
|
+
* #B48700 thì chữ quay về MỰC, ca tệ nhất chặt lại và 80% rơi xuống dưới ngưỡng. Đây là kiểu lỗi
|
|
64
|
+
* mà một lần đổi token ở styles.css kéo theo, và chỉ cổng tương phản mới thấy.
|
|
60
65
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
66
|
+
* Hai bảng còn lại rộng rãi ở 90%: bảng duyệt (mực trên #E8B824) 8,60:1, dark (mực trên #F5D060)
|
|
67
|
+
* 10,43:1. Các trạng thái khác cũng thoải mái: giữa khoảng & hôm nay 9,70:1 (light) · 10,05:1
|
|
68
|
+
* (dark), ngày thường 10,26:1 · 11,41:1.
|
|
64
69
|
*/
|
|
65
70
|
declare function CalendarDayButton({ className, day, modifiers, ...props }: CalendarDayButtonProps): React.JSX.Element;
|
|
66
71
|
|
|
@@ -133,7 +133,7 @@ function CalendarDayButton({ className, day, modifiers, ...props }) {
|
|
|
133
133
|
"data-range-end": modifiers.range_end,
|
|
134
134
|
"data-range-middle": modifiers.range_middle,
|
|
135
135
|
className: cn(
|
|
136
|
-
"data-[selected-single=true]:bg-
|
|
136
|
+
"data-[selected-single=true]:bg-control-on data-[selected-single=true]:text-control-check data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-control-on data-[range-start=true]:text-control-check data-[range-end=true]:bg-control-on data-[range-end=true]:text-control-check group-data-[focused=true]/day:outline-none group-data-[focused=true]/day:ring-ring/50 group-data-[focused=true]/day:ring-offset-2 group-data-[focused=true]/day:ring-offset-background dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-2 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-90",
|
|
137
137
|
defaultClassNames.day,
|
|
138
138
|
className
|
|
139
139
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/calendar.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { type DayButton, DayPicker, getDefaultClassNames } from \"react-day-picker\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button, type ButtonProps, buttonVariants } from \"./button.js\";\n\n/**\n * Calendar (react-day-picker v9) — lịch trần, đã map class sang token Comitor.\n * Bản có ô nhập `dd/MM/yyyy` + preset (\"Hôm nay\", \"7 ngày qua\"…) là composite\n * `DatePicker` / `DateRangePicker` ở Tier 2.\n *\n * Ngôn ngữ: truyền `locale={vi}` (date-fns/locale) để đổi tên thứ/tháng.\n *\n * Khác bản comitor-ds: tên tháng rút gọn trong dropdown lấy theo `locale.code`\n * (mặc định `vi-VN`) thay vì `\"default\"` — `\"default\"` phụ thuộc locale của môi\n * trường chạy nên server và trình duyệt có thể ra chuỗi khác nhau → hydration mismatch.\n *\n * ─ Ô NGÀY ĐANG CHỌN: `bg-primary-ink` + `text-background` ─\n * Chữ của ô ngày KHÔNG đổi màu khi được chọn (nghỉ và chọn đều là bậc mực), nên \"đang chọn\"\n * chỉ còn được kể bằng NỀN ô — thông tin thị giác, ngưỡng 3:1. `bg-primary` gold #E8B824 trên\n * nền trắng chỉ **1,86:1** ✗. Đổi sang bậc ink của brand thì đạt cả hai phía, cả hai theme:\n *\n * nền ô ⇄ nền lịch : 6,51:1 (light) · 12,67:1 (dark) ✓\n * chữ `text-background` trên nền ô : 6,51:1 (light) · 12,67:1 (dark) ✓\n *\n * Chữ phải là `text-background` (trắng ở light) chứ KHÔNG `text-primary-foreground` (mực ở cả\n * hai theme): mực trên `--primary-ink` #7A5800 chỉ 1,86:1 ✗ — cùng lý do với dấu tick\n * `Checkbox` và núm `Switch`.\n *\n * ⚠ CÒN TỒN, cố ý chưa động: `today` và `range_middle` dùng `bg-accent` — 1,09:1 (light) /\n * 1,17:1 (dark) so với nền lịch. Đưa dải giữa lên 3:1 thì cả tuần biến thành một khối đặc,\n * nên đây là quyết định THIẾT KẾ (thêm viền/gạch chân cho \"hôm nay\"?) chứ không sửa được bằng\n * đổi token — để dành cho vòng riêng.\n */\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker> & {\n /** Biến thể Button dùng cho nút điều hướng tháng. */\n buttonVariant?: ButtonProps[\"variant\"];\n};\nexport type CalendarDayButtonProps = React.ComponentProps<typeof DayButton>;\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n formatters,\n components,\n locale,\n ...props\n}: CalendarProps) {\n const defaultClassNames = getDefaultClassNames();\n const localeCode = locale?.code ?? \"vi-VN\";\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n locale={locale}\n className={cn(\n \"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent\",\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className\n )}\n captionLayout={captionLayout}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(localeCode, { month: \"short\" }),\n ...formatters\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\"flex gap-4 flex-col md:flex-row relative\", defaultClassNames.months),\n month: cn(\"flex flex-col w-full gap-4\", defaultClassNames.month),\n nav: cn(\"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between\", defaultClassNames.nav),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n defaultClassNames.button_previous\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n defaultClassNames.button_next\n ),\n month_caption: cn(\n \"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)\",\n defaultClassNames.month_caption\n ),\n dropdowns: cn(\n \"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5\",\n defaultClassNames.dropdowns\n ),\n dropdown_root: cn(\n \"relative has-focus:outline-none border border-input shadow-xs has-focus:ring-2 has-focus:ring-ring/50 has-focus:ring-offset-2 has-focus:ring-offset-background rounded-md\",\n defaultClassNames.dropdown_root\n ),\n dropdown: cn(\"absolute bg-popover inset-0 opacity-0\", defaultClassNames.dropdown),\n caption_label: cn(\n \"select-none font-medium\",\n captionLayout === \"label\"\n ? \"text-sm\"\n : \"rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5\",\n defaultClassNames.caption_label\n ),\n table: \"w-full border-collapse\",\n weekdays: cn(\"flex\", defaultClassNames.weekdays),\n weekday: cn(\n \"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none\",\n defaultClassNames.weekday\n ),\n week: cn(\"flex w-full mt-2\", defaultClassNames.week),\n week_number_header: cn(\"select-none w-(--cell-size)\", defaultClassNames.week_number_header),\n week_number: cn(\"text-[0.8rem] select-none text-muted-foreground\", defaultClassNames.week_number),\n day: cn(\n \"relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none\",\n defaultClassNames.day\n ),\n range_start: cn(\"rounded-l-md bg-accent\", defaultClassNames.range_start),\n range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\n range_end: cn(\"rounded-r-md bg-accent\", defaultClassNames.range_end),\n today: cn(\n \"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none\",\n defaultClassNames.today\n ),\n outside: cn(\"text-muted-foreground aria-selected:text-muted-foreground\", defaultClassNames.outside),\n disabled: cn(\"text-muted-foreground opacity-50\", defaultClassNames.disabled),\n hidden: cn(\"invisible\", defaultClassNames.hidden),\n ...classNames\n }}\n components={{\n Root: ({ className: rootClassName, rootRef, ...rootProps }) => {\n return <div data-slot=\"calendar\" ref={rootRef} className={cn(rootClassName)} {...rootProps} />;\n },\n Chevron: ({ className: chevronClassName, orientation, ...chevronProps }) => {\n if (orientation === \"left\") {\n return <ChevronLeftIcon className={cn(\"size-4\", chevronClassName)} {...chevronProps} />;\n }\n\n if (orientation === \"right\") {\n return <ChevronRightIcon className={cn(\"size-4\", chevronClassName)} {...chevronProps} />;\n }\n\n return <ChevronDownIcon className={cn(\"size-4\", chevronClassName)} {...chevronProps} />;\n },\n DayButton: CalendarDayButton,\n WeekNumber: ({ children, week: _week, ...weekProps }) => {\n return (\n <td {...weekProps}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">{children}</div>\n </td>\n );\n },\n ...components\n }}\n {...props}\n />\n );\n}\n\n/**\n * Ô ngày.\n *\n * ─ `[&>span]:opacity-80` — 80% LÀ MỨC MỜ TỐI ĐA CÒN ĐẠT AA ─\n * `<span>` là nội dung phụ app truyền vào ô ngày (số việc, chấm sự kiện…), cỡ `text-xs` = 12px\n * nên chịu ngưỡng CHỮ 4,5:1. Nút ngày VẪN bấm được và vẫn nhận focus → `opacity-*` chồng lên nó\n * KHÔNG được miễn trừ theo 1.4.3. Ca tệ nhất là ngày ĐÃ CHỌN ở theme sáng (`text-background`\n * trắng trên `bg-primary-ink` #7A5800), đo theo từng mức mờ:\n *\n * opacity-70 → 4,12:1 (light) · 5,90:1 (dark) ✗ ← dưới ngưỡng ở light\n * opacity-75 → 4,46:1 · 6,82:1 ✗\n * opacity-80 → 4,85:1 · 7,97:1 ✓ ← đang dùng\n *\n * Ba con số light ở trên là của nền ô MỚI. Đừng quy chúng cho `0.1.0`: bản đó tô ô chọn bằng\n * `bg-primary` (gold #E8B824) với chữ `text-primary-foreground` (mực), nên `opacity-70` khi ấy\n * đo được 5,26:1 (light) · 5,90:1 (dark) — tức mức mờ 70% VẪN đạt. Khuyết tật thật của `0.1.0`\n * nằm ở chỗ khác: nền ô chọn ⇄ nền lịch chỉ 1,86:1 (light), tức 1.4.11 mới là điều bị vi phạm.\n * Chính việc hạ nền xuống #7A5800 + chữ trắng đã làm ca tệ nhất chặt lại và kéo 70% xuống dưới\n * ngưỡng. (Ở dark hai bản trùng số vì `--primary-ink: var(--primary)`.)\n *\n * Các trạng thái còn lại thoải mái ở 80%: giữa khoảng & hôm nay 9,70:1 (light) · 10,05:1 (dark),\n * ngày thường 10,26:1 · 11,41:1. Hạ dưới 80% là hỏng đúng một ca (chọn + theme sáng) — đúng kiểu\n * lỗi chỉ lộ ở một trạng thái mà ba vòng trước đều sót.\n */\nfunction CalendarDayButton({ className, day, modifiers, ...props }: CalendarDayButtonProps) {\n const defaultClassNames = getDefaultClassNames();\n\n const ref = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={ref}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString()}\n data-selected-single={\n modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle\n }\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n \"data-[selected-single=true]:bg-primary-ink data-[selected-single=true]:text-control-check data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary-ink data-[range-start=true]:text-control-check data-[range-end=true]:bg-primary-ink data-[range-end=true]:text-control-check group-data-[focused=true]/day:outline-none group-data-[focused=true]/day:ring-ring/50 group-data-[focused=true]/day:ring-offset-2 group-data-[focused=true]/day:ring-offset-background dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-2 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-80\",\n defaultClassNames.day,\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Calendar, CalendarDayButton };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/calendar.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { type DayButton, DayPicker, getDefaultClassNames } from \"react-day-picker\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button, type ButtonProps, buttonVariants } from \"./button.js\";\n\n/**\n * Calendar (react-day-picker v9) — lịch trần, đã map class sang token Comitor.\n * Bản có ô nhập `dd/MM/yyyy` + preset (\"Hôm nay\", \"7 ngày qua\"…) là composite\n * `DatePicker` / `DateRangePicker` ở Tier 2.\n *\n * Ngôn ngữ: truyền `locale={vi}` (date-fns/locale) để đổi tên thứ/tháng.\n *\n * Khác bản comitor-ds: tên tháng rút gọn trong dropdown lấy theo `locale.code`\n * (mặc định `vi-VN`) thay vì `\"default\"` — `\"default\"` phụ thuộc locale của môi\n * trường chạy nên server và trình duyệt có thể ra chuỗi khác nhau → hydration mismatch.\n *\n * ─ Ô NGÀY ĐANG CHỌN: `bg-control-on` + `text-control-check` ─\n * Chữ của ô ngày KHÔNG đổi màu khi được chọn (nghỉ và chọn đều là bậc mực), nên \"đang chọn\" chỉ\n * còn được kể bằng NỀN ô — thông tin thị giác, ngưỡng 3:1.\n *\n * Ô ngày đang chọn là một control ở trạng thái BẬT, y như ô Checkbox đã chọn, nên nó phải đi\n * bằng CẶP token của họ control: nền `--control-on`, chữ `--control-check`. `--control-check`\n * theo định nghĩa là \"dấu đứng trên --control-on\" — ghép nó với một nền khác là hai token trôi\n * độc lập, và sẽ có ngày lệch nhau.\n *\n * ⚠ Bản trước ghép `bg-primary-ink` + `text-control-check`. Ở MỌI bảng màu trừ một, hai token đó\n * bằng nhau (`--primary-ink: var(--primary)` = `--control-on`) nên cặp sai vẫn cho ra đúng màu —\n * lỗi ngủ yên. Đến khi bảng tương phản cao sáng riêng `--control-on` lên #B48700 mà `--primary-ink`\n * ở lại gold-500 (nó còn phải làm CHỮ, ngưỡng 4,5:1) thì mới lộ: chữ mực trên #7A5800 = 2,52:1 ✗.\n * Cổng tương phản bắt được; mắt thường thì không.\n *\n * nền ô ⇄ nền lịch : 1,86:1 (bảng duyệt — khoản đã chấp nhận, cùng nhóm với rãnh Switch BẬT)\n * 3,28:1 (bảng tương phản cao) ✓\n * chữ mực trên nền ô : 10,19:1 (bảng duyệt) · 5,77:1 (tương phản cao) ✓\n *\n * ⚠ CÒN TỒN, cố ý chưa động: `today` và `range_middle` dùng `bg-accent` — 1,09:1 (light) /\n * 1,17:1 (dark) so với nền lịch. Đưa dải giữa lên 3:1 thì cả tuần biến thành một khối đặc,\n * nên đây là quyết định THIẾT KẾ (thêm viền/gạch chân cho \"hôm nay\"?) chứ không sửa được bằng\n * đổi token — để dành cho vòng riêng.\n */\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker> & {\n /** Biến thể Button dùng cho nút điều hướng tháng. */\n buttonVariant?: ButtonProps[\"variant\"];\n};\nexport type CalendarDayButtonProps = React.ComponentProps<typeof DayButton>;\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n formatters,\n components,\n locale,\n ...props\n}: CalendarProps) {\n const defaultClassNames = getDefaultClassNames();\n const localeCode = locale?.code ?? \"vi-VN\";\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n locale={locale}\n className={cn(\n \"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent\",\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className\n )}\n captionLayout={captionLayout}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(localeCode, { month: \"short\" }),\n ...formatters\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\"flex gap-4 flex-col md:flex-row relative\", defaultClassNames.months),\n month: cn(\"flex flex-col w-full gap-4\", defaultClassNames.month),\n nav: cn(\"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between\", defaultClassNames.nav),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n defaultClassNames.button_previous\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n defaultClassNames.button_next\n ),\n month_caption: cn(\n \"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)\",\n defaultClassNames.month_caption\n ),\n dropdowns: cn(\n \"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5\",\n defaultClassNames.dropdowns\n ),\n dropdown_root: cn(\n \"relative has-focus:outline-none border border-input shadow-xs has-focus:ring-2 has-focus:ring-ring/50 has-focus:ring-offset-2 has-focus:ring-offset-background rounded-md\",\n defaultClassNames.dropdown_root\n ),\n dropdown: cn(\"absolute bg-popover inset-0 opacity-0\", defaultClassNames.dropdown),\n caption_label: cn(\n \"select-none font-medium\",\n captionLayout === \"label\"\n ? \"text-sm\"\n : \"rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5\",\n defaultClassNames.caption_label\n ),\n table: \"w-full border-collapse\",\n weekdays: cn(\"flex\", defaultClassNames.weekdays),\n weekday: cn(\n \"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none\",\n defaultClassNames.weekday\n ),\n week: cn(\"flex w-full mt-2\", defaultClassNames.week),\n week_number_header: cn(\"select-none w-(--cell-size)\", defaultClassNames.week_number_header),\n week_number: cn(\"text-[0.8rem] select-none text-muted-foreground\", defaultClassNames.week_number),\n day: cn(\n \"relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none\",\n defaultClassNames.day\n ),\n range_start: cn(\"rounded-l-md bg-accent\", defaultClassNames.range_start),\n range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\n range_end: cn(\"rounded-r-md bg-accent\", defaultClassNames.range_end),\n today: cn(\n \"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none\",\n defaultClassNames.today\n ),\n outside: cn(\"text-muted-foreground aria-selected:text-muted-foreground\", defaultClassNames.outside),\n disabled: cn(\"text-muted-foreground opacity-50\", defaultClassNames.disabled),\n hidden: cn(\"invisible\", defaultClassNames.hidden),\n ...classNames\n }}\n components={{\n Root: ({ className: rootClassName, rootRef, ...rootProps }) => {\n return <div data-slot=\"calendar\" ref={rootRef} className={cn(rootClassName)} {...rootProps} />;\n },\n Chevron: ({ className: chevronClassName, orientation, ...chevronProps }) => {\n if (orientation === \"left\") {\n return <ChevronLeftIcon className={cn(\"size-4\", chevronClassName)} {...chevronProps} />;\n }\n\n if (orientation === \"right\") {\n return <ChevronRightIcon className={cn(\"size-4\", chevronClassName)} {...chevronProps} />;\n }\n\n return <ChevronDownIcon className={cn(\"size-4\", chevronClassName)} {...chevronProps} />;\n },\n DayButton: CalendarDayButton,\n WeekNumber: ({ children, week: _week, ...weekProps }) => {\n return (\n <td {...weekProps}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">{children}</div>\n </td>\n );\n },\n ...components\n }}\n {...props}\n />\n );\n}\n\n/**\n * Ô ngày.\n *\n * ─ `[&>span]:opacity-90` — MỨC MỜ TỐI ĐA CÒN ĐẠT AA ─\n * `<span>` là nội dung phụ app truyền vào ô ngày (số việc, chấm sự kiện…), cỡ `text-xs` = 12px\n * nên chịu ngưỡng CHỮ 4,5:1. Nút ngày VẪN bấm được và vẫn nhận focus → `opacity-*` chồng lên nó\n * KHÔNG được miễn trừ theo 1.4.3. Ca tệ nhất là ngày ĐÃ CHỌN ở theme sáng của bảng TƯƠNG PHẢN\n * CAO — chữ mực trên `bg-control-on` #B48700:\n *\n * opacity-80 → 4,43:1 ✗ ← mức cũ, KHÔNG còn đạt\n * opacity-85 → 4,79:1 ✓\n * opacity-90 → 5,15:1 ✓ ← đang dùng, còn biên cho lần chỉnh gold sau\n *\n * ⚠ Bộ số cũ ở đây (70% → 4,12:1 · 80% → 4,85:1) đo trên MỘT CẶP KHÁC: nền #7A5800 với chữ\n * TRẮNG. Từ khi bảng tương phản cao bỏ gold-500 (nâu-ô-liu, xem khối trên) và tô control bằng\n * #B48700 thì chữ quay về MỰC, ca tệ nhất chặt lại và 80% rơi xuống dưới ngưỡng. Đây là kiểu lỗi\n * mà một lần đổi token ở styles.css kéo theo, và chỉ cổng tương phản mới thấy.\n *\n * Hai bảng còn lại rộng rãi ở 90%: bảng duyệt (mực trên #E8B824) 8,60:1, dark (mực trên #F5D060)\n * 10,43:1. Các trạng thái khác cũng thoải mái: giữa khoảng & hôm nay 9,70:1 (light) · 10,05:1\n * (dark), ngày thường 10,26:1 · 11,41:1.\n */\nfunction CalendarDayButton({ className, day, modifiers, ...props }: CalendarDayButtonProps) {\n const defaultClassNames = getDefaultClassNames();\n\n const ref = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={ref}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString()}\n data-selected-single={\n modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle\n }\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n \"data-[selected-single=true]:bg-control-on data-[selected-single=true]:text-control-check data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-control-on data-[range-start=true]:text-control-check data-[range-end=true]:bg-control-on data-[range-end=true]:text-control-check group-data-[focused=true]/day:outline-none group-data-[focused=true]/day:ring-ring/50 group-data-[focused=true]/day:ring-offset-2 group-data-[focused=true]/day:ring-offset-background dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-2 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-90\",\n defaultClassNames.day,\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Calendar, CalendarDayButton };\n"],"mappings":"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","names":[]}
|
|
@@ -14,12 +14,12 @@ function Checkbox({ className, label, description, indeterminate, checked, id, .
|
|
|
14
14
|
"data-slot": "checkbox",
|
|
15
15
|
checked: indeterminate ? "indeterminate" : checked,
|
|
16
16
|
className: cn(
|
|
17
|
-
"peer size-4 shrink-0 rounded border border-
|
|
17
|
+
"peer size-4 shrink-0 rounded border border-choice-edge bg-background",
|
|
18
18
|
"transition-colors duration-150 ease-out",
|
|
19
19
|
"data-[state=checked]:bg-control-on data-[state=checked]:border-control-on data-[state=checked]:text-control-check",
|
|
20
20
|
"data-[state=indeterminate]:bg-control-on data-[state=indeterminate]:border-control-on data-[state=indeterminate]:text-control-check",
|
|
21
21
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
22
|
-
"hover:border-
|
|
22
|
+
"hover:border-choice-edge-strong",
|
|
23
23
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
24
24
|
className
|
|
25
25
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/checkbox.tsx"],"sourcesContent":["\"use client\";\n\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { CheckIcon, MinusIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\nexport interface CheckboxProps extends React.ComponentProps<typeof CheckboxPrimitive.Root> {\n /** Nhãn đặt cạnh ô đánh dấu; có nhãn thì component tự bọc thêm lớp bố cục. */\n label?: string;\n /** Mô tả phụ dưới nhãn. */\n description?: string;\n /**\n * Đường tắt cho trạng thái \"chọn một phần\" — đè lên `checked`.\n * Tương đương `checked=\"indeterminate\"`; giữ lại cho tương thích ngược.\n */\n indeterminate?: boolean;\n}\n\n/*\n * Style port từ comitor-ds (đổi gray-* → ash-* theo namespace của package).\n *\n * ─ ICON BÁM THEO STATE THẬT, KHÔNG THEO PROP ─\n * Bản cũ chọn icon bằng `indeterminate ? <Minus/> : <Check/>`, tức chỉ nhìn PROP. Ai truyền\n * `checked=\"indeterminate\"` thẳng (Radix cho phép, và `composite/data-table.tsx` đang làm đúng\n * như vậy cho ô \"chọn tất cả\") thì nền chuyển sang trạng thái một phần nhưng icon vẫn là dấu\n * tick → mới chọn vài dòng mà đầu bảng trông y hệt đã chọn hết. Trường hợp không kiểm soát\n * (`defaultChecked=\"indeterminate\"`) còn sai cả sau khi người dùng bấm.\n * Nay cả hai icon cùng được render và bật/tắt bằng `data-state` của chính Radix Indicator\n * (Radix phát `data-state=\"checked\" | \"indeterminate\"` trên Indicator) → luôn khớp state thật,\n * kể cả ở nhánh không kiểm soát.\n *\n * ─ MÀU: ĐI QUA TOKEN VAI TRÒ, GIỐNG `Switch` ─\n *\n * Ô đánh dấu là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1) và phải nhận ra được ở cả hai\n * trạng thái: (1) viền/ô ⇄ nền trang, (2) dấu tick ⇄ nền ô.\n *\n * ô ĐÃ CHỌN `data-[state=checked]:bg-control-on` (comitor-ds: bg-gold-300)\n * dấu tick `data-[state=checked]:text-control-check` (comitor-ds: text-ink)\n * viền nghỉ `border-
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/checkbox.tsx"],"sourcesContent":["\"use client\";\n\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { CheckIcon, MinusIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\nexport interface CheckboxProps extends React.ComponentProps<typeof CheckboxPrimitive.Root> {\n /** Nhãn đặt cạnh ô đánh dấu; có nhãn thì component tự bọc thêm lớp bố cục. */\n label?: string;\n /** Mô tả phụ dưới nhãn. */\n description?: string;\n /**\n * Đường tắt cho trạng thái \"chọn một phần\" — đè lên `checked`.\n * Tương đương `checked=\"indeterminate\"`; giữ lại cho tương thích ngược.\n */\n indeterminate?: boolean;\n}\n\n/*\n * Style port từ comitor-ds (đổi gray-* → ash-* theo namespace của package).\n *\n * ─ ICON BÁM THEO STATE THẬT, KHÔNG THEO PROP ─\n * Bản cũ chọn icon bằng `indeterminate ? <Minus/> : <Check/>`, tức chỉ nhìn PROP. Ai truyền\n * `checked=\"indeterminate\"` thẳng (Radix cho phép, và `composite/data-table.tsx` đang làm đúng\n * như vậy cho ô \"chọn tất cả\") thì nền chuyển sang trạng thái một phần nhưng icon vẫn là dấu\n * tick → mới chọn vài dòng mà đầu bảng trông y hệt đã chọn hết. Trường hợp không kiểm soát\n * (`defaultChecked=\"indeterminate\"`) còn sai cả sau khi người dùng bấm.\n * Nay cả hai icon cùng được render và bật/tắt bằng `data-state` của chính Radix Indicator\n * (Radix phát `data-state=\"checked\" | \"indeterminate\"` trên Indicator) → luôn khớp state thật,\n * kể cả ở nhánh không kiểm soát.\n *\n * ─ MÀU: ĐI QUA TOKEN VAI TRÒ, GIỐNG `Switch` ─\n *\n * Ô đánh dấu là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1) và phải nhận ra được ở cả hai\n * trạng thái: (1) viền/ô ⇄ nền trang, (2) dấu tick ⇄ nền ô.\n *\n * ô ĐÃ CHỌN `data-[state=checked]:bg-control-on` (comitor-ds: bg-gold-300)\n * dấu tick `data-[state=checked]:text-control-check` (comitor-ds: text-ink)\n * viền nghỉ `border-choice-edge` · hover `border-choice-edge-strong`\n *\n * ⚠ Dấu tick dùng `--control-check`, KHÔNG phải `--control-knob` (núm Switch) và cũng không phải\n * `text-ink` cứng: hai bảng màu lật ngược nhau ở đúng chỗ này. Bảng mặc định tô nền ô bằng gold\n * SÁNG nên tick phải là MỰC (10,19:1); bảng tương phản cao tô gold-500 TỐI nên tick phải là màu\n * nền trang (6,51:1). Núm Switch thì trắng ở bảng duyệt nên nó ở lại `--control-knob` — đó là lý\n * do hai vai trò tách thành hai token.\n *\n * Số đo hai bảng (light):\n *\n * bảng MẶC ĐỊNH bảng TƯƠNG PHẢN CAO\n * ô ⇄ nền trang 1,86:1 ✗ (đã chấp nhận) 6,51:1 ✓\n * tick ⇄ nền ô 10,19:1 ✓ 6,51:1 ✓\n * viền nghỉ ⇄ nền 2,99:1 ✗ (sát ngưỡng) 3,83:1 ✓\n *\n * Hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. Ở ĐÂY không dùng được `--control-edge-strong`\n * (ash-300 = 1,62:1, nhạt hơn cả viền nghỉ 2,99:1) — `--choice-edge-strong` là ash-500, đạt\n * 3,83:1 (light) / 4,64:1 (dark). Viền ô NHẬP thì vẫn giữ cặp ash-300/ash-500 của comitor-ds vì\n * bậc nghỉ bên đó là 1,16:1.\n */\nfunction Checkbox({ className, label, description, indeterminate, checked, id, ...props }: CheckboxProps) {\n const generatedId = React.useId();\n const checkboxId = id ?? generatedId;\n\n const control = (\n <CheckboxPrimitive.Root\n id={checkboxId}\n data-slot=\"checkbox\"\n checked={indeterminate ? \"indeterminate\" : checked}\n className={cn(\n \"peer size-4 shrink-0 rounded border border-choice-edge bg-background\",\n \"transition-colors duration-150 ease-out\",\n \"data-[state=checked]:bg-control-on data-[state=checked]:border-control-on data-[state=checked]:text-control-check\",\n \"data-[state=indeterminate]:bg-control-on data-[state=indeterminate]:border-control-on data-[state=indeterminate]:text-control-check\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"hover:border-choice-edge-strong\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"group/checkbox-indicator flex items-center justify-center text-current transition-none\"\n >\n <MinusIcon\n className=\"hidden size-2.5 group-data-[state=indeterminate]/checkbox-indicator:block\"\n strokeWidth={3}\n />\n <CheckIcon className=\"hidden size-2.5 group-data-[state=checked]/checkbox-indicator:block\" strokeWidth={3} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n );\n\n if (!label && !description) {\n return control;\n }\n\n return (\n <div className=\"flex items-start gap-2.5\">\n {control}\n <div className=\"flex flex-col gap-0.5\">\n {label && (\n <label\n htmlFor={checkboxId}\n className=\"text-sm font-medium text-foreground cursor-pointer select-none leading-none\"\n >\n {label}\n </label>\n )}\n {description && <p className=\"text-xs text-muted-foreground leading-relaxed\">{description}</p>}\n </div>\n </div>\n );\n}\n\nexport { Checkbox };\n"],"mappings":";AAgFM,SAIE,KAJF;AA9EN,YAAY,uBAAuB;AACnC,SAAS,WAAW,iBAAiB;AACrC,YAAY,WAAW;AACvB,SAAS,UAAU;AAsDnB,SAAS,SAAS,EAAE,WAAW,OAAO,aAAa,eAAe,SAAS,IAAI,GAAG,MAAM,GAAkB;AACxG,QAAM,cAAc,MAAM,MAAM;AAChC,QAAM,aAAa,MAAM;AAEzB,QAAM,UACJ;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,IAAI;AAAA,MACJ,aAAU;AAAA,MACV,SAAS,gBAAgB,kBAAkB;AAAA,MAC3C,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,kBAAkB;AAAA,QAAlB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UAEV;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,aAAa;AAAA;AAAA,YACf;AAAA,YACA,oBAAC,aAAU,WAAU,uEAAsE,aAAa,GAAG;AAAA;AAAA;AAAA,MAC7G;AAAA;AAAA,EACF;AAGF,MAAI,CAAC,SAAS,CAAC,aAAa;AAC1B,WAAO;AAAA,EACT;AAEA,SACE,qBAAC,SAAI,WAAU,4BACZ;AAAA;AAAA,IACD,qBAAC,SAAI,WAAU,yBACZ;AAAA,eACC;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MAED,eAAe,oBAAC,OAAE,WAAU,iDAAiD,uBAAY;AAAA,OAC5F;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -91,7 +91,7 @@ function CommandItem({ className, ...props }) {
|
|
|
91
91
|
{
|
|
92
92
|
"data-slot": "command-item",
|
|
93
93
|
className: cn(
|
|
94
|
-
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
94
|
+
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 mt-1 first:mt-0 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
95
95
|
className
|
|
96
96
|
),
|
|
97
97
|
...props
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/command.tsx"],"sourcesContent":["\"use client\";\n\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog.js\";\n\n/**\n * Command — danh sách lệnh có lọc theo phím gõ (cmdk).\n *\n * Đây là primitive trần: composite `CommandPalette` và `Combobox` (Tier 2) dựng lên\n * từ đây. `CommandDialog` bọc sẵn trong `Dialog` cho bảng lệnh Cmd/Ctrl+K.\n *\n * Lưu ý a11y: `CommandDialog` bắt buộc có tiêu đề + mô tả cho trình đọc màn hình\n * (đặt trong `DialogHeader` ẩn `sr-only`) — đổi bằng prop `title`/`description`.\n */\n\nexport type CommandProps = React.ComponentProps<typeof CommandPrimitive>;\n\nfunction Command({ className, ...props }: CommandProps) {\n return (\n <CommandPrimitive\n data-slot=\"command\"\n className={cn(\n \"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type CommandDialogProps = React.ComponentProps<typeof Dialog> & {\n /** Tiêu đề cho trình đọc màn hình. Mặc định \"Bảng lệnh\". */\n title?: string;\n /** Mô tả cho trình đọc màn hình. Mặc định \"Tìm và chạy một lệnh…\". */\n description?: string;\n className?: string;\n showCloseButton?: boolean;\n};\n\nfunction CommandDialog({\n title = \"Bảng lệnh\",\n description = \"Tìm và chạy một lệnh…\",\n children,\n className,\n showCloseButton = true,\n ...props\n}: CommandDialogProps) {\n return (\n <Dialog {...props}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent className={cn(\"overflow-hidden p-0\", className)} showCloseButton={showCloseButton}>\n <Command className=\"[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport type CommandInputProps = React.ComponentProps<typeof CommandPrimitive.Input>;\n\nfunction CommandInput({ className, ...props }: CommandInputProps) {\n return (\n <div data-slot=\"command-input-wrapper\" className=\"flex h-9 items-center gap-2 border-b px-3\">\n {/* Icon kính lúp là icon MANG NGHĨA (nói \"đây là ô tìm kiếm\") → ngưỡng 3:1.\n `opacity-50` cũ hạ màu kế thừa xuống 3,56:1 (light) / 4,99:1 (dark) — đạt nhưng sát,\n và là con số không token nào bảo vệ. `--muted-foreground` là bậc dành đúng cho vai trò\n này: 5,23:1 (light) · 5,35:1 (dark) trên nền popover. */}\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n data-slot=\"command-input\"\n className={cn(\n \"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport type CommandListProps = React.ComponentProps<typeof CommandPrimitive.List>;\n\nfunction CommandList({ className, ...props }: CommandListProps) {\n return (\n <CommandPrimitive.List\n data-slot=\"command-list\"\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n {...props}\n />\n );\n}\n\nexport type CommandEmptyProps = React.ComponentProps<typeof CommandPrimitive.Empty>;\n\nfunction CommandEmpty({ ...props }: CommandEmptyProps) {\n return <CommandPrimitive.Empty data-slot=\"command-empty\" className=\"py-6 text-center text-sm\" {...props} />;\n}\n\nexport type CommandGroupProps = React.ComponentProps<typeof CommandPrimitive.Group>;\n\nfunction CommandGroup({ className, ...props }: CommandGroupProps) {\n return (\n <CommandPrimitive.Group\n data-slot=\"command-group\"\n className={cn(\n \"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type CommandSeparatorProps = React.ComponentProps<typeof CommandPrimitive.Separator>;\n\nfunction CommandSeparator({ className, ...props }: CommandSeparatorProps) {\n return (\n <CommandPrimitive.Separator\n data-slot=\"command-separator\"\n className={cn(\"bg-border -mx-1 h-px\", className)}\n {...props}\n />\n );\n}\n\nexport type CommandItemProps = React.ComponentProps<typeof CommandPrimitive.Item>;\n\nfunction CommandItem({ className, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n data-slot=\"command-item\"\n className={cn(\n \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type CommandShortcutProps = React.ComponentProps<\"span\">;\n\nfunction CommandShortcut({ className, ...props }: CommandShortcutProps) {\n return (\n <span\n data-slot=\"command-shortcut\"\n className={cn(\"text-muted-foreground ml-auto text-xs tracking-widest\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Command,\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut\n};\n"],"mappings":";AAsBI,cA8BE,YA9BF;AApBJ,SAAS,WAAW,wBAAwB;AAC5C,SAAS,kBAAkB;AAE3B,SAAS,UAAU;AACnB,SAAS,QAAQ,eAAe,mBAAmB,cAAc,mBAAmB;AAcpF,SAAS,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAiB;AACtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAWA,SAAS,cAAc;AAAA,EACrB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,GAAG;AACL,GAAuB;AACrB,SACE,qBAAC,UAAQ,GAAG,OACV;AAAA,yBAAC,gBAAa,WAAU,WACtB;AAAA,0BAAC,eAAa,iBAAM;AAAA,MACpB,oBAAC,qBAAmB,uBAAY;AAAA,OAClC;AAAA,IACA,oBAAC,iBAAc,WAAW,GAAG,uBAAuB,SAAS,GAAG,iBAC9D,8BAAC,WAAQ,WAAU,yZAChB,UACH,GACF;AAAA,KACF;AAEJ;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE,qBAAC,SAAI,aAAU,yBAAwB,WAAU,6CAK/C;AAAA,wBAAC,cAAW,WAAU,8BAA6B;AAAA,IACnD;AAAA,MAAC,iBAAiB;AAAA,MAAjB;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA;AAAA,IACN;AAAA,KACF;AAEJ;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,+DAA+D,SAAS;AAAA,MACrF,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,aAAa,EAAE,GAAG,MAAM,GAAsB;AACrD,SAAO,oBAAC,iBAAiB,OAAjB,EAAuB,aAAU,iBAAgB,WAAU,4BAA4B,GAAG,OAAO;AAC3G;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wBAAwB,SAAS;AAAA,MAC9C,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,gBAAgB,EAAE,WAAW,GAAG,MAAM,GAAyB;AACtE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,yDAAyD,SAAS;AAAA,MAC/E,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/command.tsx"],"sourcesContent":["\"use client\";\n\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog.js\";\n\n/**\n * Command — danh sách lệnh có lọc theo phím gõ (cmdk).\n *\n * Đây là primitive trần: composite `CommandPalette` và `Combobox` (Tier 2) dựng lên\n * từ đây. `CommandDialog` bọc sẵn trong `Dialog` cho bảng lệnh Cmd/Ctrl+K.\n *\n * Lưu ý a11y: `CommandDialog` bắt buộc có tiêu đề + mô tả cho trình đọc màn hình\n * (đặt trong `DialogHeader` ẩn `sr-only`) — đổi bằng prop `title`/`description`.\n */\n\nexport type CommandProps = React.ComponentProps<typeof CommandPrimitive>;\n\nfunction Command({ className, ...props }: CommandProps) {\n return (\n <CommandPrimitive\n data-slot=\"command\"\n className={cn(\n \"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type CommandDialogProps = React.ComponentProps<typeof Dialog> & {\n /** Tiêu đề cho trình đọc màn hình. Mặc định \"Bảng lệnh\". */\n title?: string;\n /** Mô tả cho trình đọc màn hình. Mặc định \"Tìm và chạy một lệnh…\". */\n description?: string;\n className?: string;\n showCloseButton?: boolean;\n};\n\nfunction CommandDialog({\n title = \"Bảng lệnh\",\n description = \"Tìm và chạy một lệnh…\",\n children,\n className,\n showCloseButton = true,\n ...props\n}: CommandDialogProps) {\n return (\n <Dialog {...props}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent className={cn(\"overflow-hidden p-0\", className)} showCloseButton={showCloseButton}>\n <Command className=\"[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport type CommandInputProps = React.ComponentProps<typeof CommandPrimitive.Input>;\n\nfunction CommandInput({ className, ...props }: CommandInputProps) {\n return (\n <div data-slot=\"command-input-wrapper\" className=\"flex h-9 items-center gap-2 border-b px-3\">\n {/* Icon kính lúp là icon MANG NGHĨA (nói \"đây là ô tìm kiếm\") → ngưỡng 3:1.\n `opacity-50` cũ hạ màu kế thừa xuống 3,56:1 (light) / 4,99:1 (dark) — đạt nhưng sát,\n và là con số không token nào bảo vệ. `--muted-foreground` là bậc dành đúng cho vai trò\n này: 5,23:1 (light) · 5,35:1 (dark) trên nền popover. */}\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n data-slot=\"command-input\"\n className={cn(\n \"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport type CommandListProps = React.ComponentProps<typeof CommandPrimitive.List>;\n\nfunction CommandList({ className, ...props }: CommandListProps) {\n return (\n <CommandPrimitive.List\n data-slot=\"command-list\"\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n {...props}\n />\n );\n}\n\nexport type CommandEmptyProps = React.ComponentProps<typeof CommandPrimitive.Empty>;\n\nfunction CommandEmpty({ ...props }: CommandEmptyProps) {\n return <CommandPrimitive.Empty data-slot=\"command-empty\" className=\"py-6 text-center text-sm\" {...props} />;\n}\n\nexport type CommandGroupProps = React.ComponentProps<typeof CommandPrimitive.Group>;\n\nfunction CommandGroup({ className, ...props }: CommandGroupProps) {\n return (\n <CommandPrimitive.Group\n data-slot=\"command-group\"\n className={cn(\n \"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type CommandSeparatorProps = React.ComponentProps<typeof CommandPrimitive.Separator>;\n\nfunction CommandSeparator({ className, ...props }: CommandSeparatorProps) {\n return (\n <CommandPrimitive.Separator\n data-slot=\"command-separator\"\n className={cn(\"bg-border -mx-1 h-px\", className)}\n {...props}\n />\n );\n}\n\nexport type CommandItemProps = React.ComponentProps<typeof CommandPrimitive.Item>;\n\nfunction CommandItem({ className, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n data-slot=\"command-item\"\n className={cn(\n \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 mt-1 first:mt-0 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type CommandShortcutProps = React.ComponentProps<\"span\">;\n\nfunction CommandShortcut({ className, ...props }: CommandShortcutProps) {\n return (\n <span\n data-slot=\"command-shortcut\"\n className={cn(\"text-muted-foreground ml-auto text-xs tracking-widest\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Command,\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut\n};\n"],"mappings":";AAsBI,cA8BE,YA9BF;AApBJ,SAAS,WAAW,wBAAwB;AAC5C,SAAS,kBAAkB;AAE3B,SAAS,UAAU;AACnB,SAAS,QAAQ,eAAe,mBAAmB,cAAc,mBAAmB;AAcpF,SAAS,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAiB;AACtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAWA,SAAS,cAAc;AAAA,EACrB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,GAAG;AACL,GAAuB;AACrB,SACE,qBAAC,UAAQ,GAAG,OACV;AAAA,yBAAC,gBAAa,WAAU,WACtB;AAAA,0BAAC,eAAa,iBAAM;AAAA,MACpB,oBAAC,qBAAmB,uBAAY;AAAA,OAClC;AAAA,IACA,oBAAC,iBAAc,WAAW,GAAG,uBAAuB,SAAS,GAAG,iBAC9D,8BAAC,WAAQ,WAAU,yZAChB,UACH,GACF;AAAA,KACF;AAEJ;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE,qBAAC,SAAI,aAAU,yBAAwB,WAAU,6CAK/C;AAAA,wBAAC,cAAW,WAAU,8BAA6B;AAAA,IACnD;AAAA,MAAC,iBAAiB;AAAA,MAAjB;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA;AAAA,IACN;AAAA,KACF;AAEJ;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,+DAA+D,SAAS;AAAA,MACrF,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,aAAa,EAAE,GAAG,MAAM,GAAsB;AACrD,SAAO,oBAAC,iBAAiB,OAAjB,EAAuB,aAAU,iBAAgB,WAAU,4BAA4B,GAAG,OAAO;AAC3G;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wBAAwB,SAAS;AAAA,MAC9C,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,gBAAgB,EAAE,WAAW,GAAG,MAAM,GAAyB;AACtE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,yDAAyD,SAAS;AAAA,MAC/E,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -28,7 +28,7 @@ function ContextMenuSubTrigger({ className, inset, children, ...props }) {
|
|
|
28
28
|
"data-slot": "context-menu-sub-trigger",
|
|
29
29
|
"data-inset": inset,
|
|
30
30
|
className: cn(
|
|
31
|
-
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
31
|
+
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center mt-1 first:mt-0 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
32
32
|
className
|
|
33
33
|
),
|
|
34
34
|
...props,
|
|
@@ -73,7 +73,7 @@ function ContextMenuItem({ className, inset, variant = "default", ...props }) {
|
|
|
73
73
|
"data-inset": inset,
|
|
74
74
|
"data-variant": variant,
|
|
75
75
|
className: cn(
|
|
76
|
-
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-ink data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive-ink data-[variant=destructive]:*:[svg]:!text-destructive-ink [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
76
|
+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-ink data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive-ink data-[variant=destructive]:*:[svg]:!text-destructive-ink [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 mt-1 first:mt-0 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
77
77
|
className
|
|
78
78
|
),
|
|
79
79
|
...props
|
|
@@ -86,7 +86,7 @@ function ContextMenuCheckboxItem({ className, children, checked, ...props }) {
|
|
|
86
86
|
{
|
|
87
87
|
"data-slot": "context-menu-checkbox-item",
|
|
88
88
|
className: cn(
|
|
89
|
-
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
89
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 mt-1 first:mt-0 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
90
90
|
className
|
|
91
91
|
),
|
|
92
92
|
checked,
|
|
@@ -104,7 +104,7 @@ function ContextMenuRadioItem({ className, children, ...props }) {
|
|
|
104
104
|
{
|
|
105
105
|
"data-slot": "context-menu-radio-item",
|
|
106
106
|
className: cn(
|
|
107
|
-
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
107
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 mt-1 first:mt-0 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
108
108
|
className
|
|
109
109
|
),
|
|
110
110
|
...props,
|