@comitor/ui 0.9.0 → 0.9.1
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/shell/app-launcher.js +42 -5
- package/dist/shell/app-launcher.js.map +1 -1
- package/package.json +14 -15
|
@@ -62,10 +62,47 @@ function AppLauncherPanel({ variant = "grid", className, onSelected }) {
|
|
|
62
62
|
]
|
|
63
63
|
}
|
|
64
64
|
),
|
|
65
|
-
/* @__PURE__ */ jsxs("span", { className: cn("flex min-w-0 flex-col", variant === "
|
|
66
|
-
/* @__PURE__ */ jsxs("span", { className: "flex
|
|
67
|
-
/* @__PURE__ */ jsx(
|
|
68
|
-
|
|
65
|
+
/* @__PURE__ */ jsxs("span", { className: cn("flex min-w-0 flex-col", variant === "grid" ? "w-full" : "flex-1"), children: [
|
|
66
|
+
/* @__PURE__ */ jsxs("span", { className: cn("flex min-w-0 gap-1.5", variant === "grid" ? "items-start" : "items-center"), children: [
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
"span",
|
|
69
|
+
{
|
|
70
|
+
className: cn(
|
|
71
|
+
"text-sm font-medium text-foreground",
|
|
72
|
+
/*
|
|
73
|
+
* Lưới: tên ứng dụng dài hơn ô là chuyện thường ("Tài khoản Comitor" ở ô 109px),
|
|
74
|
+
* nên cho hai dòng rồi mới cắt — `grid-cols-3` khai `items-stretch` mặc định nên
|
|
75
|
+
* ô cao thêm một dòng không làm hàng bị so le.
|
|
76
|
+
*
|
|
77
|
+
* `break-words` đi KÈM `line-clamp-2`, không phải phòng xa: `-webkit-line-clamp`
|
|
78
|
+
* chỉ đặt được dấu "…" khi chữ THẬT SỰ tràn sang dòng thứ ba. Một tên không có
|
|
79
|
+
* khoảng trắng thì không xuống dòng được, nên nó nằm gọn một dòng và bị
|
|
80
|
+
* `overflow: hidden` cắt PHẲNG giữa chữ — đo được: `soDong: 1`,
|
|
81
|
+
* `scrollHeight === clientHeight`, không có dấu ba chấm nào. Cho phép ngắt trong
|
|
82
|
+
* từ thì nó thành hai dòng và dấu "…" quay lại.
|
|
83
|
+
*
|
|
84
|
+
* Danh sách: mỗi mục là một hàng ngang trong popover hẹp; hai dòng ở đó làm chiều
|
|
85
|
+
* cao các mục nhấp nhô. Giữ một dòng — `truncate` vốn đã chạy đúng ở đó vì `flex-1`
|
|
86
|
+
* ghim sẵn bề rộng. Cùng lý do tác giả chỉ cho `description` xuống hai dòng ở lưới.
|
|
87
|
+
*/
|
|
88
|
+
variant === "grid" ? "line-clamp-2 break-words" : "truncate"
|
|
89
|
+
),
|
|
90
|
+
children: app.name
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
app.badge ? (
|
|
94
|
+
/*
|
|
95
|
+
* `shrink-0` KHÔNG phải để chống bóp méo — đo rồi: có hay không, badge đều 116,8px,
|
|
96
|
+
* vì `whitespace-nowrap` trong `Badge` đã giữ sàn `min-width: auto` bằng trọn bề rộng
|
|
97
|
+
* chữ, và phần co dồn hết sang ô tên (`overflow: hidden` → sàn 0).
|
|
98
|
+
*
|
|
99
|
+
* Nó ở đây vì hai lý do khác: khớp quy ước của mọi chỗ gọi `Badge` khác trong shell
|
|
100
|
+
* (`workspace-switcher`, `sidebar-menu-item` — chỗ này là chỗ DUY NHẤT thiếu), và để
|
|
101
|
+
* lá chắn thôi phụ thuộc vào một lớp ngầm trong `Badge`. Ai đó thêm `truncate` vào
|
|
102
|
+
* base cva của `Badge` là sàn tụt về 0 và badge méo, im lặng.
|
|
103
|
+
*/
|
|
104
|
+
/* @__PURE__ */ jsx(Badge, { variant: "destructive", size: "sm", className: "shrink-0", children: app.badge })
|
|
105
|
+
) : null
|
|
69
106
|
] }),
|
|
70
107
|
app.description ? /* @__PURE__ */ jsx(
|
|
71
108
|
"span",
|
|
@@ -128,7 +165,7 @@ function AppLauncherPanel({ variant = "grid", className, onSelected }) {
|
|
|
128
165
|
);
|
|
129
166
|
};
|
|
130
167
|
if (total === 0) return null;
|
|
131
|
-
const groupClass = variant === "grid" ? "grid grid-cols-3 gap-1" : "flex flex-col gap-0.5";
|
|
168
|
+
const groupClass = variant === "grid" ? "grid auto-rows-fr grid-cols-3 gap-1" : "flex flex-col gap-0.5";
|
|
132
169
|
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("flex flex-col gap-3", className), children: [
|
|
133
170
|
entitled.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [
|
|
134
171
|
/* @__PURE__ */ jsx("p", { className: "px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: labels.myApps }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/app-launcher.tsx"],"sourcesContent":["\"use client\";\n\nimport { LayoutGrid, Lock } from \"lucide-react\";\nimport { type KeyboardEvent, useRef } from \"react\";\nimport { Badge } from \"../components/ui/badge.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../components/ui/popover.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ShellNavLink } from \"./shell-link.js\";\nimport type { AppDescriptor } from \"./types.js\";\nimport { appAccentStyle, getEntitledApps, getLockedApps } from \"./utils.js\";\n\n/** Đánh dấu ô app để điều hướng bàn phím tìm được, kể cả ô render bằng LinkComponent của app. */\nconst TILE_ATTR = \"data-app-tile\";\n\nexport interface AppLauncherPanelProps {\n /** \"grid\" = lưới 3 cột kiểu Lark/Google Workspace; \"list\" = danh sách dọc (menu mobile). */\n variant?: \"grid\" | \"list\";\n className?: string;\n /** Gọi sau khi người dùng chọn một app — dùng để đóng popover/menu mobile. */\n onSelected?: () => void;\n}\n\n/**\n * Nội dung của AppLauncher, tách riêng để `MobileMenu` nhúng thẳng (không popover).\n *\n * Điều hướng bàn phím: ←/→/↑/↓ chạy giữa các ô (xuyên cả hai nhóm như một lưới liền),\n * Home/End nhảy đầu-cuối; Enter/Space để chính thẻ button/link xử lý.\n * Vị trí ô đọc từ DOM chứ không giữ mảng ref: ô \"đã mở khoá\" có thể là `LinkComponent`\n * do app cung cấp — không chắc component đó nhận `ref`.\n */\nexport function AppLauncherPanel({ variant = \"grid\", className, onSelected }: AppLauncherPanelProps) {\n const { apps, currentApp, onAppSelect, onUpsell, labels } = useShell();\n\n const entitled = getEntitledApps(apps);\n const locked = getLockedApps(apps);\n const total = entitled.length + locked.length;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const columns = variant === \"grid\" ? 3 : 1;\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n const root = rootRef.current;\n if (!root) return;\n const tiles = Array.from(root.querySelectorAll<HTMLElement>(`[${TILE_ATTR}]`));\n const index = tiles.indexOf(event.currentTarget);\n if (index < 0) return;\n\n const moves: Record<string, number | undefined> = {\n ArrowRight: index + 1,\n ArrowLeft: index - 1,\n ArrowDown: index + columns,\n ArrowUp: index - columns,\n Home: 0,\n End: tiles.length - 1\n };\n const next = moves[event.key];\n if (next === undefined) return;\n\n event.preventDefault();\n tiles[Math.max(0, Math.min(tiles.length - 1, next))]?.focus();\n };\n\n const renderTile = (app: AppDescriptor) => {\n const isLocked = app.entitled === false;\n const isCurrent = app.id === currentApp?.id;\n const Icon = app.icon;\n\n // Ô KHOÁ không tô `opacity-60` nữa. Nó KHÔNG phải control vô hiệu — bấm được và gọi\n // `onUpsell(appId)` — nên không hưởng miễn trừ \"thành phần vô hiệu\" của WCAG 1.4.3/1.4.11.\n // Với 60% độ mờ thì mô tả app tụt còn 2,41:1 (light) / 2,74:1 (dark) ✗ và icon trong ô\n // `bg-muted` còn 2,31:1 / 2,60:1 ✗, chỉ phục hồi khi hover — tức người không rê chuột thì\n // không đọc được. Bỏ độ mờ: mô tả `text-muted-foreground` 5,23:1 (light) / 5,35:1 (dark) ✓,\n // icon trên `bg-muted` 4,89:1 / 4,86:1 ✓. Tín hiệu \"chưa mở khoá\" không mất gì: ô icon vẫn\n // xám (thay vì tô màu app), vẫn có huy hiệu ổ khoá, vẫn có `title`/`aria-label` nói rõ.\n const tileClass = cn(\n \"group relative flex rounded-md text-left transition-colors duration-150 ease-out\",\n variant === \"grid\" ? \"flex-col items-start gap-1.5 p-2.5\" : \"items-center gap-2.5 p-2\",\n isLocked ? \"hover:bg-muted\" : \"hover:bg-accent\",\n isCurrent && \"bg-accent\"\n );\n\n const body = (\n <>\n {/* Ô icon tô ĐẶC bằng `--app-accent` + chữ `--app-accent-foreground`, KHÔNG phải\n `bg-app-accent/15 text-app-accent`: accent mặc định là gold #E8B824, icon vàng trên\n nền vàng 15% (đã trộn ra #FCF4DE) chỉ được 1,69:1 — dưới xa mức 3:1 mà WCAG 1.4.11\n đòi cho icon. Nền đặc thì icon là mực trên vàng = 10,19:1 ✓ và ô app vẫn giữ đúng\n màu riêng của từng app. Cùng cách xử lý với `FilterChips` (chip đang bật). */}\n <span\n className={cn(\n \"relative flex shrink-0 items-center justify-center rounded-md\",\n variant === \"grid\" ? \"size-9\" : \"size-8\",\n isLocked ? \"bg-muted text-muted-foreground\" : \"bg-app-accent text-app-accent-foreground\"\n )}\n style={appAccentStyle(app)}\n >\n <Icon className=\"size-4\" />\n {isLocked ? (\n <span className=\"absolute -bottom-1 -right-1 flex size-4 items-center justify-center rounded-full bg-muted text-muted-foreground ring-2 ring-popover\">\n <Lock className=\"size-2.5\" />\n </span>\n ) : null}\n </span>\n\n <span className={cn(\"flex min-w-0 flex-col\", variant === \"list\" && \"flex-1\")}>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">{app.name}</span>\n {app.badge ? (\n <Badge variant=\"destructive\" size=\"sm\">\n {app.badge}\n </Badge>\n ) : null}\n </span>\n {app.description ? (\n <span\n className={cn(\"text-[11px] leading-snug text-muted-foreground\", variant === \"grid\" && \"line-clamp-2\")}\n >\n {app.description}\n </span>\n ) : null}\n </span>\n </>\n );\n\n // Chưa mở khoá → không điều hướng, chỉ báo cho app biết để mời nâng gói.\n if (isLocked) {\n return (\n <button\n key={app.id}\n type=\"button\"\n data-app-tile=\"\"\n title={labels.lockedHint}\n aria-label={`${app.name} — ${labels.locked}`}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => {\n onUpsell?.(app.id);\n onSelected?.();\n }}\n >\n {body}\n </button>\n );\n }\n\n // Có `onAppSelect` → app tự lo điều hướng (đổi subdomain, nạp trước dữ liệu…);\n // không có → dùng link thật để giữ chuột-giữa / mở tab mới / trình đọc màn hình.\n if (onAppSelect) {\n return (\n <button\n key={app.id}\n type=\"button\"\n data-app-tile=\"\"\n aria-current={isCurrent ? \"page\" : undefined}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => {\n onAppSelect(app);\n onSelected?.();\n }}\n >\n {body}\n </button>\n );\n }\n\n return (\n <ShellNavLink\n key={app.id}\n href={app.href}\n data-app-tile=\"\"\n aria-current={isCurrent ? \"page\" : undefined}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => onSelected?.()}\n >\n {body}\n </ShellNavLink>\n );\n };\n\n if (total === 0) return null;\n\n const groupClass = variant === \"grid\" ? \"grid grid-cols-3 gap-1\" : \"flex flex-col gap-0.5\";\n\n return (\n <div ref={rootRef} className={cn(\"flex flex-col gap-3\", className)}>\n {entitled.length > 0 ? (\n <section>\n <p className=\"px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {labels.myApps}\n </p>\n <div className={groupClass}>{entitled.map(renderTile)}</div>\n </section>\n ) : null}\n\n {locked.length > 0 ? (\n <section>\n <p className=\"px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {labels.discoverApps}\n </p>\n <div className={groupClass}>{locked.map(renderTile)}</div>\n </section>\n ) : null}\n </div>\n );\n}\n\nexport interface AppLauncherProps extends AppLauncherPanelProps {\n /** Bỏ nút bấm + Popover, render thẳng panel (MobileMenu dùng cách này). */\n inline?: boolean;\n align?: \"start\" | \"center\" | \"end\";\n}\n\n/**\n * **Trục 2 — chuyển sản phẩm trong cùng workspace.**\n *\n * Nút lưới 3×3 ở header (kiểu Lark / Google Workspace) mở popover lưới ô app.\n * Trạng thái mở nằm ở `ShellProvider` để phím tắt `Cmd/Ctrl + Shift + K` bật được.\n * App chưa có trong gói hiện ô khoá và gọi `onUpsell(appId)` thay vì điều hướng.\n */\nexport function AppLauncher({\n className,\n variant = \"grid\",\n inline = false,\n align = \"end\",\n onSelected\n}: AppLauncherProps) {\n const { apps, appLauncherOpen, setAppLauncherOpen, labels } = useShell();\n\n if (apps.length === 0) return null;\n\n if (inline) {\n return <AppLauncherPanel variant={variant} className={className} onSelected={onSelected} />;\n }\n\n return (\n <Popover open={appLauncherOpen} onOpenChange={setAppLauncherOpen}>\n <Tooltip>\n <TooltipTrigger asChild>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={labels.appLauncherLabel}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\",\n appLauncherOpen && \"bg-muted text-foreground\",\n className\n )}\n >\n <LayoutGrid className=\"size-4\" />\n </button>\n </PopoverTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{labels.apps}</TooltipContent>\n </Tooltip>\n\n <PopoverContent align={align} sideOffset={8} className=\"w-[22rem] p-2\">\n <AppLauncherPanel\n variant={variant}\n onSelected={() => {\n setAppLauncherOpen(false);\n onSelected?.();\n }}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/app-launcher.tsx"],"sourcesContent":["\"use client\";\n\nimport { LayoutGrid, Lock } from \"lucide-react\";\nimport { type KeyboardEvent, useRef } from \"react\";\nimport { Badge } from \"../components/ui/badge.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../components/ui/popover.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ShellNavLink } from \"./shell-link.js\";\nimport type { AppDescriptor } from \"./types.js\";\nimport { appAccentStyle, getEntitledApps, getLockedApps } from \"./utils.js\";\n\n/** Đánh dấu ô app để điều hướng bàn phím tìm được, kể cả ô render bằng LinkComponent của app. */\nconst TILE_ATTR = \"data-app-tile\";\n\nexport interface AppLauncherPanelProps {\n /** \"grid\" = lưới 3 cột kiểu Lark/Google Workspace; \"list\" = danh sách dọc (menu mobile). */\n variant?: \"grid\" | \"list\";\n className?: string;\n /** Gọi sau khi người dùng chọn một app — dùng để đóng popover/menu mobile. */\n onSelected?: () => void;\n}\n\n/**\n * Nội dung của AppLauncher, tách riêng để `MobileMenu` nhúng thẳng (không popover).\n *\n * Điều hướng bàn phím: ←/→/↑/↓ chạy giữa các ô (xuyên cả hai nhóm như một lưới liền),\n * Home/End nhảy đầu-cuối; Enter/Space để chính thẻ button/link xử lý.\n * Vị trí ô đọc từ DOM chứ không giữ mảng ref: ô \"đã mở khoá\" có thể là `LinkComponent`\n * do app cung cấp — không chắc component đó nhận `ref`.\n */\nexport function AppLauncherPanel({ variant = \"grid\", className, onSelected }: AppLauncherPanelProps) {\n const { apps, currentApp, onAppSelect, onUpsell, labels } = useShell();\n\n const entitled = getEntitledApps(apps);\n const locked = getLockedApps(apps);\n const total = entitled.length + locked.length;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const columns = variant === \"grid\" ? 3 : 1;\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n const root = rootRef.current;\n if (!root) return;\n const tiles = Array.from(root.querySelectorAll<HTMLElement>(`[${TILE_ATTR}]`));\n const index = tiles.indexOf(event.currentTarget);\n if (index < 0) return;\n\n const moves: Record<string, number | undefined> = {\n ArrowRight: index + 1,\n ArrowLeft: index - 1,\n ArrowDown: index + columns,\n ArrowUp: index - columns,\n Home: 0,\n End: tiles.length - 1\n };\n const next = moves[event.key];\n if (next === undefined) return;\n\n event.preventDefault();\n tiles[Math.max(0, Math.min(tiles.length - 1, next))]?.focus();\n };\n\n const renderTile = (app: AppDescriptor) => {\n const isLocked = app.entitled === false;\n const isCurrent = app.id === currentApp?.id;\n const Icon = app.icon;\n\n // Ô KHOÁ không tô `opacity-60` nữa. Nó KHÔNG phải control vô hiệu — bấm được và gọi\n // `onUpsell(appId)` — nên không hưởng miễn trừ \"thành phần vô hiệu\" của WCAG 1.4.3/1.4.11.\n // Với 60% độ mờ thì mô tả app tụt còn 2,41:1 (light) / 2,74:1 (dark) ✗ và icon trong ô\n // `bg-muted` còn 2,31:1 / 2,60:1 ✗, chỉ phục hồi khi hover — tức người không rê chuột thì\n // không đọc được. Bỏ độ mờ: mô tả `text-muted-foreground` 5,23:1 (light) / 5,35:1 (dark) ✓,\n // icon trên `bg-muted` 4,89:1 / 4,86:1 ✓. Tín hiệu \"chưa mở khoá\" không mất gì: ô icon vẫn\n // xám (thay vì tô màu app), vẫn có huy hiệu ổ khoá, vẫn có `title`/`aria-label` nói rõ.\n const tileClass = cn(\n \"group relative flex rounded-md text-left transition-colors duration-150 ease-out\",\n variant === \"grid\" ? \"flex-col items-start gap-1.5 p-2.5\" : \"items-center gap-2.5 p-2\",\n isLocked ? \"hover:bg-muted\" : \"hover:bg-accent\",\n isCurrent && \"bg-accent\"\n );\n\n const body = (\n <>\n {/* Ô icon tô ĐẶC bằng `--app-accent` + chữ `--app-accent-foreground`, KHÔNG phải\n `bg-app-accent/15 text-app-accent`: accent mặc định là gold #E8B824, icon vàng trên\n nền vàng 15% (đã trộn ra #FCF4DE) chỉ được 1,69:1 — dưới xa mức 3:1 mà WCAG 1.4.11\n đòi cho icon. Nền đặc thì icon là mực trên vàng = 10,19:1 ✓ và ô app vẫn giữ đúng\n màu riêng của từng app. Cùng cách xử lý với `FilterChips` (chip đang bật). */}\n <span\n className={cn(\n \"relative flex shrink-0 items-center justify-center rounded-md\",\n variant === \"grid\" ? \"size-9\" : \"size-8\",\n isLocked ? \"bg-muted text-muted-foreground\" : \"bg-app-accent text-app-accent-foreground\"\n )}\n style={appAccentStyle(app)}\n >\n <Icon className=\"size-4\" />\n {isLocked ? (\n <span className=\"absolute -bottom-1 -right-1 flex size-4 items-center justify-center rounded-full bg-muted text-muted-foreground ring-2 ring-popover\">\n <Lock className=\"size-2.5\" />\n </span>\n ) : null}\n </span>\n\n {/*\n * `w-full` ở ô lưới là thứ THẬT SỰ bó được chữ — không phải `min-w-0`.\n *\n * Ô lưới là `flex-col items-start` (xem `tileClass`), nên theo trục ngang cột này lấy\n * `fit-content`: nó RỘNG RA THEO TÊN thay vì bằng ô. Mọi phép cắt bên trong vì thế không\n * bao giờ có gì để bám vào. Đo ở app thật, ô 109px: tên một từ dài cho cột 272px và tràn\n * 174px, `scrollWidth === clientWidth` — `line-clamp` được đặt nhưng chưa từng chạy.\n * Thêm `min-w-0` vào hàng bên dưới KHÔNG đổi được số nào (đo: y hệt bản hỏng); `w-full`\n * đưa cột về đúng 89px và từ đó phép cắt mới có hiệu lực.\n *\n * Biến thể `list` không dính: ô đó là hàng ngang nên `flex-1` đã ghim bề rộng sẵn.\n */}\n <span className={cn(\"flex min-w-0 flex-col\", variant === \"grid\" ? \"w-full\" : \"flex-1\")}>\n {/*\n * `items-start` chứ không `items-center` khi tên có thể xuống hai dòng: căn giữa đẩy\n * badge xuống giữa khe hai dòng (đo được 10,6px), xa hẳn dòng chữ nó đang chú thích.\n * Tên một dòng thì hai cách lệch nhau 0,8px — không nhìn thấy.\n */}\n <span className={cn(\"flex min-w-0 gap-1.5\", variant === \"grid\" ? \"items-start\" : \"items-center\")}>\n <span\n className={cn(\n \"text-sm font-medium text-foreground\",\n /*\n * Lưới: tên ứng dụng dài hơn ô là chuyện thường (\"Tài khoản Comitor\" ở ô 109px),\n * nên cho hai dòng rồi mới cắt — `grid-cols-3` khai `items-stretch` mặc định nên\n * ô cao thêm một dòng không làm hàng bị so le.\n *\n * `break-words` đi KÈM `line-clamp-2`, không phải phòng xa: `-webkit-line-clamp`\n * chỉ đặt được dấu \"…\" khi chữ THẬT SỰ tràn sang dòng thứ ba. Một tên không có\n * khoảng trắng thì không xuống dòng được, nên nó nằm gọn một dòng và bị\n * `overflow: hidden` cắt PHẲNG giữa chữ — đo được: `soDong: 1`,\n * `scrollHeight === clientHeight`, không có dấu ba chấm nào. Cho phép ngắt trong\n * từ thì nó thành hai dòng và dấu \"…\" quay lại.\n *\n * Danh sách: mỗi mục là một hàng ngang trong popover hẹp; hai dòng ở đó làm chiều\n * cao các mục nhấp nhô. Giữ một dòng — `truncate` vốn đã chạy đúng ở đó vì `flex-1`\n * ghim sẵn bề rộng. Cùng lý do tác giả chỉ cho `description` xuống hai dòng ở lưới.\n */\n variant === \"grid\" ? \"line-clamp-2 break-words\" : \"truncate\"\n )}\n >\n {app.name}\n </span>\n {app.badge ? (\n /*\n * `shrink-0` KHÔNG phải để chống bóp méo — đo rồi: có hay không, badge đều 116,8px,\n * vì `whitespace-nowrap` trong `Badge` đã giữ sàn `min-width: auto` bằng trọn bề rộng\n * chữ, và phần co dồn hết sang ô tên (`overflow: hidden` → sàn 0).\n *\n * Nó ở đây vì hai lý do khác: khớp quy ước của mọi chỗ gọi `Badge` khác trong shell\n * (`workspace-switcher`, `sidebar-menu-item` — chỗ này là chỗ DUY NHẤT thiếu), và để\n * lá chắn thôi phụ thuộc vào một lớp ngầm trong `Badge`. Ai đó thêm `truncate` vào\n * base cva của `Badge` là sàn tụt về 0 và badge méo, im lặng.\n */\n <Badge variant=\"destructive\" size=\"sm\" className=\"shrink-0\">\n {app.badge}\n </Badge>\n ) : null}\n </span>\n {app.description ? (\n <span\n className={cn(\"text-[11px] leading-snug text-muted-foreground\", variant === \"grid\" && \"line-clamp-2\")}\n >\n {app.description}\n </span>\n ) : null}\n </span>\n </>\n );\n\n // Chưa mở khoá → không điều hướng, chỉ báo cho app biết để mời nâng gói.\n if (isLocked) {\n return (\n <button\n key={app.id}\n type=\"button\"\n data-app-tile=\"\"\n title={labels.lockedHint}\n aria-label={`${app.name} — ${labels.locked}`}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => {\n onUpsell?.(app.id);\n onSelected?.();\n }}\n >\n {body}\n </button>\n );\n }\n\n // Có `onAppSelect` → app tự lo điều hướng (đổi subdomain, nạp trước dữ liệu…);\n // không có → dùng link thật để giữ chuột-giữa / mở tab mới / trình đọc màn hình.\n if (onAppSelect) {\n return (\n <button\n key={app.id}\n type=\"button\"\n data-app-tile=\"\"\n aria-current={isCurrent ? \"page\" : undefined}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => {\n onAppSelect(app);\n onSelected?.();\n }}\n >\n {body}\n </button>\n );\n }\n\n return (\n <ShellNavLink\n key={app.id}\n href={app.href}\n data-app-tile=\"\"\n aria-current={isCurrent ? \"page\" : undefined}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => onSelected?.()}\n >\n {body}\n </ShellNavLink>\n );\n };\n\n if (total === 0) return null;\n\n /*\n * `auto-rows-fr` vá thứ duy nhất mà việc cho tên xuống hai dòng làm xấu đi: TRONG một hàng thì\n * các ô đã bằng nhau sẵn (`align-items: stretch` là mặc định của Grid), nhưng GIỮA các hàng thì\n * không — hàng có một tên hai dòng cao 116px trong khi hàng kia 97px. `fr` cho mọi hàng chung\n * một chiều cao.\n */\n const groupClass = variant === \"grid\" ? \"grid auto-rows-fr grid-cols-3 gap-1\" : \"flex flex-col gap-0.5\";\n\n return (\n <div ref={rootRef} className={cn(\"flex flex-col gap-3\", className)}>\n {entitled.length > 0 ? (\n <section>\n <p className=\"px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {labels.myApps}\n </p>\n <div className={groupClass}>{entitled.map(renderTile)}</div>\n </section>\n ) : null}\n\n {locked.length > 0 ? (\n <section>\n <p className=\"px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {labels.discoverApps}\n </p>\n <div className={groupClass}>{locked.map(renderTile)}</div>\n </section>\n ) : null}\n </div>\n );\n}\n\nexport interface AppLauncherProps extends AppLauncherPanelProps {\n /** Bỏ nút bấm + Popover, render thẳng panel (MobileMenu dùng cách này). */\n inline?: boolean;\n align?: \"start\" | \"center\" | \"end\";\n}\n\n/**\n * **Trục 2 — chuyển sản phẩm trong cùng workspace.**\n *\n * Nút lưới 3×3 ở header (kiểu Lark / Google Workspace) mở popover lưới ô app.\n * Trạng thái mở nằm ở `ShellProvider` để phím tắt `Cmd/Ctrl + Shift + K` bật được.\n * App chưa có trong gói hiện ô khoá và gọi `onUpsell(appId)` thay vì điều hướng.\n */\nexport function AppLauncher({\n className,\n variant = \"grid\",\n inline = false,\n align = \"end\",\n onSelected\n}: AppLauncherProps) {\n const { apps, appLauncherOpen, setAppLauncherOpen, labels } = useShell();\n\n if (apps.length === 0) return null;\n\n if (inline) {\n return <AppLauncherPanel variant={variant} className={className} onSelected={onSelected} />;\n }\n\n return (\n <Popover open={appLauncherOpen} onOpenChange={setAppLauncherOpen}>\n <Tooltip>\n <TooltipTrigger asChild>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={labels.appLauncherLabel}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\",\n appLauncherOpen && \"bg-muted text-foreground\",\n className\n )}\n >\n <LayoutGrid className=\"size-4\" />\n </button>\n </PopoverTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{labels.apps}</TooltipContent>\n </Tooltip>\n\n <PopoverContent align={align} sideOffset={8} className=\"w-[22rem] p-2\">\n <AppLauncherPanel\n variant={variant}\n onSelected={() => {\n setAppLauncherOpen(false);\n onSelected?.();\n }}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comitor/ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -57,19 +57,6 @@
|
|
|
57
57
|
"./styles.css": "./styles.css",
|
|
58
58
|
"./package.json": "./package.json"
|
|
59
59
|
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"build": "tsup",
|
|
62
|
-
"typecheck": "tsc --noEmit",
|
|
63
|
-
"lint": "biome check .",
|
|
64
|
-
"lint:fix": "biome check --write .",
|
|
65
|
-
"lint:a11y": "node scripts/check-contrast.mjs",
|
|
66
|
-
"test:a11y-gate": "node scripts/a11y/self-test.mjs",
|
|
67
|
-
"test:nav-active": "node scripts/check-nav-active.mjs",
|
|
68
|
-
"verify:directives": "node scripts/verify-directives.mjs",
|
|
69
|
-
"smoke:entries": "node scripts/smoke-entries.mjs",
|
|
70
|
-
"check:exports": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
|
|
71
|
-
"prepublishOnly": "pnpm run typecheck && pnpm run build && pnpm run verify:directives && pnpm run smoke:entries && pnpm run check:exports && pnpm run test:nav-active && pnpm run test:a11y-gate && pnpm run lint:a11y"
|
|
72
|
-
},
|
|
73
60
|
"peerDependencies": {
|
|
74
61
|
"lucide-react": ">=0.540.0",
|
|
75
62
|
"next": ">=15",
|
|
@@ -151,5 +138,17 @@
|
|
|
151
138
|
"tailwindcss": "^4.2.0",
|
|
152
139
|
"tsup": "^8.3.5",
|
|
153
140
|
"typescript": "^5.7.0"
|
|
141
|
+
},
|
|
142
|
+
"scripts": {
|
|
143
|
+
"build": "tsup",
|
|
144
|
+
"typecheck": "tsc --noEmit",
|
|
145
|
+
"lint": "biome check .",
|
|
146
|
+
"lint:fix": "biome check --write .",
|
|
147
|
+
"lint:a11y": "node scripts/check-contrast.mjs",
|
|
148
|
+
"test:a11y-gate": "node scripts/a11y/self-test.mjs",
|
|
149
|
+
"test:nav-active": "node scripts/check-nav-active.mjs",
|
|
150
|
+
"verify:directives": "node scripts/verify-directives.mjs",
|
|
151
|
+
"smoke:entries": "node scripts/smoke-entries.mjs",
|
|
152
|
+
"check:exports": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css"
|
|
154
153
|
}
|
|
155
|
-
}
|
|
154
|
+
}
|