@karni/ui 0.3.1 → 0.4.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 +712 -72
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8660 -4725
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +18 -1
- package/dist/script-BVJ9BbPm.cjs +2 -0
- package/dist/script-BVJ9BbPm.cjs.map +1 -0
- package/dist/script-D1fn4Dm4.js +6 -0
- package/dist/script-D1fn4Dm4.js.map +1 -0
- package/dist/src/auth/AuthGuard.d.ts +69 -4
- package/dist/src/auth/LoginForm.d.ts +41 -7
- package/dist/src/auth/engine.d.ts +15 -13
- package/dist/src/auth/provider.d.ts +10 -10
- package/dist/src/auth/storage.d.ts +1 -1
- package/dist/src/auth/types.d.ts +3 -3
- package/dist/src/components/brand/karni-mark.d.ts +4 -4
- package/dist/src/components/chart/data-chart.d.ts +66 -11
- package/dist/src/components/empty-state/empty-state.d.ts +22 -2
- package/dist/src/components/file-upload/FileUpload.d.ts +67 -3
- package/dist/src/components/layout/layout.d.ts +104 -9
- package/dist/src/components/layout/route-focus.d.ts +43 -0
- package/dist/src/components/modal/modal.d.ts +49 -8
- package/dist/src/components/page/page.d.ts +36 -5
- package/dist/src/components/primitives/button.d.ts +1 -0
- package/dist/src/components/primitives/checkbox.d.ts +4 -1
- package/dist/src/components/primitives/dropdown-menu.d.ts +1 -1
- package/dist/src/components/primitives/icons.d.ts +9 -1
- package/dist/src/components/primitives/input.d.ts +4 -1
- package/dist/src/components/primitives/label.d.ts +4 -1
- package/dist/src/components/primitives/select.d.ts +37 -6
- package/dist/src/components/primitives/skeleton.d.ts +4 -1
- package/dist/src/components/primitives/switch.d.ts +4 -1
- package/dist/src/components/primitives/tabs.d.ts +2 -2
- package/dist/src/components/primitives/textarea.d.ts +4 -1
- package/dist/src/components/resource/ResourceDetail.d.ts +3 -3
- package/dist/src/components/resource/ResourceForm.d.ts +4 -3
- package/dist/src/components/resource/ResourceSelect.d.ts +3 -1
- package/dist/src/components/resource/ResourceTable.d.ts +9 -3
- package/dist/src/components/resource/audit-fields.d.ts +1 -1
- package/dist/src/components/resource/detail/ResourceDetail.d.ts +109 -11
- package/dist/src/components/resource/empty-value.d.ts +17 -0
- package/dist/src/components/resource/form/ResourceForm.d.ts +65 -6
- package/dist/src/components/resource/form/field-inputs.d.ts +15 -1
- package/dist/src/components/resource/form/form-states.d.ts +87 -0
- package/dist/src/components/resource/form/prepare.d.ts +3 -3
- package/dist/src/components/resource/form/server-errors.d.ts +14 -0
- package/dist/src/components/resource/history/ChangeHistory.d.ts +46 -4
- package/dist/src/components/resource/import/ResourceImport.d.ts +73 -7
- package/dist/src/components/resource/nouns.d.ts +12 -0
- package/dist/src/components/resource/query-state.d.ts +20 -0
- package/dist/src/components/resource/resource-names.d.ts +23 -0
- package/dist/src/components/resource/select/ResourceSelect.d.ts +39 -8
- package/dist/src/components/resource/select/select-states.d.ts +68 -0
- package/dist/src/components/resource/select/use-option-pages.d.ts +37 -0
- package/dist/src/components/resource/select/venue-backend.d.ts +144 -0
- package/dist/src/components/resource/table/BulkActionsBar.d.ts +62 -5
- package/dist/src/components/resource/table/CardList.d.ts +31 -0
- package/dist/src/components/resource/table/ConfirmDialog.d.ts +28 -0
- package/dist/src/components/resource/table/ExportButtons.d.ts +2 -2
- package/dist/src/components/resource/table/FilterBar.d.ts +3 -3
- package/dist/src/components/resource/table/PaginationControls.d.ts +15 -3
- package/dist/src/components/resource/table/ResourceTable.d.ts +79 -14
- package/dist/src/components/resource/table/RowCard.d.ts +31 -0
- package/dist/src/components/resource/table/TableView.d.ts +29 -0
- package/dist/src/components/resource/table/ViewOptions.d.ts +19 -0
- package/dist/src/components/resource/table/access.d.ts +12 -0
- package/dist/src/components/resource/table/derive-columns.d.ts +2 -2
- package/dist/src/components/resource/table/grid-navigation.d.ts +23 -0
- package/dist/src/components/resource/table/layout.d.ts +37 -0
- package/dist/src/components/resource/table/preferences.d.ts +12 -0
- package/dist/src/components/resource/table/slots.d.ts +41 -0
- package/dist/src/components/resource/table/states.d.ts +91 -0
- package/dist/src/components/resource/table/story-fixtures.d.ts +25 -0
- package/dist/src/components/resource/table/structural-columns.d.ts +25 -0
- package/dist/src/components/resource/table/table-icons.d.ts +16 -0
- package/dist/src/components/resource/table/table-utils.d.ts +27 -3
- package/dist/src/components/resource/table/url-state.d.ts +67 -0
- package/dist/src/components/resource/table/use-container-width.d.ts +10 -0
- package/dist/src/components/resource/testapp-fixtures.d.ts +78 -0
- package/dist/src/components/stat/stat.d.ts +36 -6
- package/dist/src/components/toast/toast-store.d.ts +1 -1
- package/dist/src/components/toast/toaster.d.ts +39 -6
- package/dist/src/components/toast/use-toast.d.ts +1 -1
- package/dist/src/hooks/keys.d.ts +4 -4
- package/dist/src/hooks/multipart.d.ts +9 -9
- package/dist/src/hooks/params.d.ts +1 -1
- package/dist/src/hooks/types.d.ts +3 -3
- package/dist/src/hooks/useChangeHistory.d.ts +3 -2
- package/dist/src/hooks/useResourceImport.d.ts +2 -2
- package/dist/src/hooks/useResourceMutations.d.ts +2 -2
- package/dist/src/hooks/useUnsavedChangesGuard.d.ts +31 -0
- package/dist/src/index.d.ts +49 -31
- package/dist/src/lib/roles.d.ts +4 -4
- package/dist/src/lib/variants.d.ts +1 -1
- package/dist/src/primitives/registry.d.ts +81 -0
- package/dist/src/schema/client.d.ts +10 -10
- package/dist/src/schema/parse.d.ts +2 -2
- package/dist/src/schema/provider.d.ts +10 -3
- package/dist/src/schema/types.d.ts +18 -9
- package/dist/src/schema/useSchema.d.ts +5 -1
- package/dist/src/states/ErrorState.d.ts +43 -0
- package/dist/src/states/PendingButton.d.ts +16 -0
- package/dist/src/states/describe-error.d.ts +25 -0
- package/dist/src/states/hooks.d.ts +10 -0
- package/dist/src/theme/script.d.ts +12 -0
- package/dist/src/theme/theme.d.ts +45 -0
- package/dist/styles.css +174 -13
- package/dist/tailwind-preset.cjs +2 -2
- package/dist/tailwind-preset.cjs.map +1 -1
- package/dist/tailwind-preset.d.ts +83 -1
- package/dist/tailwind-preset.js +181 -62
- package/dist/tailwind-preset.js.map +1 -1
- package/dist/tailwind-v4.css +31 -0
- package/dist/vite-plugin.cjs +2 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.ts +19 -0
- package/dist/vite-plugin.js +16 -0
- package/dist/vite-plugin.js.map +1 -0
- package/eslint-plugin/index.js +4 -0
- package/eslint-plugin/rules/max-page-lines.js +1 -1
- package/eslint-plugin/rules/no-deep-imports.js +1 -1
- package/eslint-plugin/rules/no-direct-http.js +1 -1
- package/eslint-plugin/rules/no-raw-elements.js +1 -1
- package/eslint-plugin/rules/no-style-literals.js +222 -0
- package/eslint-plugin/rules.json +10 -4
- package/package.json +22 -19
package/dist/manifest.json
CHANGED
|
@@ -16,16 +16,23 @@
|
|
|
16
16
|
"DropdownMenuSeparator",
|
|
17
17
|
"DropdownMenuTrigger",
|
|
18
18
|
"EmptyState",
|
|
19
|
+
"EmptyValue",
|
|
20
|
+
"ErrorState",
|
|
19
21
|
"FileUpload",
|
|
20
22
|
"Input",
|
|
21
23
|
"KarniMark",
|
|
24
|
+
"KarniPrimitivesProvider",
|
|
22
25
|
"KarniProvider",
|
|
23
26
|
"Label",
|
|
24
27
|
"Layout",
|
|
25
28
|
"LoginForm",
|
|
26
29
|
"Modal",
|
|
27
30
|
"ModalClose",
|
|
31
|
+
"ModalDescription",
|
|
32
|
+
"ModalTitle",
|
|
28
33
|
"Page",
|
|
34
|
+
"PendingButton",
|
|
35
|
+
"PermissionDenied",
|
|
29
36
|
"ResourceDetail",
|
|
30
37
|
"ResourceForm",
|
|
31
38
|
"ResourceImport",
|
|
@@ -48,21 +55,28 @@
|
|
|
48
55
|
"TabsList",
|
|
49
56
|
"TabsTrigger",
|
|
50
57
|
"Textarea",
|
|
58
|
+
"ThemeProvider",
|
|
59
|
+
"ThemeToggle",
|
|
51
60
|
"Toaster"
|
|
52
61
|
],
|
|
53
62
|
"hooks": [
|
|
54
63
|
"useAuth",
|
|
55
64
|
"useChangeHistory",
|
|
65
|
+
"useDelayedFlag",
|
|
56
66
|
"useImportActions",
|
|
57
67
|
"useImportDescribe",
|
|
58
68
|
"useImportJob",
|
|
59
69
|
"useKarniClient",
|
|
70
|
+
"useKarniPrimitives",
|
|
71
|
+
"useOnlineStatus",
|
|
60
72
|
"usePermission",
|
|
61
73
|
"useResourceItem",
|
|
62
74
|
"useResourceList",
|
|
63
75
|
"useResourceMutations",
|
|
64
76
|
"useSchema",
|
|
65
|
-
"
|
|
77
|
+
"useTheme",
|
|
78
|
+
"useToast",
|
|
79
|
+
"useUnsavedChangesGuard"
|
|
66
80
|
],
|
|
67
81
|
"roles": {
|
|
68
82
|
"async-select": "ResourceSelect",
|
|
@@ -72,14 +86,17 @@
|
|
|
72
86
|
"data-table": "ResourceTable",
|
|
73
87
|
"detail-view": "ResourceDetail",
|
|
74
88
|
"empty-state": "EmptyState",
|
|
89
|
+
"error-state": "ErrorState",
|
|
75
90
|
"file-upload": "FileUpload",
|
|
76
91
|
"layout-shell": "Layout",
|
|
77
92
|
"login-form": "LoginForm",
|
|
78
93
|
"modal": "Modal",
|
|
79
94
|
"page-shell": "Page",
|
|
95
|
+
"permission-denied": "PermissionDenied",
|
|
80
96
|
"resource-form": "ResourceForm",
|
|
81
97
|
"resource-import": "ResourceImport",
|
|
82
98
|
"stat": "Stat",
|
|
99
|
+
"theme-toggle": "ThemeToggle",
|
|
83
100
|
"toast": "Toaster"
|
|
84
101
|
}
|
|
85
102
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=`karni-theme`,t=`(function(){try{var t=localStorage.getItem("${e}");var d=t==="dark"||((t===null||t==="system")&&matchMedia("(prefers-color-scheme: dark)").matches);document.documentElement.classList.toggle("dark",d);document.documentElement.style.colorScheme=d?"dark":"light"}catch(e){}})();`;Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return t}});
|
|
2
|
+
//# sourceMappingURL=script-BVJ9BbPm.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-BVJ9BbPm.cjs","names":["THEME_STORAGE_KEY","KARNI_THEME_SCRIPT"],"sources":["../src/theme/script.ts"],"sourcesContent":["/**\n * The theme's pre-paint script, with no React: shared by ThemeProvider and\n * the `@karni/ui/vite` plugin, which runs in Node at build time.\n */\n\n/** The localStorage key ThemeProvider keeps the user's choice under. */\nexport const THEME_STORAGE_KEY = \"karni-theme\";\n\n/**\n * Inline script for index.html's `<head>`: applies the stored (or system) theme\n * before the first paint. Uses the default storage key. In a Vite app,\n * `karniTheme()` from `@karni/ui/vite` puts it there.\n */\nexport const KARNI_THEME_SCRIPT = `(function(){try{var t=localStorage.getItem(\"${THEME_STORAGE_KEY}\");var d=t===\"dark\"||((t===null||t===\"system\")&&matchMedia(\"(prefers-color-scheme: dark)\").matches);document.documentElement.classList.toggle(\"dark\",d);document.documentElement.style.colorScheme=d?\"dark\":\"light\"}catch(e){}})();`;\n"],"mappings":"AAMA,IAAaA,EAAoB,cAOpBC,EAAqB,+CAA+CD,EAAkB,qO"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/theme/script.ts
|
|
2
|
+
var e = "karni-theme", t = `(function(){try{var t=localStorage.getItem("${e}");var d=t==="dark"||((t===null||t==="system")&&matchMedia("(prefers-color-scheme: dark)").matches);document.documentElement.classList.toggle("dark",d);document.documentElement.style.colorScheme=d?"dark":"light"}catch(e){}})();`;
|
|
3
|
+
//#endregion
|
|
4
|
+
export { e as n, t };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=script-D1fn4Dm4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-D1fn4Dm4.js","names":["THEME_STORAGE_KEY","KARNI_THEME_SCRIPT"],"sources":["../src/theme/script.ts"],"sourcesContent":["/**\n * The theme's pre-paint script, with no React: shared by ThemeProvider and\n * the `@karni/ui/vite` plugin, which runs in Node at build time.\n */\n\n/** The localStorage key ThemeProvider keeps the user's choice under. */\nexport const THEME_STORAGE_KEY = \"karni-theme\";\n\n/**\n * Inline script for index.html's `<head>`: applies the stored (or system) theme\n * before the first paint. Uses the default storage key. In a Vite app,\n * `karniTheme()` from `@karni/ui/vite` puts it there.\n */\nexport const KARNI_THEME_SCRIPT = `(function(){try{var t=localStorage.getItem(\"${THEME_STORAGE_KEY}\");var d=t===\"dark\"||((t===null||t===\"system\")&&matchMedia(\"(prefers-color-scheme: dark)\").matches);document.documentElement.classList.toggle(\"dark\",d);document.documentElement.style.colorScheme=d?\"dark\":\"light\"}catch(e){}})();`;\n"],"mappings":";AAMA,IAAaA,IAAoB,eAOpBC,IAAqB,+CAA+CD,EAAkB"}
|
|
@@ -1,12 +1,77 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { KarniUser } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* <AuthGuard> renders children only for authenticated users (optionally
|
|
5
|
+
* holding one of the given roles). Otherwise it renders a fallback or says
|
|
6
|
+
* why, in plain words (contract "Hooks + auth").
|
|
7
|
+
*
|
|
8
|
+
* Two different refusals: with `explain`, a visitor who is not signed in sees
|
|
9
|
+
* a sign-in prompt (the `Anonymous` slot: a LoginForm, so signing in reveals
|
|
10
|
+
* the content in place) and a signed-in user whose role lacks access sees
|
|
11
|
+
* PermissionDenied (the `Denied` slot). `anonymous` / `denied` render your own
|
|
12
|
+
* node for one of them, and `fallback` (one node for both) keeps working.
|
|
13
|
+
*
|
|
14
|
+
* Deprecation (0.4 → 1.0): a guard with none of `explain`, `fallback`,
|
|
15
|
+
* `anonymous`, `denied` or `slots` renders nothing when it refuses, as in
|
|
16
|
+
* 0.3, and warns once in development. 1.0 makes `explain` the default. A
|
|
17
|
+
* guard that hides a piece of UI passes `fallback={null}`.
|
|
18
|
+
*
|
|
19
|
+
* While the initial auth state resolves, the `loading` slot renders
|
|
20
|
+
* (default: nothing) so protected content never flashes a login screen
|
|
21
|
+
* during session restore.
|
|
22
|
+
*/
|
|
23
|
+
import * as React from "react";
|
|
24
|
+
/** What the `Anonymous` slot receives: nobody is signed in. */
|
|
25
|
+
export interface AuthGuardAnonymousProps {
|
|
26
|
+
/** The roles the content needs, when the guard names some. */
|
|
27
|
+
roles: readonly string[];
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
/** What the `Denied` slot receives: someone is signed in, and no role of theirs is in `roles`. */
|
|
31
|
+
export interface AuthGuardDeniedProps {
|
|
32
|
+
user: KarniUser;
|
|
33
|
+
/** The roles the content needs. */
|
|
34
|
+
roles: readonly string[];
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The replaceable refusals of AuthGuard. Pass any subset as `slots`.
|
|
39
|
+
* `defaultAuthGuardSlots` holds Karni's own, to wrap them.
|
|
40
|
+
*/
|
|
41
|
+
export interface AuthGuardSlots {
|
|
42
|
+
/** Not signed in: a sign-in prompt. */
|
|
43
|
+
Anonymous: React.ComponentType<AuthGuardAnonymousProps>;
|
|
44
|
+
/** Signed in without the role: PermissionDenied. */
|
|
45
|
+
Denied: React.ComponentType<AuthGuardDeniedProps>;
|
|
46
|
+
}
|
|
2
47
|
export interface AuthGuardProps {
|
|
3
48
|
/** Grant access when the user holds AT LEAST ONE of these role names. */
|
|
4
49
|
roles?: string[];
|
|
5
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Rendered for anonymous users and role mismatches alike. `anonymous` and
|
|
52
|
+
* `denied` (or `slots`) are preferred: they say which of the two it is.
|
|
53
|
+
* `null` renders nothing.
|
|
54
|
+
*/
|
|
6
55
|
fallback?: ReactNode;
|
|
7
|
-
/** Rendered
|
|
56
|
+
/** Rendered for a visitor who is not signed in (wins over `fallback`). */
|
|
57
|
+
anonymous?: ReactNode;
|
|
58
|
+
/** Rendered for a signed-in user whose role lacks access (wins over `fallback`). */
|
|
59
|
+
denied?: ReactNode;
|
|
60
|
+
/** Rendered while the auth status is still "loading". Default: nothing. */
|
|
8
61
|
loading?: ReactNode;
|
|
62
|
+
/** Replace the default refusals: Anonymous (sign-in prompt), Denied (PermissionDenied). */
|
|
63
|
+
slots?: Partial<AuthGuardSlots>;
|
|
64
|
+
/**
|
|
65
|
+
* Say why instead of rendering nothing: the sign-in prompt for a visitor,
|
|
66
|
+
* PermissionDenied for a role mismatch. It is the default from 1.0. Until
|
|
67
|
+
* then a guard with no fallback and no `explain` renders nothing (and warns).
|
|
68
|
+
*/
|
|
69
|
+
explain?: boolean;
|
|
70
|
+
/** Merged onto the default refusal's root. */
|
|
71
|
+
className?: string;
|
|
9
72
|
children?: ReactNode;
|
|
10
73
|
}
|
|
11
|
-
/**
|
|
12
|
-
export declare
|
|
74
|
+
/** Karni's own slots, exported so a project can wrap one instead of rewriting it. */
|
|
75
|
+
export declare const defaultAuthGuardSlots: AuthGuardSlots;
|
|
76
|
+
/** Renders its children only for a signed-in user, optionally with one of the given roles, and otherwise says why. */
|
|
77
|
+
export declare function AuthGuard(props: AuthGuardProps): ReactElement | null;
|
|
@@ -1,29 +1,63 @@
|
|
|
1
|
+
import { KarniErrorDescription } from '../states/describe-error';
|
|
2
|
+
import { ErrorSlotProps } from '../states/ErrorState';
|
|
1
3
|
import { KarniUser } from './types';
|
|
2
4
|
/**
|
|
3
|
-
* LoginForm
|
|
5
|
+
* LoginForm is the sign-in form every app needs (contract: auth).
|
|
4
6
|
*
|
|
5
7
|
* Email + password against `useAuth().login`, with the error inline. On
|
|
6
8
|
* success it calls `onSuccess(user)` (navigate there). When the backend's
|
|
7
9
|
* 2FA gate fires, `on2FA` receives the ephemeral token so the app can render
|
|
8
|
-
* its code step (`POST <authBasePath>/2fa/login/`)
|
|
10
|
+
* its code step (`POST <authBasePath>/2fa/login/`). Without a handler the
|
|
9
11
|
* form explains that a code is required. Render it inside `AuthProvider`,
|
|
10
12
|
* e.g. as `AuthGuard`'s `fallback`.
|
|
11
13
|
*/
|
|
12
14
|
import * as React from "react";
|
|
15
|
+
/** What the `Error` slot receives: ErrorSlotProps plus the message, already worded for sign-in. */
|
|
16
|
+
export interface LoginFormErrorProps extends ErrorSlotProps {
|
|
17
|
+
/** Put this id on the message's root: the inputs' `aria-describedby` points at it. */
|
|
18
|
+
id: string;
|
|
19
|
+
/**
|
|
20
|
+
* The failure in plain words: wrong credentials ("That email and password
|
|
21
|
+
* do not match"), throttling ("Wait 42 seconds"), offline, a server error.
|
|
22
|
+
*/
|
|
23
|
+
described: KarniErrorDescription;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The replaceable parts of LoginForm. Pass any subset as `slots`.
|
|
27
|
+
* `defaultLoginFormSlots` holds Karni's own, to wrap them.
|
|
28
|
+
*/
|
|
29
|
+
export interface LoginFormSlots {
|
|
30
|
+
/** The message under the fields when signing in fails (announced as an alert). */
|
|
31
|
+
Error: React.ComponentType<LoginFormErrorProps>;
|
|
32
|
+
}
|
|
13
33
|
export interface LoginFormProps {
|
|
14
|
-
/** Called with the signed-in user
|
|
34
|
+
/** Called with the signed-in user. Navigate from here. */
|
|
15
35
|
onSuccess?: (user: KarniUser) => void;
|
|
16
36
|
/** Called when the account needs a second factor (the token completes it). */
|
|
17
37
|
on2FA?: (ephemeralToken: string) => void;
|
|
18
|
-
/** Heading above the fields
|
|
38
|
+
/** Heading above the fields. `null` renders none. Default "Sign in". */
|
|
19
39
|
title?: React.ReactNode;
|
|
20
40
|
/** Text under the heading (demo logins, a signup link, ...). */
|
|
21
41
|
description?: React.ReactNode;
|
|
22
42
|
/** The submit button's label. Default "Sign in". */
|
|
23
43
|
submitLabel?: React.ReactNode;
|
|
24
|
-
/**
|
|
44
|
+
/** Disables the form (e.g. while the auth status is still "loading"). */
|
|
25
45
|
disabled?: boolean;
|
|
46
|
+
/** Focus the email field when the form appears. Default true: signing in is the page's purpose. */
|
|
47
|
+
autoFocus?: boolean;
|
|
48
|
+
/** Replace parts of the form: Error. */
|
|
49
|
+
slots?: Partial<LoginFormSlots>;
|
|
26
50
|
className?: string;
|
|
27
51
|
}
|
|
28
|
-
/**
|
|
29
|
-
export declare
|
|
52
|
+
/** Karni's own slots, exported so a project can wrap one instead of rewriting it. */
|
|
53
|
+
export declare const defaultLoginFormSlots: LoginFormSlots;
|
|
54
|
+
/**
|
|
55
|
+
* Email and password sign-in form with inline errors and a callback for the two-factor step.
|
|
56
|
+
*
|
|
57
|
+
* Wrong credentials, throttling, a dropped connection and a server error each
|
|
58
|
+
* read differently. The message is announced, both fields point at it
|
|
59
|
+
* (`aria-describedby`) and are marked invalid when they are the problem.
|
|
60
|
+
* After a failure the email stays. Wrong credentials clear the password and
|
|
61
|
+
* focus it. Escape hatch: `slots.Error`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function LoginForm({ onSuccess, on2FA, title, description, submitLabel, disabled, autoFocus, slots, className, }: LoginFormProps): React.JSX.Element;
|
|
@@ -14,8 +14,9 @@ export interface AuthEngineOptions {
|
|
|
14
14
|
/** Path prefix of the auth endpoints, relative to the client's baseUrl. */
|
|
15
15
|
authBasePath?: string;
|
|
16
16
|
/**
|
|
17
|
-
* Reads whatever the schema loader cached under karniKeys.schema()
|
|
18
|
-
*
|
|
17
|
+
* Reads whatever the schema loader cached under karniKeys.schema(). The
|
|
18
|
+
* engine consults it (shape-guarded) for `authMode` before fetching the
|
|
19
|
+
* document.
|
|
19
20
|
*/
|
|
20
21
|
readCachedSchema?: () => unknown;
|
|
21
22
|
}
|
|
@@ -49,16 +50,16 @@ export declare class AuthEngine {
|
|
|
49
50
|
/** Registers the D6 hook points on the client. Idempotent. */
|
|
50
51
|
attach(): void;
|
|
51
52
|
/**
|
|
52
|
-
* Clears every hook this engine registered
|
|
53
|
-
* the app passed to createKarniClient
|
|
54
|
-
* the client fully unauthenticated by design
|
|
53
|
+
* Clears every hook this engine registered, including a `getAccessToken`
|
|
54
|
+
* the app passed to createKarniClient. An unmounted AuthProvider leaves
|
|
55
|
+
* the client fully unauthenticated by design.
|
|
55
56
|
*/
|
|
56
57
|
detach(): void;
|
|
57
58
|
private csrfHeaders;
|
|
58
59
|
/**
|
|
59
60
|
* The client calls this at most once per 401-ing request (D6). Returning
|
|
60
61
|
* true makes the client retry that request exactly once with
|
|
61
|
-
* freshly-injected credentials
|
|
62
|
+
* freshly-injected credentials. The retry's outcome is always surfaced.
|
|
62
63
|
*/
|
|
63
64
|
private handleUnauthorized;
|
|
64
65
|
readonly subscribe: (listener: () => void) => (() => void);
|
|
@@ -68,16 +69,16 @@ export declare class AuthEngine {
|
|
|
68
69
|
ensureInitialized(): Promise<void>;
|
|
69
70
|
private initialize;
|
|
70
71
|
private fetchSchemaAuthMode;
|
|
71
|
-
/** Fetches `/me/` and mirrors the result
|
|
72
|
+
/** Fetches `/me/` and mirrors the result. Any failure means anonymous. */
|
|
72
73
|
private syncUser;
|
|
73
74
|
/**
|
|
74
75
|
* Reads the CSRF cookie, bootstrapping it via `GET <authBasePath>/csrf/`
|
|
75
76
|
* when absent (A4). Returns null when the cookie stays unreadable (e.g.
|
|
76
|
-
* CSRF_COOKIE_HTTPONLY)
|
|
77
|
+
* CSRF_COOKIE_HTTPONLY). Requests then go out without the header and the
|
|
77
78
|
* backend's 403 surfaces, which is the honest failure mode.
|
|
78
79
|
*/
|
|
79
80
|
private ensureCsrfToken;
|
|
80
|
-
/** ONE shared in-flight refresh
|
|
81
|
+
/** ONE shared in-flight refresh. Resolves true on success. */
|
|
81
82
|
private ensureRefresh;
|
|
82
83
|
private performRefresh;
|
|
83
84
|
login(email: string, password: string): Promise<LoginResult>;
|
|
@@ -85,10 +86,11 @@ export declare class AuthEngine {
|
|
|
85
86
|
/** Best-effort server logout, then always clears local state (anonymous). */
|
|
86
87
|
logout(): Promise<void>;
|
|
87
88
|
/**
|
|
88
|
-
* Adopts tokens issued out of band
|
|
89
|
-
* when a support user starts or stops
|
|
90
|
-
* with tokens for the other identity).
|
|
91
|
-
* server-side session already changed,
|
|
89
|
+
* Adopts tokens issued out of band, which is what
|
|
90
|
+
* `@karni/impersonation-ui` does when a support user starts or stops
|
|
91
|
+
* impersonating (the backend answers with tokens for the other identity).
|
|
92
|
+
* JWT mode only. In session mode the server-side session already changed,
|
|
93
|
+
* so this just re-syncs `/me/`.
|
|
92
94
|
*/
|
|
93
95
|
adoptTokens(access: string | null, refresh: string | null): Promise<void>;
|
|
94
96
|
/**
|
|
@@ -3,18 +3,18 @@ import { TokenStorage } from './storage';
|
|
|
3
3
|
import { AuthStatus, KarniUser, LoginResult } from './types';
|
|
4
4
|
export interface AuthProviderProps {
|
|
5
5
|
/**
|
|
6
|
-
* "jwt" | "session"
|
|
6
|
+
* "jwt" | "session". When omitted, the mode comes from the schema root's
|
|
7
7
|
* x-karni-auth-mode (D6), falling back to "jwt" when the schema does not
|
|
8
8
|
* declare one.
|
|
9
9
|
*/
|
|
10
10
|
mode?: "jwt" | "session";
|
|
11
|
-
/** JWT mode refresh-token storage
|
|
11
|
+
/** JWT mode refresh-token storage. Defaults to the localStorage adapter. */
|
|
12
12
|
tokenStorage?: TokenStorage;
|
|
13
|
-
/** Session mode CSRF cookie name
|
|
13
|
+
/** Session mode CSRF cookie name. Default "csrftoken" (D6/A4). */
|
|
14
14
|
csrfCookieName?: string;
|
|
15
|
-
/** Session mode CSRF header name
|
|
15
|
+
/** Session mode CSRF header name. Default "X-CSRFToken" (D6/A4). */
|
|
16
16
|
csrfHeaderName?: string;
|
|
17
|
-
/** Path prefix of the auth endpoints relative to baseUrl
|
|
17
|
+
/** Path prefix of the auth endpoints relative to baseUrl. Default "/auth". */
|
|
18
18
|
authBasePath?: string;
|
|
19
19
|
children?: ReactNode;
|
|
20
20
|
}
|
|
@@ -26,18 +26,18 @@ export interface AuthContextValue {
|
|
|
26
26
|
logout(): Promise<void>;
|
|
27
27
|
refresh(): Promise<void>;
|
|
28
28
|
/**
|
|
29
|
-
* Adopts tokens issued out of band (JWT mode
|
|
30
|
-
* e.g. after starting or stopping an impersonated session.
|
|
29
|
+
* Adopts tokens issued out of band (JWT mode, or a `/me/` re-sync in session
|
|
30
|
+
* mode), e.g. after starting or stopping an impersonated session.
|
|
31
31
|
*/
|
|
32
32
|
adoptTokens(access: string | null, refresh: string | null): Promise<void>;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* The React mirror of the auth engine's state. Exported for advanced
|
|
36
|
-
* composition (stories, tests, custom providers)
|
|
36
|
+
* composition (stories, tests, custom providers). Application code should
|
|
37
37
|
* use <AuthProvider> + useAuth().
|
|
38
38
|
*/
|
|
39
39
|
export declare const AuthContext: import('react').Context<AuthContextValue | null>;
|
|
40
|
-
/**
|
|
40
|
+
/** Rendered inside `KarniProvider`, it holds the signed-in user and attaches credentials (JWT or session) to every request. */
|
|
41
41
|
export declare function AuthProvider({ mode, tokenStorage, csrfCookieName, csrfHeaderName, authBasePath, children, }: AuthProviderProps): ReactElement;
|
|
42
|
-
/** Returns the signed-in user, auth status, `login` and `logout
|
|
42
|
+
/** Returns the signed-in user, auth status, `login` and `logout`, and throws a descriptive error outside `AuthProvider`. */
|
|
43
43
|
export declare function useAuth(): AuthContextValue;
|
|
@@ -13,7 +13,7 @@ export interface TokenStorage {
|
|
|
13
13
|
clear(): void;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* The default storage, which keeps the refresh token in localStorage. `key` defaults to "karni.refreshToken". Every storage
|
|
17
17
|
* access is guarded: when localStorage is unavailable (SSR, private windows,
|
|
18
18
|
* blocked site data) the adapter degrades to a no-op that returns `null`
|
|
19
19
|
* instead of throwing.
|
package/dist/src/auth/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* Shape of the `/api/auth/me/` response. The accounts contract (A15)
|
|
6
|
-
* guarantees `roles: string[]` on MeSerializer
|
|
6
|
+
* guarantees `roles: string[]` on MeSerializer. Every other field the
|
|
7
7
|
* backend's user serializer exposes rides along via the index signature.
|
|
8
8
|
*/
|
|
9
9
|
export interface KarniUser {
|
|
@@ -19,8 +19,8 @@ export interface KarniUser {
|
|
|
19
19
|
export type AuthStatus = "loading" | "authenticated" | "anonymous";
|
|
20
20
|
/**
|
|
21
21
|
* Discriminated login result (D6). When the backend's 2FA gate fires
|
|
22
|
-
* (accounts A6), `login()` resolves `{ requires2fa, ephemeralToken }
|
|
23
|
-
*
|
|
22
|
+
* (accounts A6), `login()` resolves `{ requires2fa, ephemeralToken }`.
|
|
23
|
+
* The 2FA UI is out of scope in v0.1, so consumers exchange the ephemeral
|
|
24
24
|
* token themselves via `POST /api/auth/2fa/login/`.
|
|
25
25
|
*/
|
|
26
26
|
export type LoginResult = {
|
|
@@ -4,17 +4,17 @@ export interface KarniMarkProps extends Omit<React.SVGAttributes<SVGSVGElement>,
|
|
|
4
4
|
size?: number;
|
|
5
5
|
/**
|
|
6
6
|
* Which drawing: the full mark, the small-size variant, or `auto` (the
|
|
7
|
-
* default)
|
|
7
|
+
* default): the small variant at 32px and below, the full mark above.
|
|
8
8
|
*/
|
|
9
9
|
variant?: "auto" | "full" | "simple";
|
|
10
|
-
/** Accessible name
|
|
10
|
+
/** Accessible name. Pass an empty string to hide a purely decorative mark. */
|
|
11
11
|
title?: string;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* The Karni mark
|
|
14
|
+
* The Karni mark as inline SVG, so there is no asset to load or bundle.
|
|
15
15
|
*
|
|
16
16
|
* It is the default content of `<Layout>`'s `sidebarHeader` slot so a bare
|
|
17
|
-
* shell is never nameless
|
|
17
|
+
* shell is never nameless. A project replaces it by passing its own
|
|
18
18
|
* `sidebarHeader` (the scaffold already does). Do not recolour or restyle it:
|
|
19
19
|
* the mark is Karni's, see assets/brand/README.md in the repository.
|
|
20
20
|
*/
|
|
@@ -3,14 +3,16 @@ import * as React from "react";
|
|
|
3
3
|
* In-house SVG chart (contract: no chart dependency).
|
|
4
4
|
*
|
|
5
5
|
* Accessibility model (contract D10): the SVG is a labelled image
|
|
6
|
-
* (role="img" + aria-label + <title>/<desc>)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* element and hidden from assistive tech.
|
|
6
|
+
* (role="img" + aria-label + <title>/<desc>) described by a generated
|
|
7
|
+
* summary (the range of each series). The REAL accessible data surface is a
|
|
8
|
+
* visually-hidden <table> rendered next to it. The tooltip is rendered
|
|
9
|
+
* OUTSIDE the role="img" element and hidden from assistive tech. The SVG is
|
|
10
|
+
* focusable, the arrow keys move the tooltip from point to point, and each
|
|
11
|
+
* point is announced through a polite live region.
|
|
10
12
|
*
|
|
11
13
|
* Colors come exclusively from the --karni-chart-1..5 tokens, via the
|
|
12
14
|
* Tailwind preset's chart color scale (stroke-chart-N / fill-chart-N /
|
|
13
|
-
* bg-chart-N classes
|
|
15
|
+
* bg-chart-N classes, whole class names per contract D7).
|
|
14
16
|
*/
|
|
15
17
|
export type DataChartKind = "line" | "bar";
|
|
16
18
|
export interface DataChartPoint {
|
|
@@ -31,23 +33,64 @@ export interface DataChartTooltipContext {
|
|
|
31
33
|
swatchClassName: string;
|
|
32
34
|
}>;
|
|
33
35
|
}
|
|
36
|
+
/** What the `Tooltip` slot receives: the hovered (or keyboard-selected) category. */
|
|
37
|
+
export interface DataChartTooltipProps extends DataChartTooltipContext {
|
|
38
|
+
valueFormatter: (value: number) => string;
|
|
39
|
+
}
|
|
40
|
+
/** What the `Legend` slot receives. */
|
|
41
|
+
export interface DataChartLegendProps {
|
|
42
|
+
series: ReadonlyArray<{
|
|
43
|
+
name: string;
|
|
44
|
+
swatchClassName: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
/** What the `Empty` slot receives (no plottable data). */
|
|
48
|
+
export interface DataChartEmptyProps {
|
|
49
|
+
/** The chart's title, for screen readers. */
|
|
50
|
+
title: string;
|
|
51
|
+
/** The `emptyLabel` prop. */
|
|
52
|
+
label: React.ReactNode;
|
|
53
|
+
}
|
|
54
|
+
/** What the `Loading` slot receives (`loading` is true). */
|
|
55
|
+
export interface DataChartLoadingProps {
|
|
56
|
+
title: string;
|
|
57
|
+
/** The viewBox size, for a placeholder of the chart's proportions. */
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
}
|
|
61
|
+
/** The parts of the chart a project can replace (`slots={{ Tooltip: MyTooltip }}`). */
|
|
62
|
+
export interface DataChartSlots {
|
|
63
|
+
/** The tooltip's content (the positioned box around it stays the chart's). */
|
|
64
|
+
Tooltip: React.ComponentType<DataChartTooltipProps>;
|
|
65
|
+
/** The series legend, decorative (the hidden table carries the names). */
|
|
66
|
+
Legend: React.ComponentType<DataChartLegendProps>;
|
|
67
|
+
/** Shown instead of the chart when there is nothing to plot. */
|
|
68
|
+
Empty: React.ComponentType<DataChartEmptyProps>;
|
|
69
|
+
/** Shown instead of the chart while `loading`. */
|
|
70
|
+
Loading: React.ComponentType<DataChartLoadingProps>;
|
|
71
|
+
}
|
|
34
72
|
export interface DataChartProps {
|
|
35
73
|
kind: DataChartKind;
|
|
36
74
|
/** Single-series shorthand (ignored when `series` is given). */
|
|
37
75
|
data?: readonly DataChartPoint[];
|
|
38
|
-
/** Multi-series data
|
|
76
|
+
/** Multi-series data. Series cycle through --karni-chart-1..5. */
|
|
39
77
|
series?: readonly DataChartSeries[];
|
|
40
78
|
/** Chart title: SVG <title>, aria-label and the hidden table's caption. */
|
|
41
79
|
title: string;
|
|
42
80
|
/** Longer description for the SVG <desc>. */
|
|
43
81
|
description?: string;
|
|
82
|
+
/**
|
|
83
|
+
* What screen readers hear after the title and description. Default: a
|
|
84
|
+
* generated summary (each series' lowest and highest value). "" omits it.
|
|
85
|
+
*/
|
|
86
|
+
summary?: string;
|
|
44
87
|
/** Formats values in the tooltip, y-axis ticks and the hidden table. */
|
|
45
88
|
valueFormatter?: (value: number) => string;
|
|
46
89
|
/** Formats x-axis tick labels (default: the category label itself). */
|
|
47
90
|
xTickFormatter?: (label: string) => string;
|
|
48
|
-
/**
|
|
91
|
+
/** Fully custom tooltip content. Prefer the `Tooltip` slot. */
|
|
49
92
|
renderTooltip?: (context: DataChartTooltipContext) => React.ReactNode;
|
|
50
|
-
/** viewBox width (default 640)
|
|
93
|
+
/** viewBox width (default 640). The rendered size is responsive. */
|
|
51
94
|
width?: number;
|
|
52
95
|
/** viewBox height (default 240). */
|
|
53
96
|
height?: number;
|
|
@@ -55,20 +98,32 @@ export interface DataChartProps {
|
|
|
55
98
|
yTickCount?: number;
|
|
56
99
|
/** Show the series legend (default: only for 2+ series). */
|
|
57
100
|
showLegend?: boolean;
|
|
58
|
-
/** Show the hover
|
|
101
|
+
/** Show the tooltip on hover and keyboard focus (default true). */
|
|
59
102
|
showTooltip?: boolean;
|
|
60
103
|
/** Column header of the hidden table's category column (default "Label"). */
|
|
61
104
|
categoryHeader?: string;
|
|
62
105
|
/** Text shown when there is no plottable data. */
|
|
63
106
|
emptyLabel?: string;
|
|
107
|
+
/** True while the data loads: a placeholder of the chart's size replaces it. */
|
|
108
|
+
loading?: boolean;
|
|
109
|
+
/** Replaces the Tooltip, Legend, Empty or Loading. See DataChartSlots. */
|
|
110
|
+
slots?: Partial<DataChartSlots>;
|
|
64
111
|
className?: string;
|
|
65
112
|
}
|
|
113
|
+
/** The default pieces of the chart, for wrapping one. */
|
|
114
|
+
export declare const defaultDataChartSlots: DataChartSlots;
|
|
66
115
|
/**
|
|
67
116
|
* Minimal line/bar chart driven by `{label, value}` points or named series.
|
|
68
117
|
*
|
|
69
|
-
*
|
|
118
|
+
* Keyboard and screen readers: the chart is an image named by `title`,
|
|
119
|
+
* described by `description` and a generated summary, with every value in a
|
|
120
|
+
* visually-hidden table. Tab focuses it. The arrow keys (Home, End) move the
|
|
121
|
+
* tooltip from category to category and announce each one. Escape hides it.
|
|
122
|
+
*
|
|
123
|
+
* Escape hatches: `slots` (replaces the Tooltip, Legend, Empty or Loading
|
|
124
|
+
* pieces), `valueFormatter`/`xTickFormatter`, `summary`, `renderTooltip`,
|
|
70
125
|
* `width`/`height`/`yTickCount`, legend and tooltip toggles, `className`
|
|
71
126
|
* (merged via cn()). Colors are only themeable through the
|
|
72
127
|
* --karni-chart-1..5 CSS variables.
|
|
73
128
|
*/
|
|
74
|
-
export declare function DataChart({ kind, data, series, title, description, valueFormatter, xTickFormatter, renderTooltip, width, height, yTickCount, showLegend, showTooltip, categoryHeader, emptyLabel, className, }: DataChartProps): React.ReactElement;
|
|
129
|
+
export declare function DataChart({ kind, data, series, title, description, summary, valueFormatter, xTickFormatter, renderTooltip, width, height, yTickCount, showLegend, showTooltip, categoryHeader, emptyLabel, loading, slots, className, }: DataChartProps): React.ReactElement;
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/** What each EmptyState slot receives: the matching prop. */
|
|
3
|
+
export interface EmptyStateSlotProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/** The parts of the placeholder a project can replace (`slots={{ Title: MyHeading }}`). */
|
|
7
|
+
export interface EmptyStateSlots {
|
|
8
|
+
/** Wraps `icon`. It is decorative and hidden from assistive tech. */
|
|
9
|
+
Icon: React.ComponentType<EmptyStateSlotProps>;
|
|
10
|
+
/** The headline. The default is an <h3>. */
|
|
11
|
+
Title: React.ComponentType<EmptyStateSlotProps>;
|
|
12
|
+
/** The supporting copy. */
|
|
13
|
+
Description: React.ComponentType<EmptyStateSlotProps>;
|
|
14
|
+
/** Wraps `action`. */
|
|
15
|
+
Actions: React.ComponentType<EmptyStateSlotProps>;
|
|
16
|
+
}
|
|
2
17
|
export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
3
18
|
/** Optional illustration/icon slot, rendered above the title. */
|
|
4
19
|
icon?: React.ReactNode;
|
|
@@ -8,13 +23,18 @@ export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
8
23
|
description?: React.ReactNode;
|
|
9
24
|
/** Slot for a call to action (e.g. a "Create" button). */
|
|
10
25
|
action?: React.ReactNode;
|
|
26
|
+
/** Replaces the Icon, Title, Description or Actions. See EmptyStateSlots. */
|
|
27
|
+
slots?: Partial<EmptyStateSlots>;
|
|
11
28
|
children?: React.ReactNode;
|
|
12
29
|
}
|
|
30
|
+
/** The default pieces of the placeholder, for wrapping one. */
|
|
31
|
+
export declare const defaultEmptyStateSlots: EmptyStateSlots;
|
|
13
32
|
/**
|
|
14
33
|
* Centered placeholder for empty collections: icon, title, description and
|
|
15
34
|
* an action slot. Used by ResourceTable's `empty` slot and reusable anywhere.
|
|
16
35
|
*
|
|
17
|
-
* Escape hatches:
|
|
18
|
-
*
|
|
36
|
+
* Escape hatches: `slots` replaces the Icon, Title (an <h3> by default: swap
|
|
37
|
+
* it for the heading level your page needs), Description or Actions.
|
|
38
|
+
* `className` merges via cn(), and extra children render under the action.
|
|
19
39
|
*/
|
|
20
40
|
export declare const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
|