@godxjp/ui 18.1.0 → 18.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/app-provider.d.ts +2 -1
- package/dist/components/charts/area-chart.d.ts +1 -1
- package/dist/components/charts/bar-chart.d.ts +1 -1
- package/dist/components/charts/chart-cartesian.d.ts +1 -1
- package/dist/components/charts/chart-frame.d.ts +1 -1
- package/dist/components/charts/line-chart.d.ts +1 -1
- package/dist/components/charts/pie-chart.d.ts +1 -1
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/card.d.ts +1 -1
- package/dist/components/data-display/credential-reveal.d.ts +2 -0
- package/dist/components/data-display/credential-reveal.js +5 -0
- package/dist/components/data-display/data-table.d.ts +10 -10
- package/dist/components/data-display/descriptions.d.ts +2 -2
- package/dist/components/data-display/empty-state.d.ts +1 -1
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/popover.d.ts +6 -6
- package/dist/components/data-display/progress.d.ts +2 -1
- package/dist/components/data-display/timeline.d.ts +1 -1
- package/dist/components/data-display/tree-list.d.ts +1 -1
- package/dist/components/data-entry/calendar.d.ts +1 -1
- package/dist/components/data-entry/cascader.d.ts +2 -1
- package/dist/components/data-entry/checkbox-group.d.ts +2 -1
- package/dist/components/data-entry/color-picker.d.ts +2 -1
- package/dist/components/data-entry/date-picker.d.ts +2 -1
- package/dist/components/data-entry/date-range-picker.d.ts +2 -1
- package/dist/components/data-entry/field.d.ts +2 -1
- package/dist/components/data-entry/form-field.d.ts +2 -1
- package/dist/components/data-entry/month-picker.d.ts +2 -1
- package/dist/components/data-entry/month-range-picker.d.ts +2 -1
- package/dist/components/data-entry/password-strength.d.ts +1 -1
- package/dist/components/data-entry/radio.d.ts +1 -1
- package/dist/components/data-entry/search-input.d.ts +1 -1
- package/dist/components/data-entry/search-select.d.ts +2 -1
- package/dist/components/data-entry/select.d.ts +3 -3
- package/dist/components/data-entry/time-picker.d.ts +2 -1
- package/dist/components/data-entry/transfer.d.ts +2 -1
- package/dist/components/data-entry/tree-select.d.ts +2 -1
- package/dist/components/data-entry/upload-crop-dialog.d.ts +2 -1
- package/dist/components/data-entry/upload.d.ts +2 -1
- package/dist/components/feedback/alert.d.ts +1 -1
- package/dist/components/feedback/dialog.d.ts +21 -14
- package/dist/components/feedback/dialog.js +43 -11
- package/dist/components/feedback/sheet.d.ts +7 -7
- package/dist/components/feedback/skeleton.d.ts +5 -5
- package/dist/components/feedback/sonner.d.ts +2 -1
- package/dist/components/feedback/tooltip.d.ts +2 -2
- package/dist/components/general/button.js +1 -1
- package/dist/components/general/reveal.d.ts +1 -1
- package/dist/components/layout/app-shell.d.ts +2 -1
- package/dist/components/layout/app-shell.js +1 -1
- package/dist/components/layout/auth-shell.d.ts +1 -1
- package/dist/components/layout/breadcrumb.d.ts +1 -1
- package/dist/components/layout/centered-shell.d.ts +1 -1
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/page-container.d.ts +2 -2
- package/dist/components/layout/resizable.d.ts +3 -3
- package/dist/components/layout/responsive-grid.d.ts +1 -1
- package/dist/components/layout/sidebar.d.ts +4 -4
- package/dist/components/layout/split-pane.d.ts +1 -1
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/navigation/context-menu.d.ts +2 -2
- package/dist/components/navigation/dropdown-menu.d.ts +7 -7
- package/dist/components/navigation/filter-bar.d.ts +2 -2
- package/dist/components/navigation/filter-bar.js +8 -2
- package/dist/components/navigation/menubar.d.ts +3 -3
- package/dist/components/navigation/pagination.d.ts +2 -2
- package/dist/components/navigation/steps.d.ts +2 -1
- package/dist/components/navigation/tabs.d.ts +1 -1
- package/dist/components/query/data-state.d.ts +2 -1
- package/dist/components/query/infinite-query-state.d.ts +2 -1
- package/dist/components/query/mutation-feedback.d.ts +1 -1
- package/dist/components/query/prefetch-link.d.ts +2 -1
- package/dist/components/query/query-refetch-button.d.ts +1 -1
- package/dist/components/ui/credential-reveal.d.ts +12 -0
- package/dist/components/ui/credential-reveal.js +160 -0
- package/dist/components/ui/hover-card.d.ts +1 -1
- package/dist/form/form-field-control.d.ts +1 -1
- package/dist/form/form-root.d.ts +1 -1
- package/dist/i18n/messages/en.json +14 -1
- package/dist/i18n/messages/ja.json +14 -1
- package/dist/i18n/messages/vi.json +14 -1
- package/dist/lib/permission-grid.d.ts +59 -0
- package/dist/lib/permission-grid.js +32 -0
- package/dist/props/components/data-display.prop.d.ts +49 -1
- package/dist/props/components/feedback.prop.d.ts +4 -0
- package/dist/props/components/navigation.prop.d.ts +8 -1
- package/dist/props/registry.d.ts +19 -1
- package/dist/props/registry.js +35 -1
- package/dist/props/vocabulary/data.prop.d.ts +6 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +9 -1
- package/dist/styles/data-display-layout.css +50 -0
- package/dist/styles/layout.css +10 -0
- package/dist/styles/shell-layout.css +19 -11
- package/dist/tokens/components/navigation.css +7 -0
- package/dist/tokens/foundation.css +8 -4
- package/package.json +55 -46
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check, Copy, Download, Eye, EyeOff } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { Text } from "../general/typography.js";
|
|
8
|
+
import { Button } from "../general/button.js";
|
|
9
|
+
import { Alert, AlertDescription } from "../feedback/alert.js";
|
|
10
|
+
const MASK = "\u2022".repeat(24);
|
|
11
|
+
const COPIED_RESET_MS = 2e3;
|
|
12
|
+
const CredentialReveal = React.forwardRef(
|
|
13
|
+
({
|
|
14
|
+
secret,
|
|
15
|
+
label,
|
|
16
|
+
warning,
|
|
17
|
+
revealed,
|
|
18
|
+
defaultRevealed = false,
|
|
19
|
+
onRevealedChange,
|
|
20
|
+
onCopy,
|
|
21
|
+
onAcknowledge,
|
|
22
|
+
acknowledgeLabel,
|
|
23
|
+
downloadable = false,
|
|
24
|
+
downloadFileName = "credential.txt",
|
|
25
|
+
size = "md",
|
|
26
|
+
tone = "warning",
|
|
27
|
+
className,
|
|
28
|
+
id,
|
|
29
|
+
"aria-label": ariaLabel
|
|
30
|
+
}, ref) => {
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const [internalRevealed, setInternalRevealed] = React.useState(defaultRevealed);
|
|
33
|
+
const isRevealed = revealed ?? internalRevealed;
|
|
34
|
+
const [copied, setCopied] = React.useState(false);
|
|
35
|
+
const copyTimer = React.useRef(null);
|
|
36
|
+
const labelId = React.useId();
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
if (revealed === void 0) setInternalRevealed(defaultRevealed);
|
|
39
|
+
setCopied(false);
|
|
40
|
+
}, [secret]);
|
|
41
|
+
React.useEffect(
|
|
42
|
+
() => () => {
|
|
43
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
44
|
+
},
|
|
45
|
+
[]
|
|
46
|
+
);
|
|
47
|
+
const setRevealed = (next) => {
|
|
48
|
+
if (revealed === void 0) setInternalRevealed(next);
|
|
49
|
+
onRevealedChange?.(next);
|
|
50
|
+
};
|
|
51
|
+
const copy = async () => {
|
|
52
|
+
try {
|
|
53
|
+
await navigator.clipboard.writeText(secret);
|
|
54
|
+
setCopied(true);
|
|
55
|
+
onCopy?.(secret);
|
|
56
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
57
|
+
copyTimer.current = setTimeout(() => setCopied(false), COPIED_RESET_MS);
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const download = () => {
|
|
62
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return;
|
|
63
|
+
const url = URL.createObjectURL(new Blob([secret], { type: "text/plain" }));
|
|
64
|
+
const anchor = document.createElement("a");
|
|
65
|
+
anchor.href = url;
|
|
66
|
+
anchor.download = downloadFileName;
|
|
67
|
+
document.body.appendChild(anchor);
|
|
68
|
+
anchor.click();
|
|
69
|
+
anchor.remove();
|
|
70
|
+
URL.revokeObjectURL(url);
|
|
71
|
+
};
|
|
72
|
+
const accessibleName = ariaLabel ?? (typeof label === "string" ? label : void 0) ?? t("ui.credentialReveal.secretLabel");
|
|
73
|
+
return /* @__PURE__ */ jsxs(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
ref,
|
|
77
|
+
id,
|
|
78
|
+
"data-slot": "credential-reveal",
|
|
79
|
+
"data-size": size,
|
|
80
|
+
role: "group",
|
|
81
|
+
"aria-label": accessibleName,
|
|
82
|
+
className: cn("ui-credential-reveal", className),
|
|
83
|
+
children: [
|
|
84
|
+
warning !== null ? /* @__PURE__ */ jsx(Alert, { tone, className: "ui-credential-reveal-warning", children: /* @__PURE__ */ jsx(AlertDescription, { children: warning ?? t("ui.credentialReveal.warning") }) }) : null,
|
|
85
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-credential-reveal-surface", children: [
|
|
86
|
+
label ? /* @__PURE__ */ jsx(
|
|
87
|
+
Text,
|
|
88
|
+
{
|
|
89
|
+
id: labelId,
|
|
90
|
+
size: "xs",
|
|
91
|
+
tone: "muted",
|
|
92
|
+
weight: "medium",
|
|
93
|
+
className: "ui-credential-reveal-label",
|
|
94
|
+
children: label
|
|
95
|
+
}
|
|
96
|
+
) : null,
|
|
97
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-credential-reveal-row", children: [
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
Text,
|
|
100
|
+
{
|
|
101
|
+
mono: true,
|
|
102
|
+
size: "sm",
|
|
103
|
+
"data-slot": "credential-reveal-secret",
|
|
104
|
+
"data-state": isRevealed ? "revealed" : "masked",
|
|
105
|
+
"aria-labelledby": label ? labelId : void 0,
|
|
106
|
+
"aria-label": label ? void 0 : accessibleName,
|
|
107
|
+
className: "ui-credential-reveal-secret",
|
|
108
|
+
children: isRevealed ? secret : MASK
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-credential-reveal-actions", children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
Button,
|
|
114
|
+
{
|
|
115
|
+
type: "button",
|
|
116
|
+
variant: "ghost",
|
|
117
|
+
size,
|
|
118
|
+
onClick: () => setRevealed(!isRevealed),
|
|
119
|
+
"aria-pressed": isRevealed,
|
|
120
|
+
"aria-label": isRevealed ? t("ui.credentialReveal.hide") : t("ui.credentialReveal.show"),
|
|
121
|
+
children: isRevealed ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
Button,
|
|
126
|
+
{
|
|
127
|
+
type: "button",
|
|
128
|
+
variant: "outline",
|
|
129
|
+
size,
|
|
130
|
+
onClick: copy,
|
|
131
|
+
"data-state": copied ? "copied" : "idle",
|
|
132
|
+
"aria-label": copied ? t("ui.credentialReveal.copied") : t("ui.credentialReveal.copy"),
|
|
133
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Copy, { "aria-hidden": "true" })
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
downloadable ? /* @__PURE__ */ jsx(
|
|
137
|
+
Button,
|
|
138
|
+
{
|
|
139
|
+
type: "button",
|
|
140
|
+
variant: "ghost",
|
|
141
|
+
size,
|
|
142
|
+
onClick: download,
|
|
143
|
+
"aria-label": t("ui.credentialReveal.download"),
|
|
144
|
+
children: /* @__PURE__ */ jsx(Download, { "aria-hidden": "true" })
|
|
145
|
+
}
|
|
146
|
+
) : null
|
|
147
|
+
] })
|
|
148
|
+
] })
|
|
149
|
+
] }),
|
|
150
|
+
onAcknowledge ? /* @__PURE__ */ jsx("div", { className: "ui-credential-reveal-footer", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "default", size, onClick: onAcknowledge, children: acknowledgeLabel ?? t("ui.credentialReveal.acknowledge") }) }) : null,
|
|
151
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "credential-reveal-status", children: copied ? t("ui.credentialReveal.copiedAnnounce") : "" })
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
CredentialReveal.displayName = "CredentialReveal";
|
|
158
|
+
export {
|
|
159
|
+
CredentialReveal
|
|
160
|
+
};
|
|
@@ -5,6 +5,6 @@ import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
|
|
5
5
|
* laggy); consumers can still pass `openDelay`/`closeDelay`. A small open delay (not 0) is
|
|
6
6
|
* intentional — it prevents accidental triggering as the pointer passes over the trigger.
|
|
7
7
|
*/
|
|
8
|
-
export declare function HoverCard({ openDelay, closeDelay, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>):
|
|
8
|
+
export declare function HoverCard({ openDelay, closeDelay, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>): React.JSX.Element;
|
|
9
9
|
export declare const HoverCardTrigger: React.ForwardRefExoticComponent<HoverCardPrimitive.HoverCardTriggerProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
10
10
|
export declare const HoverCardContent: React.ForwardRefExoticComponent<Omit<HoverCardPrimitive.HoverCardContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type FieldValues } from "react-hook-form";
|
|
2
2
|
import type { FormFieldControlProp } from "../props/components/form.prop.js";
|
|
3
3
|
export type { FormFieldControlProp } from "../props/components/form.prop.js";
|
|
4
|
-
export declare function FormFieldControl<TFieldValues extends FieldValues>({ name, label, required, helper, className, children, }: FormFieldControlProp<TFieldValues>): import("react
|
|
4
|
+
export declare function FormFieldControl<TFieldValues extends FieldValues>({ name, label, required, helper, className, children, }: FormFieldControlProp<TFieldValues>): import("react").JSX.Element;
|
package/dist/form/form-root.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export type { FormRootProp } from "../props/components/form.prop.js";
|
|
|
7
7
|
* library — Inertia's `useForm` via `@godxjp/ui/inertia`, formik, TanStack Form). Both drive the same
|
|
8
8
|
* `FormFieldControl` auto-binding; the core keeps ZERO dependency on any non-RHF form library.
|
|
9
9
|
*/
|
|
10
|
-
export declare function FormRoot<TFieldValues extends FieldValues>({ form, adapter, onSubmit, children, className, id, }: FormRootProp<TFieldValues>): import("react
|
|
10
|
+
export declare function FormRoot<TFieldValues extends FieldValues>({ form, adapter, onSubmit, children, className, id, }: FormRootProp<TFieldValues>): import("react").JSX.Element;
|
|
@@ -118,7 +118,9 @@
|
|
|
118
118
|
},
|
|
119
119
|
"genericError": "Something went wrong. Please retry.",
|
|
120
120
|
"alert": {
|
|
121
|
-
"dismiss": "Dismiss"
|
|
121
|
+
"dismiss": "Dismiss",
|
|
122
|
+
"verifying": "Verifying…",
|
|
123
|
+
"stepUpFailed": "Re-authentication failed. Please try again."
|
|
122
124
|
}
|
|
123
125
|
},
|
|
124
126
|
"layout": {
|
|
@@ -176,6 +178,17 @@
|
|
|
176
178
|
"show": "Show password",
|
|
177
179
|
"hide": "Hide password"
|
|
178
180
|
},
|
|
181
|
+
"credentialReveal": {
|
|
182
|
+
"secretLabel": "Secret",
|
|
183
|
+
"warning": "This secret is shown only once. Copy it now — you won't be able to see it again.",
|
|
184
|
+
"show": "Reveal secret",
|
|
185
|
+
"hide": "Hide secret",
|
|
186
|
+
"copy": "Copy secret",
|
|
187
|
+
"copied": "Copied",
|
|
188
|
+
"copiedAnnounce": "Secret copied to clipboard",
|
|
189
|
+
"download": "Download secret",
|
|
190
|
+
"acknowledge": "I've saved it"
|
|
191
|
+
},
|
|
179
192
|
"numberInput": {
|
|
180
193
|
"increment": "Increase",
|
|
181
194
|
"decrement": "Decrease"
|
|
@@ -118,7 +118,9 @@
|
|
|
118
118
|
},
|
|
119
119
|
"genericError": "問題が発生しました。もう一度お試しください。",
|
|
120
120
|
"alert": {
|
|
121
|
-
"dismiss": "閉じる"
|
|
121
|
+
"dismiss": "閉じる",
|
|
122
|
+
"verifying": "確認中…",
|
|
123
|
+
"stepUpFailed": "再認証に失敗しました。もう一度お試しください。"
|
|
122
124
|
}
|
|
123
125
|
},
|
|
124
126
|
"layout": {
|
|
@@ -176,6 +178,17 @@
|
|
|
176
178
|
"show": "パスワードを表示",
|
|
177
179
|
"hide": "パスワードを隠す"
|
|
178
180
|
},
|
|
181
|
+
"credentialReveal": {
|
|
182
|
+
"secretLabel": "シークレット",
|
|
183
|
+
"warning": "このシークレットは一度だけ表示されます。今すぐコピーしてください。再表示はできません。",
|
|
184
|
+
"show": "シークレットを表示",
|
|
185
|
+
"hide": "シークレットを隠す",
|
|
186
|
+
"copy": "シークレットをコピー",
|
|
187
|
+
"copied": "コピーしました",
|
|
188
|
+
"copiedAnnounce": "シークレットをクリップボードにコピーしました",
|
|
189
|
+
"download": "シークレットをダウンロード",
|
|
190
|
+
"acknowledge": "保存しました"
|
|
191
|
+
},
|
|
179
192
|
"numberInput": {
|
|
180
193
|
"increment": "増やす",
|
|
181
194
|
"decrement": "減らす"
|
|
@@ -118,7 +118,9 @@
|
|
|
118
118
|
},
|
|
119
119
|
"genericError": "Đã xảy ra lỗi. Vui lòng thử lại.",
|
|
120
120
|
"alert": {
|
|
121
|
-
"dismiss": "Đóng"
|
|
121
|
+
"dismiss": "Đóng",
|
|
122
|
+
"verifying": "Đang xác minh…",
|
|
123
|
+
"stepUpFailed": "Xác thực lại thất bại. Vui lòng thử lại."
|
|
122
124
|
}
|
|
123
125
|
},
|
|
124
126
|
"layout": {
|
|
@@ -176,6 +178,17 @@
|
|
|
176
178
|
"show": "Hiện mật khẩu",
|
|
177
179
|
"hide": "Ẩn mật khẩu"
|
|
178
180
|
},
|
|
181
|
+
"credentialReveal": {
|
|
182
|
+
"secretLabel": "Khóa bí mật",
|
|
183
|
+
"warning": "Khóa bí mật này chỉ hiển thị một lần. Hãy sao chép ngay — bạn sẽ không thể xem lại.",
|
|
184
|
+
"show": "Hiện khóa bí mật",
|
|
185
|
+
"hide": "Ẩn khóa bí mật",
|
|
186
|
+
"copy": "Sao chép khóa",
|
|
187
|
+
"copied": "Đã sao chép",
|
|
188
|
+
"copiedAnnounce": "Đã sao chép khóa bí mật vào bộ nhớ tạm",
|
|
189
|
+
"download": "Tải khóa bí mật",
|
|
190
|
+
"acknowledge": "Tôi đã lưu"
|
|
191
|
+
},
|
|
179
192
|
"numberInput": {
|
|
180
193
|
"increment": "Tăng",
|
|
181
194
|
"decrement": "Giảm"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* permission-grid — pure, render-neutral helpers for RBAC role × permission
|
|
3
|
+
* matrices.
|
|
4
|
+
*
|
|
5
|
+
* A permission matrix maps a set of PERMISSION rows against a set of ROLE
|
|
6
|
+
* columns; each (role, permission) pair is either granted or not. A "permission
|
|
7
|
+
* matrix" is NOT a framework component — it is a COMPOSITION of `Table`/
|
|
8
|
+
* `DataTable` + `Badge` + tokens (see `docs/COMPOSITION-VS-COMPONENT.md`, Gate 0:
|
|
9
|
+
* fails C2/C3/C7). What it DOES need is a single, tested source of truth for the
|
|
10
|
+
* non-trivial data logic every consumer would otherwise re-derive: grant lookup,
|
|
11
|
+
* the per-permission diff between two roles (compare mode), and the row set that
|
|
12
|
+
* survives a "differences only" (差分のみ) filter.
|
|
13
|
+
*
|
|
14
|
+
* These functions are framework-neutral (no React) and locale-neutral (no
|
|
15
|
+
* strings) so the showcase — and any RBAC admin — composes UI on top of them.
|
|
16
|
+
*
|
|
17
|
+
* Grants are modelled as a flat `Set` of composite keys for O(1) membership,
|
|
18
|
+
* which mirrors how an authz decision cache (`role:permission` tuples) is
|
|
19
|
+
* typically held in memory.
|
|
20
|
+
*/
|
|
21
|
+
/** Any object addressable by a stable string `id` (a role or a permission). */
|
|
22
|
+
export interface GridEntity {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
}
|
|
25
|
+
/** A pair of role ids being compared side by side in compare mode. */
|
|
26
|
+
export type ComparePair = readonly [roleA: string, roleB: string];
|
|
27
|
+
/** Options controlling which permission rows are visible. */
|
|
28
|
+
export interface VisibleRowsOptions {
|
|
29
|
+
/** The two roles under comparison; enables the diff-only filter. */
|
|
30
|
+
readonly compare?: ComparePair | null;
|
|
31
|
+
/**
|
|
32
|
+
* When true AND `compare` is set, keep only the permissions on which the two
|
|
33
|
+
* compared roles disagree. Without a `compare` pair this is a no-op (there is
|
|
34
|
+
* nothing to diff against), so every row is returned.
|
|
35
|
+
*/
|
|
36
|
+
readonly diffOnly?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the composite grant key for a (role, permission) pair. Use this both to
|
|
40
|
+
* seed the grant `Set` and to query it, so the encoding stays in one place.
|
|
41
|
+
*/
|
|
42
|
+
export declare function grantKey(roleId: string, permissionId: string): string;
|
|
43
|
+
/** Is `permissionId` granted to `roleId`? */
|
|
44
|
+
export declare function hasGrant(grants: ReadonlySet<string>, roleId: string, permissionId: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Do the two compared roles DISAGREE on `permissionId` (one grants it, the other
|
|
47
|
+
* does not)? Comparing a role with itself is always `false`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function rolesDifferOnPermission(grants: ReadonlySet<string>, compare: ComparePair, permissionId: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The permission rows to render: every row normally, or — when `diffOnly` is on
|
|
52
|
+
* and a `compare` pair is set — only the rows on which the two roles differ.
|
|
53
|
+
* Order is preserved; the input array is never mutated.
|
|
54
|
+
*/
|
|
55
|
+
export declare function visibleRows<P extends GridEntity>(permissions: readonly P[], grants: ReadonlySet<string>, { compare, diffOnly }?: VisibleRowsOptions): P[];
|
|
56
|
+
/** How many permissions the two compared roles disagree on (badge in the header). */
|
|
57
|
+
export declare function countDifferences<P extends GridEntity>(permissions: readonly P[], grants: ReadonlySet<string>, compare: ComparePair): number;
|
|
58
|
+
/** How many permissions a single role is granted (column subtotal). */
|
|
59
|
+
export declare function countGrants<P extends GridEntity>(permissions: readonly P[], grants: ReadonlySet<string>, roleId: string): number;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const KEY_SEP = "\0";
|
|
2
|
+
function grantKey(roleId, permissionId) {
|
|
3
|
+
return `${roleId}${KEY_SEP}${permissionId}`;
|
|
4
|
+
}
|
|
5
|
+
function hasGrant(grants, roleId, permissionId) {
|
|
6
|
+
return grants.has(grantKey(roleId, permissionId));
|
|
7
|
+
}
|
|
8
|
+
function rolesDifferOnPermission(grants, compare, permissionId) {
|
|
9
|
+
const [a, b] = compare;
|
|
10
|
+
return hasGrant(grants, a, permissionId) !== hasGrant(grants, b, permissionId);
|
|
11
|
+
}
|
|
12
|
+
function visibleRows(permissions, grants, { compare, diffOnly = false } = {}) {
|
|
13
|
+
if (!diffOnly || !compare) return permissions.slice();
|
|
14
|
+
return permissions.filter((p) => rolesDifferOnPermission(grants, compare, p.id));
|
|
15
|
+
}
|
|
16
|
+
function countDifferences(permissions, grants, compare) {
|
|
17
|
+
return permissions.reduce(
|
|
18
|
+
(n, p) => n + (rolesDifferOnPermission(grants, compare, p.id) ? 1 : 0),
|
|
19
|
+
0
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function countGrants(permissions, grants, roleId) {
|
|
23
|
+
return permissions.reduce((n, p) => n + (hasGrant(grants, roleId, p.id) ? 1 : 0), 0);
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
countDifferences,
|
|
27
|
+
countGrants,
|
|
28
|
+
grantKey,
|
|
29
|
+
hasGrant,
|
|
30
|
+
rolesDifferOnPermission,
|
|
31
|
+
visibleRows
|
|
32
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Data Display component prop types — @see docs/COMPONENTS.md#data-display */
|
|
2
2
|
import type * as React from "react";
|
|
3
|
-
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, ChildrenProp, ToneProp, HeadingLevelProp } from "../vocabulary/index.js";
|
|
3
|
+
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, ChildrenProp, ToneProp, HeadingLevelProp, SizeProp, LabelProp, IdProp } from "../vocabulary/index.js";
|
|
4
4
|
/** @see EmptyState */
|
|
5
5
|
/**
|
|
6
6
|
* Semantic intent of the EmptyState icon medallion — a subset of the shared `ToneProp` vocabulary
|
|
@@ -62,6 +62,54 @@ export type BadgeProp = {
|
|
|
62
62
|
className?: ClassNameProp;
|
|
63
63
|
children?: ChildrenProp;
|
|
64
64
|
};
|
|
65
|
+
/** @see CredentialReveal */
|
|
66
|
+
/**
|
|
67
|
+
* Severity of the one-time-secret caution banner — a subset of the shared `ToneProp`
|
|
68
|
+
* vocabulary that carries a caution/danger/informational meaning (no `default`/`success`/
|
|
69
|
+
* `muted`/`neutral`, which would read as "safe"). Drives the composed `Alert` tone.
|
|
70
|
+
*/
|
|
71
|
+
export type CredentialRevealTone = Extract<ToneProp, "warning" | "destructive" | "info">;
|
|
72
|
+
/** @see CredentialReveal */
|
|
73
|
+
export type CredentialRevealProp = {
|
|
74
|
+
/** The one-time secret value shown masked by default and copied verbatim. */
|
|
75
|
+
secret: string;
|
|
76
|
+
/**
|
|
77
|
+
* Accessible name / caption for the secret (e.g. "API key", "デバイス資格情報"). When set it labels
|
|
78
|
+
* the secret region; when omitted a generic localized label is used.
|
|
79
|
+
*/
|
|
80
|
+
label?: LabelProp;
|
|
81
|
+
/**
|
|
82
|
+
* Caution banner copy. Defaults to the localized "shown only once" warning. Pass `null` to
|
|
83
|
+
* suppress the banner entirely (e.g. when the surrounding Dialog already carries the caution).
|
|
84
|
+
*/
|
|
85
|
+
warning?: React.ReactNode | null;
|
|
86
|
+
/** Controlled reveal state (masked ↔ shown). */
|
|
87
|
+
revealed?: boolean;
|
|
88
|
+
/** Uncontrolled initial reveal state. Default `false` (masked). */
|
|
89
|
+
defaultRevealed?: boolean;
|
|
90
|
+
/** Reveal-state change handler (fires on the show/hide toggle). */
|
|
91
|
+
onRevealedChange?: (revealed: boolean) => void;
|
|
92
|
+
/** Called after the secret is written to the clipboard. */
|
|
93
|
+
onCopy?: (secret: string) => void;
|
|
94
|
+
/**
|
|
95
|
+
* When provided, renders a confirm/acknowledge button that calls this — pair it with the
|
|
96
|
+
* surrounding Dialog's `onOpenChange(false)` so the secret re-blurs once dismissed.
|
|
97
|
+
*/
|
|
98
|
+
onAcknowledge?: () => void;
|
|
99
|
+
/** Label for the acknowledge button. Defaults to a localized "I've saved it". */
|
|
100
|
+
acknowledgeLabel?: React.ReactNode;
|
|
101
|
+
/** Offer a download-as-file button next to copy. Default `false`. */
|
|
102
|
+
downloadable?: boolean;
|
|
103
|
+
/** Filename for the downloaded secret. Default `"credential.txt"`. */
|
|
104
|
+
downloadFileName?: string;
|
|
105
|
+
/** Control size tier for the action buttons. Default `md`. */
|
|
106
|
+
size?: SizeProp;
|
|
107
|
+
/** Caution banner severity. Default `warning`. */
|
|
108
|
+
tone?: CredentialRevealTone;
|
|
109
|
+
className?: ClassNameProp;
|
|
110
|
+
id?: IdProp;
|
|
111
|
+
"aria-label"?: string;
|
|
112
|
+
};
|
|
65
113
|
/** @see DataTable */
|
|
66
114
|
export type DataTableProp<T> = {
|
|
67
115
|
data: T[];
|
|
@@ -13,7 +13,11 @@ export type AlertDialogProp = {
|
|
|
13
13
|
variant?: ConfirmVariantProp;
|
|
14
14
|
/** Type-to-confirm friction — enables destructive flow (GitHub/Stripe style). */
|
|
15
15
|
confirmPhrase?: string;
|
|
16
|
+
/** Semantic alias of `confirmPhrase` — the exact token to type (e.g. an org slug) to arm confirm. */
|
|
17
|
+
challenge?: string;
|
|
16
18
|
onConfirm: HandlerProp;
|
|
19
|
+
/** Optional step-up re-auth gate (passkey/2FA); must resolve truthy before `onConfirm` fires. */
|
|
20
|
+
stepUp?: () => Promise<boolean> | boolean;
|
|
17
21
|
keepOpenOnConfirm?: boolean;
|
|
18
22
|
pending?: PendingProp;
|
|
19
23
|
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/** Navigation component prop types — @see docs/COMPONENTS.md#navigation */
|
|
2
2
|
import type * as React from "react";
|
|
3
|
-
import type { ChildrenProp, ClassNameProp, DisabledProp, HasActiveFiltersProp, LabelProp, OnClearFiltersProp } from "../vocabulary/index.js";
|
|
3
|
+
import type { ChildrenProp, ClassNameProp, DisabledProp, HasActiveFiltersProp, LabelProp, OnClearFiltersProp, StickyProp } from "../vocabulary/index.js";
|
|
4
4
|
/** @see Toolbar */
|
|
5
5
|
export type ToolbarProp = {
|
|
6
6
|
onClear?: OnClearFiltersProp;
|
|
7
7
|
hasActiveFilters?: HasActiveFiltersProp;
|
|
8
|
+
/**
|
|
9
|
+
* Pin the strip to the top of its scroll container while the list scrolls beneath it
|
|
10
|
+
* (list-page filter bars, #197). Opt-in — default `false` keeps the toolbar quiet. Tune
|
|
11
|
+
* the pinned offset/background with the `--filter-bar-sticky-offset` /
|
|
12
|
+
* `--filter-bar-sticky-background` theme knobs.
|
|
13
|
+
*/
|
|
14
|
+
sticky?: StickyProp;
|
|
8
15
|
className?: ClassNameProp;
|
|
9
16
|
children: ChildrenProp;
|
|
10
17
|
};
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -384,6 +384,11 @@ export declare const VOCABULARY_REGISTRY: {
|
|
|
384
384
|
readonly category: "data";
|
|
385
385
|
readonly description: "Any filter active flag";
|
|
386
386
|
};
|
|
387
|
+
readonly StickyProp: {
|
|
388
|
+
readonly file: "vocabulary/data.prop.ts";
|
|
389
|
+
readonly category: "data";
|
|
390
|
+
readonly description: "Pin the filter strip while the list scrolls";
|
|
391
|
+
};
|
|
387
392
|
};
|
|
388
393
|
export type VocabularyPropName = keyof typeof VOCABULARY_REGISTRY;
|
|
389
394
|
export declare const COMPONENT_PROP_REGISTRY: {
|
|
@@ -724,6 +729,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
724
729
|
readonly file: "components/data-display.prop.ts";
|
|
725
730
|
readonly vocabulary: readonly ["ColumnDefProp", "DensityProp", "SortStateProp", "SelectedIdsProp"];
|
|
726
731
|
};
|
|
732
|
+
readonly CredentialRevealProp: {
|
|
733
|
+
readonly group: "data-display";
|
|
734
|
+
readonly file: "components/data-display.prop.ts";
|
|
735
|
+
readonly vocabulary: readonly ["LabelProp", "SizeProp", "ToneProp", "OnValueChangeProp", "HandlerProp", "ClassNameProp", "IdProp"];
|
|
736
|
+
};
|
|
727
737
|
readonly ChartSeriesProp: {
|
|
728
738
|
readonly group: "data-display";
|
|
729
739
|
readonly file: "components/charts.prop.ts";
|
|
@@ -900,7 +910,15 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
900
910
|
readonly field: "confirmPhrase";
|
|
901
911
|
readonly local: true;
|
|
902
912
|
readonly reason: "Destructive-dialog type-to-confirm friction phrase.";
|
|
913
|
+
}, {
|
|
914
|
+
readonly field: "challenge";
|
|
915
|
+
readonly local: true;
|
|
916
|
+
readonly reason: "Semantic alias of confirmPhrase — the typed challenge token (e.g. an org slug).";
|
|
903
917
|
}, "HandlerProp", {
|
|
918
|
+
readonly field: "stepUp";
|
|
919
|
+
readonly local: true;
|
|
920
|
+
readonly reason: "Step-up re-auth (passkey/2FA) gate resolved before confirm fires.";
|
|
921
|
+
}, {
|
|
904
922
|
readonly field: "keepOpenOnConfirm";
|
|
905
923
|
readonly local: true;
|
|
906
924
|
readonly reason: "AlertDialog-specific async completion behavior.";
|
|
@@ -985,7 +1003,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
985
1003
|
readonly ToolbarProp: {
|
|
986
1004
|
readonly group: "navigation";
|
|
987
1005
|
readonly file: "components/navigation.prop.ts";
|
|
988
|
-
readonly vocabulary: readonly ["OnClearFiltersProp", "HasActiveFiltersProp", "ClassNameProp", "ChildrenProp"];
|
|
1006
|
+
readonly vocabulary: readonly ["OnClearFiltersProp", "HasActiveFiltersProp", "StickyProp", "ClassNameProp", "ChildrenProp"];
|
|
989
1007
|
};
|
|
990
1008
|
readonly ToolbarGroupProp: {
|
|
991
1009
|
readonly group: "navigation";
|
package/dist/props/registry.js
CHANGED
|
@@ -384,6 +384,11 @@ const VOCABULARY_REGISTRY = {
|
|
|
384
384
|
file: "vocabulary/data.prop.ts",
|
|
385
385
|
category: "data",
|
|
386
386
|
description: "Any filter active flag"
|
|
387
|
+
},
|
|
388
|
+
StickyProp: {
|
|
389
|
+
file: "vocabulary/data.prop.ts",
|
|
390
|
+
category: "data",
|
|
391
|
+
description: "Pin the filter strip while the list scrolls"
|
|
387
392
|
}
|
|
388
393
|
};
|
|
389
394
|
const COMPONENT_PROP_REGISTRY = {
|
|
@@ -792,6 +797,19 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
792
797
|
file: "components/data-display.prop.ts",
|
|
793
798
|
vocabulary: ["ColumnDefProp", "DensityProp", "SortStateProp", "SelectedIdsProp"]
|
|
794
799
|
},
|
|
800
|
+
CredentialRevealProp: {
|
|
801
|
+
group: "data-display",
|
|
802
|
+
file: "components/data-display.prop.ts",
|
|
803
|
+
vocabulary: [
|
|
804
|
+
"LabelProp",
|
|
805
|
+
"SizeProp",
|
|
806
|
+
"ToneProp",
|
|
807
|
+
"OnValueChangeProp",
|
|
808
|
+
"HandlerProp",
|
|
809
|
+
"ClassNameProp",
|
|
810
|
+
"IdProp"
|
|
811
|
+
]
|
|
812
|
+
},
|
|
795
813
|
ChartSeriesProp: {
|
|
796
814
|
group: "data-display",
|
|
797
815
|
file: "components/charts.prop.ts",
|
|
@@ -919,7 +937,17 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
919
937
|
local: true,
|
|
920
938
|
reason: "Destructive-dialog type-to-confirm friction phrase."
|
|
921
939
|
},
|
|
940
|
+
{
|
|
941
|
+
field: "challenge",
|
|
942
|
+
local: true,
|
|
943
|
+
reason: "Semantic alias of confirmPhrase \u2014 the typed challenge token (e.g. an org slug)."
|
|
944
|
+
},
|
|
922
945
|
"HandlerProp",
|
|
946
|
+
{
|
|
947
|
+
field: "stepUp",
|
|
948
|
+
local: true,
|
|
949
|
+
reason: "Step-up re-auth (passkey/2FA) gate resolved before confirm fires."
|
|
950
|
+
},
|
|
923
951
|
{
|
|
924
952
|
field: "keepOpenOnConfirm",
|
|
925
953
|
local: true,
|
|
@@ -1007,7 +1035,13 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
1007
1035
|
ToolbarProp: {
|
|
1008
1036
|
group: "navigation",
|
|
1009
1037
|
file: "components/navigation.prop.ts",
|
|
1010
|
-
vocabulary: [
|
|
1038
|
+
vocabulary: [
|
|
1039
|
+
"OnClearFiltersProp",
|
|
1040
|
+
"HasActiveFiltersProp",
|
|
1041
|
+
"StickyProp",
|
|
1042
|
+
"ClassNameProp",
|
|
1043
|
+
"ChildrenProp"
|
|
1044
|
+
]
|
|
1011
1045
|
},
|
|
1012
1046
|
ToolbarGroupProp: {
|
|
1013
1047
|
group: "navigation",
|
|
@@ -58,3 +58,9 @@ export type OnSearchChangeProp = (query: string) => void;
|
|
|
58
58
|
export type OnClearFiltersProp = () => void;
|
|
59
59
|
/** Whether any filter is active — shows "Clear all". */
|
|
60
60
|
export type HasActiveFiltersProp = boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the filter strip pins to the top of its scroll container while the list
|
|
63
|
+
* scrolls beneath it. Opt-in (default `false`) so the toolbar stays quiet chrome;
|
|
64
|
+
* offset/background are tuned via the `--filter-bar-sticky-*` theme knobs.
|
|
65
|
+
*/
|
|
66
|
+
export type StickyProp = boolean;
|
|
@@ -4,4 +4,4 @@ export type { TitleProp, SubtitleProp, DescriptionProp, ExtraProp, FooterProp, A
|
|
|
4
4
|
export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, TableDensityProp, DensityProp, GapProp, } from "./layout.prop.js";
|
|
5
5
|
export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, ShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, SizeProp, FormLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, } from "./interaction.prop.js";
|
|
6
6
|
export type { BreadcrumbItemProp, BreadcrumbProp } from "./navigation.prop.js";
|
|
7
|
-
export type { GetRowIdProp, OnRowClickProp, ColumnDefProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, } from "./data.prop.js";
|
|
7
|
+
export type { GetRowIdProp, OnRowClickProp, ColumnDefProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, } from "./data.prop.js";
|
package/dist/styles/control.css
CHANGED
|
@@ -117,8 +117,16 @@
|
|
|
117
117
|
box-shadow: var(--shadow-sm);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/* SOLID hover/active tokens, never `--destructive / 0.9`: an alpha fill composites with whatever
|
|
121
|
+
* surface sits behind the button (a lighter Card/AlertDialog vs the page), so the effective colour
|
|
122
|
+
* — and its contrast with the label — drifts per context and can dip below AA (gh#199). The solid
|
|
123
|
+
* --destructive-hover/active are tuned to clear 4.5:1 on every surface. */
|
|
120
124
|
.ui-button--destructive:hover {
|
|
121
|
-
background: hsl(var(--destructive)
|
|
125
|
+
background: hsl(var(--destructive-hover));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ui-button--destructive:active {
|
|
129
|
+
background: hsl(var(--destructive-active));
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
/* Outline + ghost set their text colour EXPLICITLY (read --foreground) — never rely on inherited
|