@diwauhris/ui 1.0.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 +75 -0
- package/lib/assets/ui.css +154 -0
- package/lib/index.cjs +183 -0
- package/lib/index.mjs +65 -0
- package/lib/index10.cjs +33 -0
- package/lib/index10.cjs.map +1 -0
- package/lib/index10.mjs +33 -0
- package/lib/index10.mjs.map +1 -0
- package/lib/index11.cjs +16 -0
- package/lib/index11.cjs.map +1 -0
- package/lib/index11.mjs +16 -0
- package/lib/index11.mjs.map +1 -0
- package/lib/index12.cjs +16 -0
- package/lib/index12.cjs.map +1 -0
- package/lib/index12.mjs +15 -0
- package/lib/index12.mjs.map +1 -0
- package/lib/index13.cjs +57 -0
- package/lib/index13.cjs.map +1 -0
- package/lib/index13.mjs +57 -0
- package/lib/index13.mjs.map +1 -0
- package/lib/index14.cjs +10 -0
- package/lib/index14.cjs.map +1 -0
- package/lib/index14.mjs +10 -0
- package/lib/index14.mjs.map +1 -0
- package/lib/index15.cjs +30 -0
- package/lib/index15.cjs.map +1 -0
- package/lib/index15.mjs +30 -0
- package/lib/index15.mjs.map +1 -0
- package/lib/index16.cjs +28 -0
- package/lib/index16.cjs.map +1 -0
- package/lib/index16.mjs +26 -0
- package/lib/index16.mjs.map +1 -0
- package/lib/index17.cjs +43 -0
- package/lib/index17.cjs.map +1 -0
- package/lib/index17.mjs +43 -0
- package/lib/index17.mjs.map +1 -0
- package/lib/index18.cjs +59 -0
- package/lib/index18.cjs.map +1 -0
- package/lib/index18.mjs +59 -0
- package/lib/index18.mjs.map +1 -0
- package/lib/index19.cjs +67 -0
- package/lib/index19.cjs.map +1 -0
- package/lib/index19.mjs +67 -0
- package/lib/index19.mjs.map +1 -0
- package/lib/index2.cjs +33 -0
- package/lib/index2.mjs +13 -0
- package/lib/index20.cjs +79 -0
- package/lib/index20.cjs.map +1 -0
- package/lib/index20.mjs +79 -0
- package/lib/index20.mjs.map +1 -0
- package/lib/index21.cjs +105 -0
- package/lib/index21.cjs.map +1 -0
- package/lib/index21.mjs +104 -0
- package/lib/index21.mjs.map +1 -0
- package/lib/index22.cjs +50 -0
- package/lib/index22.cjs.map +1 -0
- package/lib/index22.mjs +50 -0
- package/lib/index22.mjs.map +1 -0
- package/lib/index23.cjs +143 -0
- package/lib/index23.cjs.map +1 -0
- package/lib/index23.mjs +143 -0
- package/lib/index23.mjs.map +1 -0
- package/lib/index25.cjs +177 -0
- package/lib/index25.cjs.map +1 -0
- package/lib/index25.mjs +175 -0
- package/lib/index25.mjs.map +1 -0
- package/lib/index26.cjs +108 -0
- package/lib/index26.cjs.map +1 -0
- package/lib/index26.mjs +108 -0
- package/lib/index26.mjs.map +1 -0
- package/lib/index27.cjs +120 -0
- package/lib/index27.cjs.map +1 -0
- package/lib/index27.mjs +120 -0
- package/lib/index27.mjs.map +1 -0
- package/lib/index28.cjs +54 -0
- package/lib/index28.cjs.map +1 -0
- package/lib/index28.mjs +54 -0
- package/lib/index28.mjs.map +1 -0
- package/lib/index29.cjs +100 -0
- package/lib/index29.cjs.map +1 -0
- package/lib/index29.mjs +100 -0
- package/lib/index29.mjs.map +1 -0
- package/lib/index3.cjs +38 -0
- package/lib/index3.cjs.map +1 -0
- package/lib/index3.mjs +38 -0
- package/lib/index3.mjs.map +1 -0
- package/lib/index30.cjs +27 -0
- package/lib/index30.cjs.map +1 -0
- package/lib/index30.mjs +27 -0
- package/lib/index30.mjs.map +1 -0
- package/lib/index31.cjs +72 -0
- package/lib/index31.cjs.map +1 -0
- package/lib/index31.mjs +72 -0
- package/lib/index31.mjs.map +1 -0
- package/lib/index32.cjs +19 -0
- package/lib/index32.cjs.map +1 -0
- package/lib/index32.mjs +19 -0
- package/lib/index32.mjs.map +1 -0
- package/lib/index33.cjs +25 -0
- package/lib/index33.cjs.map +1 -0
- package/lib/index33.mjs +25 -0
- package/lib/index33.mjs.map +1 -0
- package/lib/index34.cjs +55 -0
- package/lib/index34.cjs.map +1 -0
- package/lib/index34.mjs +51 -0
- package/lib/index34.mjs.map +1 -0
- package/lib/index35.cjs +25 -0
- package/lib/index35.cjs.map +1 -0
- package/lib/index35.mjs +25 -0
- package/lib/index35.mjs.map +1 -0
- package/lib/index36.cjs +54 -0
- package/lib/index36.cjs.map +1 -0
- package/lib/index36.mjs +49 -0
- package/lib/index36.mjs.map +1 -0
- package/lib/index37.cjs +130 -0
- package/lib/index37.cjs.map +1 -0
- package/lib/index37.mjs +128 -0
- package/lib/index37.mjs.map +1 -0
- package/lib/index38.cjs +106 -0
- package/lib/index38.cjs.map +1 -0
- package/lib/index38.mjs +102 -0
- package/lib/index38.mjs.map +1 -0
- package/lib/index39.cjs +137 -0
- package/lib/index39.cjs.map +1 -0
- package/lib/index39.mjs +137 -0
- package/lib/index39.mjs.map +1 -0
- package/lib/index4.cjs +24 -0
- package/lib/index4.cjs.map +1 -0
- package/lib/index4.mjs +24 -0
- package/lib/index4.mjs.map +1 -0
- package/lib/index40.cjs +130 -0
- package/lib/index40.cjs.map +1 -0
- package/lib/index40.mjs +126 -0
- package/lib/index40.mjs.map +1 -0
- package/lib/index41.cjs +60 -0
- package/lib/index41.cjs.map +1 -0
- package/lib/index41.mjs +58 -0
- package/lib/index41.mjs.map +1 -0
- package/lib/index42.cjs +105 -0
- package/lib/index42.cjs.map +1 -0
- package/lib/index42.mjs +105 -0
- package/lib/index42.mjs.map +1 -0
- package/lib/index43.cjs +47 -0
- package/lib/index43.cjs.map +1 -0
- package/lib/index43.mjs +42 -0
- package/lib/index43.mjs.map +1 -0
- package/lib/index44.cjs +40 -0
- package/lib/index44.cjs.map +1 -0
- package/lib/index44.mjs +35 -0
- package/lib/index44.mjs.map +1 -0
- package/lib/index45.cjs +91 -0
- package/lib/index45.cjs.map +1 -0
- package/lib/index45.mjs +91 -0
- package/lib/index45.mjs.map +1 -0
- package/lib/index46.cjs +180 -0
- package/lib/index46.cjs.map +1 -0
- package/lib/index46.mjs +180 -0
- package/lib/index46.mjs.map +1 -0
- package/lib/index47.cjs +61 -0
- package/lib/index47.cjs.map +1 -0
- package/lib/index47.mjs +61 -0
- package/lib/index47.mjs.map +1 -0
- package/lib/index48.cjs +165 -0
- package/lib/index48.cjs.map +1 -0
- package/lib/index48.mjs +161 -0
- package/lib/index48.mjs.map +1 -0
- package/lib/index49.cjs +83 -0
- package/lib/index49.cjs.map +1 -0
- package/lib/index49.mjs +83 -0
- package/lib/index49.mjs.map +1 -0
- package/lib/index5.cjs +39 -0
- package/lib/index5.cjs.map +1 -0
- package/lib/index5.mjs +39 -0
- package/lib/index5.mjs.map +1 -0
- package/lib/index50.cjs +100 -0
- package/lib/index50.cjs.map +1 -0
- package/lib/index50.mjs +100 -0
- package/lib/index50.mjs.map +1 -0
- package/lib/index51.cjs +116 -0
- package/lib/index51.cjs.map +1 -0
- package/lib/index51.mjs +113 -0
- package/lib/index51.mjs.map +1 -0
- package/lib/index52.cjs +93 -0
- package/lib/index52.cjs.map +1 -0
- package/lib/index52.mjs +93 -0
- package/lib/index52.mjs.map +1 -0
- package/lib/index53.cjs +120 -0
- package/lib/index53.cjs.map +1 -0
- package/lib/index53.mjs +120 -0
- package/lib/index53.mjs.map +1 -0
- package/lib/index54.cjs +53 -0
- package/lib/index54.cjs.map +1 -0
- package/lib/index54.mjs +50 -0
- package/lib/index54.mjs.map +1 -0
- package/lib/index55.cjs +130 -0
- package/lib/index55.cjs.map +1 -0
- package/lib/index55.mjs +127 -0
- package/lib/index55.mjs.map +1 -0
- package/lib/index56.cjs +36 -0
- package/lib/index56.cjs.map +1 -0
- package/lib/index56.mjs +35 -0
- package/lib/index56.mjs.map +1 -0
- package/lib/index57.cjs +47 -0
- package/lib/index57.cjs.map +1 -0
- package/lib/index57.mjs +47 -0
- package/lib/index57.mjs.map +1 -0
- package/lib/index58.cjs +97 -0
- package/lib/index58.cjs.map +1 -0
- package/lib/index58.mjs +97 -0
- package/lib/index58.mjs.map +1 -0
- package/lib/index59.cjs +120 -0
- package/lib/index59.cjs.map +1 -0
- package/lib/index59.mjs +116 -0
- package/lib/index59.mjs.map +1 -0
- package/lib/index6.cjs +29 -0
- package/lib/index6.cjs.map +1 -0
- package/lib/index6.mjs +29 -0
- package/lib/index6.mjs.map +1 -0
- package/lib/index60.cjs +34 -0
- package/lib/index60.cjs.map +1 -0
- package/lib/index60.mjs +34 -0
- package/lib/index60.mjs.map +1 -0
- package/lib/index61.cjs +62 -0
- package/lib/index61.cjs.map +1 -0
- package/lib/index61.mjs +62 -0
- package/lib/index61.mjs.map +1 -0
- package/lib/index62.cjs +213 -0
- package/lib/index62.cjs.map +1 -0
- package/lib/index62.mjs +213 -0
- package/lib/index62.mjs.map +1 -0
- package/lib/index63.cjs +42 -0
- package/lib/index63.cjs.map +1 -0
- package/lib/index63.mjs +42 -0
- package/lib/index63.mjs.map +1 -0
- package/lib/index64.cjs +86 -0
- package/lib/index64.cjs.map +1 -0
- package/lib/index64.mjs +86 -0
- package/lib/index64.mjs.map +1 -0
- package/lib/index65.cjs +156 -0
- package/lib/index65.cjs.map +1 -0
- package/lib/index65.mjs +156 -0
- package/lib/index65.mjs.map +1 -0
- package/lib/index66.cjs +107 -0
- package/lib/index66.cjs.map +1 -0
- package/lib/index66.mjs +106 -0
- package/lib/index66.mjs.map +1 -0
- package/lib/index67.cjs +52 -0
- package/lib/index67.cjs.map +1 -0
- package/lib/index67.mjs +52 -0
- package/lib/index67.mjs.map +1 -0
- package/lib/index68.cjs +125 -0
- package/lib/index68.cjs.map +1 -0
- package/lib/index68.mjs +115 -0
- package/lib/index68.mjs.map +1 -0
- package/lib/index69.cjs +34 -0
- package/lib/index69.cjs.map +1 -0
- package/lib/index69.mjs +29 -0
- package/lib/index69.mjs.map +1 -0
- package/lib/index7.cjs +17 -0
- package/lib/index7.cjs.map +1 -0
- package/lib/index7.mjs +17 -0
- package/lib/index7.mjs.map +1 -0
- package/lib/index8.cjs +86 -0
- package/lib/index8.cjs.map +1 -0
- package/lib/index8.mjs +86 -0
- package/lib/index8.mjs.map +1 -0
- package/lib/index9.cjs +29 -0
- package/lib/index9.cjs.map +1 -0
- package/lib/index9.mjs +29 -0
- package/lib/index9.mjs.map +1 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +68 -0
package/lib/index64.cjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
|
+
let lucide_react = require("lucide-react");
|
|
3
|
+
//#region src/ui-library/gallery/permission-matrix/PermissionMatrix.tsx
|
|
4
|
+
/**
|
|
5
|
+
* PermissionMatrix — Design System Component
|
|
6
|
+
*
|
|
7
|
+
* A grid showing which roles have which permissions, using checkboxes
|
|
8
|
+
* or check icons. Purely presentational. No RBAC logic.
|
|
9
|
+
*
|
|
10
|
+
* Based on the "Effective Permission Matrix" pattern in RoleDetailPage.tsx
|
|
11
|
+
* but domain-free.
|
|
12
|
+
*
|
|
13
|
+
* Accessibility:
|
|
14
|
+
* - <table> with <caption>
|
|
15
|
+
* - Column headers: role names
|
|
16
|
+
* - Row headers: permission names
|
|
17
|
+
* - Cell has aria-label: "Permission X for Role Y: granted/denied"
|
|
18
|
+
*/
|
|
19
|
+
function PermissionMatrix({ roles, permissions, caption = "Permission matrix", className = "" }) {
|
|
20
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21
|
+
className: `w-full overflow-x-auto rounded-xl border border-slate-200 ${className}`,
|
|
22
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
23
|
+
className: "w-full text-sm border-collapse",
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("caption", {
|
|
26
|
+
className: "sr-only",
|
|
27
|
+
children: caption
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
30
|
+
className: "border-b border-slate-100 bg-slate-50",
|
|
31
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
32
|
+
scope: "col",
|
|
33
|
+
className: "px-5 py-3 text-left text-[11px] font-bold uppercase tracking-widest text-slate-500",
|
|
34
|
+
children: "Permission"
|
|
35
|
+
}), roles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
36
|
+
scope: "col",
|
|
37
|
+
className: "px-4 py-3 text-center text-[11px] font-bold uppercase tracking-widest text-slate-500",
|
|
38
|
+
children: role
|
|
39
|
+
}, role))]
|
|
40
|
+
}) }),
|
|
41
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", {
|
|
42
|
+
className: "divide-y divide-slate-100 bg-white",
|
|
43
|
+
children: permissions.map((perm) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
44
|
+
className: "hover:bg-slate-50/60",
|
|
45
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
46
|
+
scope: "row",
|
|
47
|
+
className: "px-5 py-3 text-left text-xs font-semibold text-slate-700",
|
|
48
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49
|
+
className: "flex items-center gap-2",
|
|
50
|
+
children: [perm.group && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
51
|
+
className: "text-[9px] font-bold uppercase tracking-wider text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded",
|
|
52
|
+
children: perm.group
|
|
53
|
+
}), perm.label]
|
|
54
|
+
})
|
|
55
|
+
}), perm.cells.map((cell, ci) => {
|
|
56
|
+
const roleLabel = roles[ci] ?? "";
|
|
57
|
+
const cellLabel = `${perm.label} for ${roleLabel}: ${cell.granted ? "granted" : "denied"}${cell.inherited ? " (inherited)" : ""}`;
|
|
58
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
59
|
+
className: "px-4 py-3 text-center",
|
|
60
|
+
"aria-label": cellLabel,
|
|
61
|
+
children: cell.granted ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
62
|
+
className: `inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto ${cell.inherited ? "bg-brand-blue/15 text-brand-blue/60" : "bg-brand-blue text-white"}`,
|
|
63
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {
|
|
64
|
+
size: 11,
|
|
65
|
+
strokeWidth: 2.5,
|
|
66
|
+
"aria-hidden": "true"
|
|
67
|
+
})
|
|
68
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
69
|
+
className: "inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto bg-slate-100 text-slate-300",
|
|
70
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Minus, {
|
|
71
|
+
size: 11,
|
|
72
|
+
"aria-hidden": "true"
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}, ci);
|
|
76
|
+
})]
|
|
77
|
+
}, perm.label))
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
exports.PermissionMatrix = PermissionMatrix;
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=index64.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index64.cjs","names":[],"sources":["../src/ui-library/gallery/permission-matrix/PermissionMatrix.tsx"],"sourcesContent":["/**\n * PermissionMatrix — Design System Component\n *\n * A grid showing which roles have which permissions, using checkboxes\n * or check icons. Purely presentational. No RBAC logic.\n *\n * Based on the \"Effective Permission Matrix\" pattern in RoleDetailPage.tsx\n * but domain-free.\n *\n * Accessibility:\n * - <table> with <caption>\n * - Column headers: role names\n * - Row headers: permission names\n * - Cell has aria-label: \"Permission X for Role Y: granted/denied\"\n */\n\nimport { Check, Minus } from 'lucide-react';\n\nexport interface PermissionMatrixCell {\n granted: boolean;\n /** If true, the cell is read-only and visually subdued */\n inherited?: boolean;\n}\n\nexport interface PermissionMatrixProps {\n /** Column headers (roles) */\n roles: string[];\n /** Row definitions */\n permissions: Array<{\n label: string;\n group?: string;\n cells: PermissionMatrixCell[];\n }>;\n caption?: string;\n className?: string;\n}\n\nexport function PermissionMatrix({\n roles,\n permissions,\n caption = 'Permission matrix',\n className = '',\n}: PermissionMatrixProps) {\n return (\n <div className={`w-full overflow-x-auto rounded-xl border border-slate-200 ${className}`}>\n <table className=\"w-full text-sm border-collapse\">\n <caption className=\"sr-only\">{caption}</caption>\n <thead>\n <tr className=\"border-b border-slate-100 bg-slate-50\">\n <th scope=\"col\" className=\"px-5 py-3 text-left text-[11px] font-bold uppercase tracking-widest text-slate-500\">\n Permission\n </th>\n {roles.map((role) => (\n <th\n key={role}\n scope=\"col\"\n className=\"px-4 py-3 text-center text-[11px] font-bold uppercase tracking-widest text-slate-500\"\n >\n {role}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-slate-100 bg-white\">\n {permissions.map((perm) => (\n <tr key={perm.label} className=\"hover:bg-slate-50/60\">\n <th\n scope=\"row\"\n className=\"px-5 py-3 text-left text-xs font-semibold text-slate-700\"\n >\n <div className=\"flex items-center gap-2\">\n {perm.group && (\n <span className=\"text-[9px] font-bold uppercase tracking-wider text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded\">\n {perm.group}\n </span>\n )}\n {perm.label}\n </div>\n </th>\n {perm.cells.map((cell, ci) => {\n const roleLabel = roles[ci] ?? '';\n const cellLabel = `${perm.label} for ${roleLabel}: ${cell.granted ? 'granted' : 'denied'}${cell.inherited ? ' (inherited)' : ''}`;\n return (\n <td\n key={ci}\n className=\"px-4 py-3 text-center\"\n aria-label={cellLabel}\n >\n {cell.granted ? (\n <span className={`inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto ${cell.inherited ? 'bg-brand-blue/15 text-brand-blue/60' : 'bg-brand-blue text-white'}`}>\n <Check size={11} strokeWidth={2.5} aria-hidden=\"true\" />\n </span>\n ) : (\n <span className=\"inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto bg-slate-100 text-slate-300\">\n <Minus size={11} aria-hidden=\"true\" />\n </span>\n )}\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,SAAgB,iBAAiB,EAC/B,OACA,aACA,UAAU,qBACV,YAAY,MACY;CACxB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,WAAW,6DAA6D;EAC3E,UAAA,iBAAA,GAAA,kBAAA,KAAA,CAAC,SAAD;GAAO,WAAU;GAAjB,UAAA;IACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD;KAAS,WAAU;KAAW,UAAA;IAAiB,CAAA;IAC/C,iBAAA,GAAA,kBAAA,IAAA,CAAC,SAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,MAAD;KAAI,WAAU;KAAd,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;MAAI,OAAM;MAAM,WAAU;MAAqF,UAAA;KAE3G,CAAA,GACH,MAAM,KAAK,SACV,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;MAEE,OAAM;MACN,WAAU;MAET,UAAA;KACC,GALG,IAKH,CACL,CACC;IACC,CAAA,EAAA,CAAA;IACP,iBAAA,GAAA,kBAAA,IAAA,CAAC,SAAD;KAAO,WAAU;KACd,UAAA,YAAY,KAAK,SAChB,iBAAA,GAAA,kBAAA,KAAA,CAAC,MAAD;MAAqB,WAAU;MAA/B,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;OACE,OAAM;OACN,WAAU;OAEV,UAAA,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACG,KAAK,SACJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAU;SACb,UAAA,KAAK;QACF,CAAA,GAEP,KAAK,KACH;;MACH,CAAA,GACH,KAAK,MAAM,KAAK,MAAM,OAAO;OAC5B,MAAM,YAAY,MAAM,OAAO;OAC/B,MAAM,YAAY,GAAG,KAAK,MAAM,OAAO,UAAU,IAAI,KAAK,UAAU,YAAY,WAAW,KAAK,YAAY,iBAAiB;OAC7H,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;QAEE,WAAU;QACV,cAAY;QAEX,UAAA,KAAK,UACJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAW,wEAAwE,KAAK,YAAY,wCAAwC;SAChJ,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,OAAD;UAAO,MAAM;UAAI,aAAa;UAAK,eAAY;SAAQ,CAAA;QACnD,CAAA,IAEN,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAU;SACd,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,OAAD;UAAO,MAAM;UAAI,eAAY;SAAQ,CAAA;QACjC,CAAA;OAEN,GAbG,EAaH;MAER,CAAC,CACC;KAnCK,GAAA,KAAK,KAmCV,CACL;IACI,CAAA;GACF;;CACJ,CAAA;AAET"}
|
package/lib/index64.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check, Minus } from "lucide-react";
|
|
3
|
+
//#region src/ui-library/gallery/permission-matrix/PermissionMatrix.tsx
|
|
4
|
+
/**
|
|
5
|
+
* PermissionMatrix — Design System Component
|
|
6
|
+
*
|
|
7
|
+
* A grid showing which roles have which permissions, using checkboxes
|
|
8
|
+
* or check icons. Purely presentational. No RBAC logic.
|
|
9
|
+
*
|
|
10
|
+
* Based on the "Effective Permission Matrix" pattern in RoleDetailPage.tsx
|
|
11
|
+
* but domain-free.
|
|
12
|
+
*
|
|
13
|
+
* Accessibility:
|
|
14
|
+
* - <table> with <caption>
|
|
15
|
+
* - Column headers: role names
|
|
16
|
+
* - Row headers: permission names
|
|
17
|
+
* - Cell has aria-label: "Permission X for Role Y: granted/denied"
|
|
18
|
+
*/
|
|
19
|
+
function PermissionMatrix({ roles, permissions, caption = "Permission matrix", className = "" }) {
|
|
20
|
+
return /* @__PURE__ */ jsx("div", {
|
|
21
|
+
className: `w-full overflow-x-auto rounded-xl border border-slate-200 ${className}`,
|
|
22
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
23
|
+
className: "w-full text-sm border-collapse",
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ jsx("caption", {
|
|
26
|
+
className: "sr-only",
|
|
27
|
+
children: caption
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
30
|
+
className: "border-b border-slate-100 bg-slate-50",
|
|
31
|
+
children: [/* @__PURE__ */ jsx("th", {
|
|
32
|
+
scope: "col",
|
|
33
|
+
className: "px-5 py-3 text-left text-[11px] font-bold uppercase tracking-widest text-slate-500",
|
|
34
|
+
children: "Permission"
|
|
35
|
+
}), roles.map((role) => /* @__PURE__ */ jsx("th", {
|
|
36
|
+
scope: "col",
|
|
37
|
+
className: "px-4 py-3 text-center text-[11px] font-bold uppercase tracking-widest text-slate-500",
|
|
38
|
+
children: role
|
|
39
|
+
}, role))]
|
|
40
|
+
}) }),
|
|
41
|
+
/* @__PURE__ */ jsx("tbody", {
|
|
42
|
+
className: "divide-y divide-slate-100 bg-white",
|
|
43
|
+
children: permissions.map((perm) => /* @__PURE__ */ jsxs("tr", {
|
|
44
|
+
className: "hover:bg-slate-50/60",
|
|
45
|
+
children: [/* @__PURE__ */ jsx("th", {
|
|
46
|
+
scope: "row",
|
|
47
|
+
className: "px-5 py-3 text-left text-xs font-semibold text-slate-700",
|
|
48
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
49
|
+
className: "flex items-center gap-2",
|
|
50
|
+
children: [perm.group && /* @__PURE__ */ jsx("span", {
|
|
51
|
+
className: "text-[9px] font-bold uppercase tracking-wider text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded",
|
|
52
|
+
children: perm.group
|
|
53
|
+
}), perm.label]
|
|
54
|
+
})
|
|
55
|
+
}), perm.cells.map((cell, ci) => {
|
|
56
|
+
const roleLabel = roles[ci] ?? "";
|
|
57
|
+
const cellLabel = `${perm.label} for ${roleLabel}: ${cell.granted ? "granted" : "denied"}${cell.inherited ? " (inherited)" : ""}`;
|
|
58
|
+
return /* @__PURE__ */ jsx("td", {
|
|
59
|
+
className: "px-4 py-3 text-center",
|
|
60
|
+
"aria-label": cellLabel,
|
|
61
|
+
children: cell.granted ? /* @__PURE__ */ jsx("span", {
|
|
62
|
+
className: `inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto ${cell.inherited ? "bg-brand-blue/15 text-brand-blue/60" : "bg-brand-blue text-white"}`,
|
|
63
|
+
children: /* @__PURE__ */ jsx(Check, {
|
|
64
|
+
size: 11,
|
|
65
|
+
strokeWidth: 2.5,
|
|
66
|
+
"aria-hidden": "true"
|
|
67
|
+
})
|
|
68
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
69
|
+
className: "inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto bg-slate-100 text-slate-300",
|
|
70
|
+
children: /* @__PURE__ */ jsx(Minus, {
|
|
71
|
+
size: 11,
|
|
72
|
+
"aria-hidden": "true"
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}, ci);
|
|
76
|
+
})]
|
|
77
|
+
}, perm.label))
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
export { PermissionMatrix };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=index64.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index64.mjs","names":[],"sources":["../src/ui-library/gallery/permission-matrix/PermissionMatrix.tsx"],"sourcesContent":["/**\n * PermissionMatrix — Design System Component\n *\n * A grid showing which roles have which permissions, using checkboxes\n * or check icons. Purely presentational. No RBAC logic.\n *\n * Based on the \"Effective Permission Matrix\" pattern in RoleDetailPage.tsx\n * but domain-free.\n *\n * Accessibility:\n * - <table> with <caption>\n * - Column headers: role names\n * - Row headers: permission names\n * - Cell has aria-label: \"Permission X for Role Y: granted/denied\"\n */\n\nimport { Check, Minus } from 'lucide-react';\n\nexport interface PermissionMatrixCell {\n granted: boolean;\n /** If true, the cell is read-only and visually subdued */\n inherited?: boolean;\n}\n\nexport interface PermissionMatrixProps {\n /** Column headers (roles) */\n roles: string[];\n /** Row definitions */\n permissions: Array<{\n label: string;\n group?: string;\n cells: PermissionMatrixCell[];\n }>;\n caption?: string;\n className?: string;\n}\n\nexport function PermissionMatrix({\n roles,\n permissions,\n caption = 'Permission matrix',\n className = '',\n}: PermissionMatrixProps) {\n return (\n <div className={`w-full overflow-x-auto rounded-xl border border-slate-200 ${className}`}>\n <table className=\"w-full text-sm border-collapse\">\n <caption className=\"sr-only\">{caption}</caption>\n <thead>\n <tr className=\"border-b border-slate-100 bg-slate-50\">\n <th scope=\"col\" className=\"px-5 py-3 text-left text-[11px] font-bold uppercase tracking-widest text-slate-500\">\n Permission\n </th>\n {roles.map((role) => (\n <th\n key={role}\n scope=\"col\"\n className=\"px-4 py-3 text-center text-[11px] font-bold uppercase tracking-widest text-slate-500\"\n >\n {role}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-slate-100 bg-white\">\n {permissions.map((perm) => (\n <tr key={perm.label} className=\"hover:bg-slate-50/60\">\n <th\n scope=\"row\"\n className=\"px-5 py-3 text-left text-xs font-semibold text-slate-700\"\n >\n <div className=\"flex items-center gap-2\">\n {perm.group && (\n <span className=\"text-[9px] font-bold uppercase tracking-wider text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded\">\n {perm.group}\n </span>\n )}\n {perm.label}\n </div>\n </th>\n {perm.cells.map((cell, ci) => {\n const roleLabel = roles[ci] ?? '';\n const cellLabel = `${perm.label} for ${roleLabel}: ${cell.granted ? 'granted' : 'denied'}${cell.inherited ? ' (inherited)' : ''}`;\n return (\n <td\n key={ci}\n className=\"px-4 py-3 text-center\"\n aria-label={cellLabel}\n >\n {cell.granted ? (\n <span className={`inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto ${cell.inherited ? 'bg-brand-blue/15 text-brand-blue/60' : 'bg-brand-blue text-white'}`}>\n <Check size={11} strokeWidth={2.5} aria-hidden=\"true\" />\n </span>\n ) : (\n <span className=\"inline-flex items-center justify-center h-5 w-5 rounded-full mx-auto bg-slate-100 text-slate-300\">\n <Minus size={11} aria-hidden=\"true\" />\n </span>\n )}\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,SAAgB,iBAAiB,EAC/B,OACA,aACA,UAAU,qBACV,YAAY,MACY;CACxB,OACE,oBAAC,OAAD;EAAK,WAAW,6DAA6D;EAC3E,UAAA,qBAAC,SAAD;GAAO,WAAU;GAAjB,UAAA;IACE,oBAAC,WAAD;KAAS,WAAU;KAAW,UAAA;IAAiB,CAAA;IAC/C,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD;KAAI,WAAU;KAAd,UAAA,CACE,oBAAC,MAAD;MAAI,OAAM;MAAM,WAAU;MAAqF,UAAA;KAE3G,CAAA,GACH,MAAM,KAAK,SACV,oBAAC,MAAD;MAEE,OAAM;MACN,WAAU;MAET,UAAA;KACC,GALG,IAKH,CACL,CACC;IACC,CAAA,EAAA,CAAA;IACP,oBAAC,SAAD;KAAO,WAAU;KACd,UAAA,YAAY,KAAK,SAChB,qBAAC,MAAD;MAAqB,WAAU;MAA/B,UAAA,CACE,oBAAC,MAAD;OACE,OAAM;OACN,WAAU;OAEV,UAAA,qBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACG,KAAK,SACJ,oBAAC,QAAD;SAAM,WAAU;SACb,UAAA,KAAK;QACF,CAAA,GAEP,KAAK,KACH;;MACH,CAAA,GACH,KAAK,MAAM,KAAK,MAAM,OAAO;OAC5B,MAAM,YAAY,MAAM,OAAO;OAC/B,MAAM,YAAY,GAAG,KAAK,MAAM,OAAO,UAAU,IAAI,KAAK,UAAU,YAAY,WAAW,KAAK,YAAY,iBAAiB;OAC7H,OACE,oBAAC,MAAD;QAEE,WAAU;QACV,cAAY;QAEX,UAAA,KAAK,UACJ,oBAAC,QAAD;SAAM,WAAW,wEAAwE,KAAK,YAAY,wCAAwC;SAChJ,UAAA,oBAAC,OAAD;UAAO,MAAM;UAAI,aAAa;UAAK,eAAY;SAAQ,CAAA;QACnD,CAAA,IAEN,oBAAC,QAAD;SAAM,WAAU;SACd,UAAA,oBAAC,OAAD;UAAO,MAAM;UAAI,eAAY;SAAQ,CAAA;QACjC,CAAA;OAEN,GAbG,EAaH;MAER,CAAC,CACC;KAnCK,GAAA,KAAK,KAmCV,CACL;IACI,CAAA;GACF;;CACJ,CAAA;AAET"}
|
package/lib/index65.cjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let lucide_react = require("lucide-react");
|
|
4
|
+
//#region src/ui-library/gallery/calendar/Calendar.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Calendar — Design System Component
|
|
7
|
+
*
|
|
8
|
+
* A standalone month-view calendar grid for date selection and display.
|
|
9
|
+
* Controlled or uncontrolled. No time support.
|
|
10
|
+
*
|
|
11
|
+
* Accessibility:
|
|
12
|
+
* - role="grid" for the calendar table
|
|
13
|
+
* - role="gridcell" for each day cell
|
|
14
|
+
* - aria-label on each cell: "Day Month Year, [Today|Selected]"
|
|
15
|
+
* - aria-selected on selected day
|
|
16
|
+
* - aria-current="date" on today
|
|
17
|
+
* - Keyboard: ArrowKeys navigate days, Enter/Space select
|
|
18
|
+
* - Previous/Next month buttons have aria-label
|
|
19
|
+
*/
|
|
20
|
+
var MONTHS = [
|
|
21
|
+
"January",
|
|
22
|
+
"February",
|
|
23
|
+
"March",
|
|
24
|
+
"April",
|
|
25
|
+
"May",
|
|
26
|
+
"June",
|
|
27
|
+
"July",
|
|
28
|
+
"August",
|
|
29
|
+
"September",
|
|
30
|
+
"October",
|
|
31
|
+
"November",
|
|
32
|
+
"December"
|
|
33
|
+
];
|
|
34
|
+
var DAYS = [
|
|
35
|
+
"Su",
|
|
36
|
+
"Mo",
|
|
37
|
+
"Tu",
|
|
38
|
+
"We",
|
|
39
|
+
"Th",
|
|
40
|
+
"Fr",
|
|
41
|
+
"Sa"
|
|
42
|
+
];
|
|
43
|
+
function isSameDay(a, b) {
|
|
44
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
45
|
+
}
|
|
46
|
+
function Calendar({ value, defaultValue, onChange, minDate, maxDate, className = "" }) {
|
|
47
|
+
const today = /* @__PURE__ */ new Date();
|
|
48
|
+
today.setHours(0, 0, 0, 0);
|
|
49
|
+
const [internal, setInternal] = (0, react.useState)(defaultValue ?? null);
|
|
50
|
+
const selected = value !== void 0 ? value : internal;
|
|
51
|
+
const [viewYear, setViewYear] = (0, react.useState)((selected ?? today).getFullYear());
|
|
52
|
+
const [viewMonth, setViewMonth] = (0, react.useState)((selected ?? today).getMonth());
|
|
53
|
+
const firstDay = new Date(viewYear, viewMonth, 1).getDay();
|
|
54
|
+
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
|
|
55
|
+
const prevMonth = () => {
|
|
56
|
+
if (viewMonth === 0) {
|
|
57
|
+
setViewMonth(11);
|
|
58
|
+
setViewYear((y) => y - 1);
|
|
59
|
+
} else setViewMonth((m) => m - 1);
|
|
60
|
+
};
|
|
61
|
+
const nextMonth = () => {
|
|
62
|
+
if (viewMonth === 11) {
|
|
63
|
+
setViewMonth(0);
|
|
64
|
+
setViewYear((y) => y + 1);
|
|
65
|
+
} else setViewMonth((m) => m + 1);
|
|
66
|
+
};
|
|
67
|
+
const handleSelect = (day) => {
|
|
68
|
+
const date = new Date(viewYear, viewMonth, day);
|
|
69
|
+
if (minDate && date < minDate) return;
|
|
70
|
+
if (maxDate && date > maxDate) return;
|
|
71
|
+
if (!value) setInternal(date);
|
|
72
|
+
onChange?.(date);
|
|
73
|
+
};
|
|
74
|
+
const cells = [...Array(firstDay).fill(null), ...Array.from({ length: daysInMonth }, (_, i) => i + 1)];
|
|
75
|
+
while (cells.length % 7 !== 0) cells.push(null);
|
|
76
|
+
const weeks = [];
|
|
77
|
+
for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
|
|
78
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
79
|
+
className: `inline-block select-none rounded-2xl border border-slate-200 bg-white p-4 shadow-sm ${className}`,
|
|
80
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
81
|
+
className: "mb-3 flex items-center justify-between",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
84
|
+
type: "button",
|
|
85
|
+
onClick: prevMonth,
|
|
86
|
+
"aria-label": "Previous month",
|
|
87
|
+
className: "rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
88
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, {
|
|
89
|
+
size: 16,
|
|
90
|
+
"aria-hidden": "true"
|
|
91
|
+
})
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
94
|
+
className: "text-sm font-bold text-slate-800",
|
|
95
|
+
children: [
|
|
96
|
+
MONTHS[viewMonth],
|
|
97
|
+
" ",
|
|
98
|
+
viewYear
|
|
99
|
+
]
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
102
|
+
type: "button",
|
|
103
|
+
onClick: nextMonth,
|
|
104
|
+
"aria-label": "Next month",
|
|
105
|
+
className: "rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
106
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
|
|
107
|
+
size: 16,
|
|
108
|
+
"aria-hidden": "true"
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
113
|
+
role: "grid",
|
|
114
|
+
"aria-label": `${MONTHS[viewMonth]} ${viewYear}`,
|
|
115
|
+
className: "w-full border-collapse",
|
|
116
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: DAYS.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
117
|
+
scope: "col",
|
|
118
|
+
className: "pb-2 text-center text-[10px] font-bold uppercase tracking-widest text-slate-400",
|
|
119
|
+
children: d
|
|
120
|
+
}, d)) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: weeks.map((week, wi) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: week.map((day, di) => {
|
|
121
|
+
if (!day) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
122
|
+
role: "gridcell",
|
|
123
|
+
"aria-label": "Empty"
|
|
124
|
+
}, di);
|
|
125
|
+
const date = new Date(viewYear, viewMonth, day);
|
|
126
|
+
const isToday = isSameDay(date, today);
|
|
127
|
+
const isSelected = !!selected && isSameDay(date, selected);
|
|
128
|
+
const isDisabled = (minDate ? date < minDate : false) || (maxDate ? date > maxDate : false);
|
|
129
|
+
const label = `${day} ${MONTHS[viewMonth]} ${viewYear}${isToday ? ", Today" : ""}${isSelected ? ", Selected" : ""}`;
|
|
130
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
131
|
+
role: "gridcell",
|
|
132
|
+
"aria-label": label,
|
|
133
|
+
"aria-selected": isSelected,
|
|
134
|
+
className: "p-0.5",
|
|
135
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
136
|
+
type: "button",
|
|
137
|
+
onClick: () => handleSelect(day),
|
|
138
|
+
disabled: isDisabled,
|
|
139
|
+
"aria-current": isToday ? "date" : void 0,
|
|
140
|
+
className: [
|
|
141
|
+
"flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition",
|
|
142
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
143
|
+
"disabled:cursor-not-allowed disabled:opacity-30",
|
|
144
|
+
isSelected ? "bg-brand-blue text-white shadow-md" : isToday ? "border border-brand-blue/60 text-brand-blue hover:bg-blue-50" : "text-slate-700 hover:bg-slate-100"
|
|
145
|
+
].join(" "),
|
|
146
|
+
children: day
|
|
147
|
+
})
|
|
148
|
+
}, di);
|
|
149
|
+
}) }, wi)) })]
|
|
150
|
+
})]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
exports.Calendar = Calendar;
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=index65.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index65.cjs","names":[],"sources":["../src/ui-library/gallery/calendar/Calendar.tsx"],"sourcesContent":["/**\n * Calendar — Design System Component\n *\n * A standalone month-view calendar grid for date selection and display.\n * Controlled or uncontrolled. No time support.\n *\n * Accessibility:\n * - role=\"grid\" for the calendar table\n * - role=\"gridcell\" for each day cell\n * - aria-label on each cell: \"Day Month Year, [Today|Selected]\"\n * - aria-selected on selected day\n * - aria-current=\"date\" on today\n * - Keyboard: ArrowKeys navigate days, Enter/Space select\n * - Previous/Next month buttons have aria-label\n */\n\nimport { useState } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\nexport interface CalendarProps {\n /** Selected date (controlled). */\n value?: Date | null;\n /** Default selected date (uncontrolled). */\n defaultValue?: Date | null;\n /** Called when a date is selected. */\n onChange?: (date: Date) => void;\n /** Minimum selectable date. */\n minDate?: Date;\n /** Maximum selectable date. */\n maxDate?: Date;\n /** Additional class on the root element. */\n className?: string;\n}\n\nconst MONTHS = [\n 'January','February','March','April','May','June',\n 'July','August','September','October','November','December',\n];\nconst DAYS = ['Su','Mo','Tu','We','Th','Fr','Sa'];\n\nfunction isSameDay(a: Date, b: Date) {\n return a.getFullYear() === b.getFullYear()\n && a.getMonth() === b.getMonth()\n && a.getDate() === b.getDate();\n}\n\nexport function Calendar({\n value,\n defaultValue,\n onChange,\n minDate,\n maxDate,\n className = '',\n}: CalendarProps) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n const [internal, setInternal] = useState<Date | null>(defaultValue ?? null);\n const selected = value !== undefined ? value : internal;\n\n const [viewYear, setViewYear] = useState(\n (selected ?? today).getFullYear(),\n );\n const [viewMonth, setViewMonth] = useState(\n (selected ?? today).getMonth(),\n );\n\n const firstDay = new Date(viewYear, viewMonth, 1).getDay();\n const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();\n\n const prevMonth = () => {\n if (viewMonth === 0) { setViewMonth(11); setViewYear(y => y - 1); }\n else setViewMonth(m => m - 1);\n };\n const nextMonth = () => {\n if (viewMonth === 11) { setViewMonth(0); setViewYear(y => y + 1); }\n else setViewMonth(m => m + 1);\n };\n\n const handleSelect = (day: number) => {\n const date = new Date(viewYear, viewMonth, day);\n if (minDate && date < minDate) return;\n if (maxDate && date > maxDate) return;\n if (!value) setInternal(date);\n onChange?.(date);\n };\n\n // Build calendar grid\n const cells: Array<number | null> = [\n ...Array(firstDay).fill(null),\n ...Array.from({ length: daysInMonth }, (_, i) => i + 1),\n ];\n // Pad to complete last row\n while (cells.length % 7 !== 0) cells.push(null);\n const weeks = [];\n for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));\n\n return (\n <div className={`inline-block select-none rounded-2xl border border-slate-200 bg-white p-4 shadow-sm ${className}`}>\n {/* Header */}\n <div className=\"mb-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={prevMonth}\n aria-label=\"Previous month\"\n className=\"rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <ChevronLeft size={16} aria-hidden=\"true\" />\n </button>\n <span className=\"text-sm font-bold text-slate-800\">\n {MONTHS[viewMonth]} {viewYear}\n </span>\n <button\n type=\"button\"\n onClick={nextMonth}\n aria-label=\"Next month\"\n className=\"rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <ChevronRight size={16} aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Grid */}\n <table\n role=\"grid\"\n aria-label={`${MONTHS[viewMonth]} ${viewYear}`}\n className=\"w-full border-collapse\"\n >\n <thead>\n <tr>\n {DAYS.map((d) => (\n <th key={d} scope=\"col\" className=\"pb-2 text-center text-[10px] font-bold uppercase tracking-widest text-slate-400\">\n {d}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {weeks.map((week, wi) => (\n <tr key={wi}>\n {week.map((day, di) => {\n if (!day) return <td key={di} role=\"gridcell\" aria-label=\"Empty\" />;\n const date = new Date(viewYear, viewMonth, day);\n const isToday = isSameDay(date, today);\n const isSelected = !!selected && isSameDay(date, selected);\n const isDisabled = (minDate ? date < minDate : false) || (maxDate ? date > maxDate : false);\n const label = `${day} ${MONTHS[viewMonth]} ${viewYear}${isToday ? ', Today' : ''}${isSelected ? ', Selected' : ''}`;\n\n return (\n <td key={di} role=\"gridcell\" aria-label={label} aria-selected={isSelected} className=\"p-0.5\">\n <button\n type=\"button\"\n onClick={() => handleSelect(day)}\n disabled={isDisabled}\n aria-current={isToday ? 'date' : undefined}\n className={[\n 'flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n 'disabled:cursor-not-allowed disabled:opacity-30',\n isSelected\n ? 'bg-brand-blue text-white shadow-md'\n : isToday\n ? 'border border-brand-blue/60 text-brand-blue hover:bg-blue-50'\n : 'text-slate-700 hover:bg-slate-100',\n ].join(' ')}\n >\n {day}\n </button>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/lib/index65.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
//#region src/ui-library/gallery/calendar/Calendar.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Calendar — Design System Component
|
|
7
|
+
*
|
|
8
|
+
* A standalone month-view calendar grid for date selection and display.
|
|
9
|
+
* Controlled or uncontrolled. No time support.
|
|
10
|
+
*
|
|
11
|
+
* Accessibility:
|
|
12
|
+
* - role="grid" for the calendar table
|
|
13
|
+
* - role="gridcell" for each day cell
|
|
14
|
+
* - aria-label on each cell: "Day Month Year, [Today|Selected]"
|
|
15
|
+
* - aria-selected on selected day
|
|
16
|
+
* - aria-current="date" on today
|
|
17
|
+
* - Keyboard: ArrowKeys navigate days, Enter/Space select
|
|
18
|
+
* - Previous/Next month buttons have aria-label
|
|
19
|
+
*/
|
|
20
|
+
var MONTHS = [
|
|
21
|
+
"January",
|
|
22
|
+
"February",
|
|
23
|
+
"March",
|
|
24
|
+
"April",
|
|
25
|
+
"May",
|
|
26
|
+
"June",
|
|
27
|
+
"July",
|
|
28
|
+
"August",
|
|
29
|
+
"September",
|
|
30
|
+
"October",
|
|
31
|
+
"November",
|
|
32
|
+
"December"
|
|
33
|
+
];
|
|
34
|
+
var DAYS = [
|
|
35
|
+
"Su",
|
|
36
|
+
"Mo",
|
|
37
|
+
"Tu",
|
|
38
|
+
"We",
|
|
39
|
+
"Th",
|
|
40
|
+
"Fr",
|
|
41
|
+
"Sa"
|
|
42
|
+
];
|
|
43
|
+
function isSameDay(a, b) {
|
|
44
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
45
|
+
}
|
|
46
|
+
function Calendar({ value, defaultValue, onChange, minDate, maxDate, className = "" }) {
|
|
47
|
+
const today = /* @__PURE__ */ new Date();
|
|
48
|
+
today.setHours(0, 0, 0, 0);
|
|
49
|
+
const [internal, setInternal] = useState(defaultValue ?? null);
|
|
50
|
+
const selected = value !== void 0 ? value : internal;
|
|
51
|
+
const [viewYear, setViewYear] = useState((selected ?? today).getFullYear());
|
|
52
|
+
const [viewMonth, setViewMonth] = useState((selected ?? today).getMonth());
|
|
53
|
+
const firstDay = new Date(viewYear, viewMonth, 1).getDay();
|
|
54
|
+
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
|
|
55
|
+
const prevMonth = () => {
|
|
56
|
+
if (viewMonth === 0) {
|
|
57
|
+
setViewMonth(11);
|
|
58
|
+
setViewYear((y) => y - 1);
|
|
59
|
+
} else setViewMonth((m) => m - 1);
|
|
60
|
+
};
|
|
61
|
+
const nextMonth = () => {
|
|
62
|
+
if (viewMonth === 11) {
|
|
63
|
+
setViewMonth(0);
|
|
64
|
+
setViewYear((y) => y + 1);
|
|
65
|
+
} else setViewMonth((m) => m + 1);
|
|
66
|
+
};
|
|
67
|
+
const handleSelect = (day) => {
|
|
68
|
+
const date = new Date(viewYear, viewMonth, day);
|
|
69
|
+
if (minDate && date < minDate) return;
|
|
70
|
+
if (maxDate && date > maxDate) return;
|
|
71
|
+
if (!value) setInternal(date);
|
|
72
|
+
onChange?.(date);
|
|
73
|
+
};
|
|
74
|
+
const cells = [...Array(firstDay).fill(null), ...Array.from({ length: daysInMonth }, (_, i) => i + 1)];
|
|
75
|
+
while (cells.length % 7 !== 0) cells.push(null);
|
|
76
|
+
const weeks = [];
|
|
77
|
+
for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
|
|
78
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
79
|
+
className: `inline-block select-none rounded-2xl border border-slate-200 bg-white p-4 shadow-sm ${className}`,
|
|
80
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
81
|
+
className: "mb-3 flex items-center justify-between",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx("button", {
|
|
84
|
+
type: "button",
|
|
85
|
+
onClick: prevMonth,
|
|
86
|
+
"aria-label": "Previous month",
|
|
87
|
+
className: "rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
88
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, {
|
|
89
|
+
size: 16,
|
|
90
|
+
"aria-hidden": "true"
|
|
91
|
+
})
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ jsxs("span", {
|
|
94
|
+
className: "text-sm font-bold text-slate-800",
|
|
95
|
+
children: [
|
|
96
|
+
MONTHS[viewMonth],
|
|
97
|
+
" ",
|
|
98
|
+
viewYear
|
|
99
|
+
]
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ jsx("button", {
|
|
102
|
+
type: "button",
|
|
103
|
+
onClick: nextMonth,
|
|
104
|
+
"aria-label": "Next month",
|
|
105
|
+
className: "rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
106
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
107
|
+
size: 16,
|
|
108
|
+
"aria-hidden": "true"
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
}), /* @__PURE__ */ jsxs("table", {
|
|
113
|
+
role: "grid",
|
|
114
|
+
"aria-label": `${MONTHS[viewMonth]} ${viewYear}`,
|
|
115
|
+
className: "w-full border-collapse",
|
|
116
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: DAYS.map((d) => /* @__PURE__ */ jsx("th", {
|
|
117
|
+
scope: "col",
|
|
118
|
+
className: "pb-2 text-center text-[10px] font-bold uppercase tracking-widest text-slate-400",
|
|
119
|
+
children: d
|
|
120
|
+
}, d)) }) }), /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, wi) => /* @__PURE__ */ jsx("tr", { children: week.map((day, di) => {
|
|
121
|
+
if (!day) return /* @__PURE__ */ jsx("td", {
|
|
122
|
+
role: "gridcell",
|
|
123
|
+
"aria-label": "Empty"
|
|
124
|
+
}, di);
|
|
125
|
+
const date = new Date(viewYear, viewMonth, day);
|
|
126
|
+
const isToday = isSameDay(date, today);
|
|
127
|
+
const isSelected = !!selected && isSameDay(date, selected);
|
|
128
|
+
const isDisabled = (minDate ? date < minDate : false) || (maxDate ? date > maxDate : false);
|
|
129
|
+
const label = `${day} ${MONTHS[viewMonth]} ${viewYear}${isToday ? ", Today" : ""}${isSelected ? ", Selected" : ""}`;
|
|
130
|
+
return /* @__PURE__ */ jsx("td", {
|
|
131
|
+
role: "gridcell",
|
|
132
|
+
"aria-label": label,
|
|
133
|
+
"aria-selected": isSelected,
|
|
134
|
+
className: "p-0.5",
|
|
135
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
136
|
+
type: "button",
|
|
137
|
+
onClick: () => handleSelect(day),
|
|
138
|
+
disabled: isDisabled,
|
|
139
|
+
"aria-current": isToday ? "date" : void 0,
|
|
140
|
+
className: [
|
|
141
|
+
"flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition",
|
|
142
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
143
|
+
"disabled:cursor-not-allowed disabled:opacity-30",
|
|
144
|
+
isSelected ? "bg-brand-blue text-white shadow-md" : isToday ? "border border-brand-blue/60 text-brand-blue hover:bg-blue-50" : "text-slate-700 hover:bg-slate-100"
|
|
145
|
+
].join(" "),
|
|
146
|
+
children: day
|
|
147
|
+
})
|
|
148
|
+
}, di);
|
|
149
|
+
}) }, wi)) })]
|
|
150
|
+
})]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { Calendar };
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=index65.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index65.mjs","names":[],"sources":["../src/ui-library/gallery/calendar/Calendar.tsx"],"sourcesContent":["/**\n * Calendar — Design System Component\n *\n * A standalone month-view calendar grid for date selection and display.\n * Controlled or uncontrolled. No time support.\n *\n * Accessibility:\n * - role=\"grid\" for the calendar table\n * - role=\"gridcell\" for each day cell\n * - aria-label on each cell: \"Day Month Year, [Today|Selected]\"\n * - aria-selected on selected day\n * - aria-current=\"date\" on today\n * - Keyboard: ArrowKeys navigate days, Enter/Space select\n * - Previous/Next month buttons have aria-label\n */\n\nimport { useState } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\nexport interface CalendarProps {\n /** Selected date (controlled). */\n value?: Date | null;\n /** Default selected date (uncontrolled). */\n defaultValue?: Date | null;\n /** Called when a date is selected. */\n onChange?: (date: Date) => void;\n /** Minimum selectable date. */\n minDate?: Date;\n /** Maximum selectable date. */\n maxDate?: Date;\n /** Additional class on the root element. */\n className?: string;\n}\n\nconst MONTHS = [\n 'January','February','March','April','May','June',\n 'July','August','September','October','November','December',\n];\nconst DAYS = ['Su','Mo','Tu','We','Th','Fr','Sa'];\n\nfunction isSameDay(a: Date, b: Date) {\n return a.getFullYear() === b.getFullYear()\n && a.getMonth() === b.getMonth()\n && a.getDate() === b.getDate();\n}\n\nexport function Calendar({\n value,\n defaultValue,\n onChange,\n minDate,\n maxDate,\n className = '',\n}: CalendarProps) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n const [internal, setInternal] = useState<Date | null>(defaultValue ?? null);\n const selected = value !== undefined ? value : internal;\n\n const [viewYear, setViewYear] = useState(\n (selected ?? today).getFullYear(),\n );\n const [viewMonth, setViewMonth] = useState(\n (selected ?? today).getMonth(),\n );\n\n const firstDay = new Date(viewYear, viewMonth, 1).getDay();\n const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();\n\n const prevMonth = () => {\n if (viewMonth === 0) { setViewMonth(11); setViewYear(y => y - 1); }\n else setViewMonth(m => m - 1);\n };\n const nextMonth = () => {\n if (viewMonth === 11) { setViewMonth(0); setViewYear(y => y + 1); }\n else setViewMonth(m => m + 1);\n };\n\n const handleSelect = (day: number) => {\n const date = new Date(viewYear, viewMonth, day);\n if (minDate && date < minDate) return;\n if (maxDate && date > maxDate) return;\n if (!value) setInternal(date);\n onChange?.(date);\n };\n\n // Build calendar grid\n const cells: Array<number | null> = [\n ...Array(firstDay).fill(null),\n ...Array.from({ length: daysInMonth }, (_, i) => i + 1),\n ];\n // Pad to complete last row\n while (cells.length % 7 !== 0) cells.push(null);\n const weeks = [];\n for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));\n\n return (\n <div className={`inline-block select-none rounded-2xl border border-slate-200 bg-white p-4 shadow-sm ${className}`}>\n {/* Header */}\n <div className=\"mb-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={prevMonth}\n aria-label=\"Previous month\"\n className=\"rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <ChevronLeft size={16} aria-hidden=\"true\" />\n </button>\n <span className=\"text-sm font-bold text-slate-800\">\n {MONTHS[viewMonth]} {viewYear}\n </span>\n <button\n type=\"button\"\n onClick={nextMonth}\n aria-label=\"Next month\"\n className=\"rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <ChevronRight size={16} aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Grid */}\n <table\n role=\"grid\"\n aria-label={`${MONTHS[viewMonth]} ${viewYear}`}\n className=\"w-full border-collapse\"\n >\n <thead>\n <tr>\n {DAYS.map((d) => (\n <th key={d} scope=\"col\" className=\"pb-2 text-center text-[10px] font-bold uppercase tracking-widest text-slate-400\">\n {d}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {weeks.map((week, wi) => (\n <tr key={wi}>\n {week.map((day, di) => {\n if (!day) return <td key={di} role=\"gridcell\" aria-label=\"Empty\" />;\n const date = new Date(viewYear, viewMonth, day);\n const isToday = isSameDay(date, today);\n const isSelected = !!selected && isSameDay(date, selected);\n const isDisabled = (minDate ? date < minDate : false) || (maxDate ? date > maxDate : false);\n const label = `${day} ${MONTHS[viewMonth]} ${viewYear}${isToday ? ', Today' : ''}${isSelected ? ', Selected' : ''}`;\n\n return (\n <td key={di} role=\"gridcell\" aria-label={label} aria-selected={isSelected} className=\"p-0.5\">\n <button\n type=\"button\"\n onClick={() => handleSelect(day)}\n disabled={isDisabled}\n aria-current={isToday ? 'date' : undefined}\n className={[\n 'flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n 'disabled:cursor-not-allowed disabled:opacity-30',\n isSelected\n ? 'bg-brand-blue text-white shadow-md'\n : isToday\n ? 'border border-brand-blue/60 text-brand-blue hover:bg-blue-50'\n : 'text-slate-700 hover:bg-slate-100',\n ].join(' ')}\n >\n {day}\n </button>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":"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"}
|