@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.
Files changed (127) hide show
  1. package/README.md +712 -72
  2. package/dist/index.cjs +5 -5
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +8660 -4725
  5. package/dist/index.js.map +1 -1
  6. package/dist/manifest.json +18 -1
  7. package/dist/script-BVJ9BbPm.cjs +2 -0
  8. package/dist/script-BVJ9BbPm.cjs.map +1 -0
  9. package/dist/script-D1fn4Dm4.js +6 -0
  10. package/dist/script-D1fn4Dm4.js.map +1 -0
  11. package/dist/src/auth/AuthGuard.d.ts +69 -4
  12. package/dist/src/auth/LoginForm.d.ts +41 -7
  13. package/dist/src/auth/engine.d.ts +15 -13
  14. package/dist/src/auth/provider.d.ts +10 -10
  15. package/dist/src/auth/storage.d.ts +1 -1
  16. package/dist/src/auth/types.d.ts +3 -3
  17. package/dist/src/components/brand/karni-mark.d.ts +4 -4
  18. package/dist/src/components/chart/data-chart.d.ts +66 -11
  19. package/dist/src/components/empty-state/empty-state.d.ts +22 -2
  20. package/dist/src/components/file-upload/FileUpload.d.ts +67 -3
  21. package/dist/src/components/layout/layout.d.ts +104 -9
  22. package/dist/src/components/layout/route-focus.d.ts +43 -0
  23. package/dist/src/components/modal/modal.d.ts +49 -8
  24. package/dist/src/components/page/page.d.ts +36 -5
  25. package/dist/src/components/primitives/button.d.ts +1 -0
  26. package/dist/src/components/primitives/checkbox.d.ts +4 -1
  27. package/dist/src/components/primitives/dropdown-menu.d.ts +1 -1
  28. package/dist/src/components/primitives/icons.d.ts +9 -1
  29. package/dist/src/components/primitives/input.d.ts +4 -1
  30. package/dist/src/components/primitives/label.d.ts +4 -1
  31. package/dist/src/components/primitives/select.d.ts +37 -6
  32. package/dist/src/components/primitives/skeleton.d.ts +4 -1
  33. package/dist/src/components/primitives/switch.d.ts +4 -1
  34. package/dist/src/components/primitives/tabs.d.ts +2 -2
  35. package/dist/src/components/primitives/textarea.d.ts +4 -1
  36. package/dist/src/components/resource/ResourceDetail.d.ts +3 -3
  37. package/dist/src/components/resource/ResourceForm.d.ts +4 -3
  38. package/dist/src/components/resource/ResourceSelect.d.ts +3 -1
  39. package/dist/src/components/resource/ResourceTable.d.ts +9 -3
  40. package/dist/src/components/resource/audit-fields.d.ts +1 -1
  41. package/dist/src/components/resource/detail/ResourceDetail.d.ts +109 -11
  42. package/dist/src/components/resource/empty-value.d.ts +17 -0
  43. package/dist/src/components/resource/form/ResourceForm.d.ts +65 -6
  44. package/dist/src/components/resource/form/field-inputs.d.ts +15 -1
  45. package/dist/src/components/resource/form/form-states.d.ts +87 -0
  46. package/dist/src/components/resource/form/prepare.d.ts +3 -3
  47. package/dist/src/components/resource/form/server-errors.d.ts +14 -0
  48. package/dist/src/components/resource/history/ChangeHistory.d.ts +46 -4
  49. package/dist/src/components/resource/import/ResourceImport.d.ts +73 -7
  50. package/dist/src/components/resource/nouns.d.ts +12 -0
  51. package/dist/src/components/resource/query-state.d.ts +20 -0
  52. package/dist/src/components/resource/resource-names.d.ts +23 -0
  53. package/dist/src/components/resource/select/ResourceSelect.d.ts +39 -8
  54. package/dist/src/components/resource/select/select-states.d.ts +68 -0
  55. package/dist/src/components/resource/select/use-option-pages.d.ts +37 -0
  56. package/dist/src/components/resource/select/venue-backend.d.ts +144 -0
  57. package/dist/src/components/resource/table/BulkActionsBar.d.ts +62 -5
  58. package/dist/src/components/resource/table/CardList.d.ts +31 -0
  59. package/dist/src/components/resource/table/ConfirmDialog.d.ts +28 -0
  60. package/dist/src/components/resource/table/ExportButtons.d.ts +2 -2
  61. package/dist/src/components/resource/table/FilterBar.d.ts +3 -3
  62. package/dist/src/components/resource/table/PaginationControls.d.ts +15 -3
  63. package/dist/src/components/resource/table/ResourceTable.d.ts +79 -14
  64. package/dist/src/components/resource/table/RowCard.d.ts +31 -0
  65. package/dist/src/components/resource/table/TableView.d.ts +29 -0
  66. package/dist/src/components/resource/table/ViewOptions.d.ts +19 -0
  67. package/dist/src/components/resource/table/access.d.ts +12 -0
  68. package/dist/src/components/resource/table/derive-columns.d.ts +2 -2
  69. package/dist/src/components/resource/table/grid-navigation.d.ts +23 -0
  70. package/dist/src/components/resource/table/layout.d.ts +37 -0
  71. package/dist/src/components/resource/table/preferences.d.ts +12 -0
  72. package/dist/src/components/resource/table/slots.d.ts +41 -0
  73. package/dist/src/components/resource/table/states.d.ts +91 -0
  74. package/dist/src/components/resource/table/story-fixtures.d.ts +25 -0
  75. package/dist/src/components/resource/table/structural-columns.d.ts +25 -0
  76. package/dist/src/components/resource/table/table-icons.d.ts +16 -0
  77. package/dist/src/components/resource/table/table-utils.d.ts +27 -3
  78. package/dist/src/components/resource/table/url-state.d.ts +67 -0
  79. package/dist/src/components/resource/table/use-container-width.d.ts +10 -0
  80. package/dist/src/components/resource/testapp-fixtures.d.ts +78 -0
  81. package/dist/src/components/stat/stat.d.ts +36 -6
  82. package/dist/src/components/toast/toast-store.d.ts +1 -1
  83. package/dist/src/components/toast/toaster.d.ts +39 -6
  84. package/dist/src/components/toast/use-toast.d.ts +1 -1
  85. package/dist/src/hooks/keys.d.ts +4 -4
  86. package/dist/src/hooks/multipart.d.ts +9 -9
  87. package/dist/src/hooks/params.d.ts +1 -1
  88. package/dist/src/hooks/types.d.ts +3 -3
  89. package/dist/src/hooks/useChangeHistory.d.ts +3 -2
  90. package/dist/src/hooks/useResourceImport.d.ts +2 -2
  91. package/dist/src/hooks/useResourceMutations.d.ts +2 -2
  92. package/dist/src/hooks/useUnsavedChangesGuard.d.ts +31 -0
  93. package/dist/src/index.d.ts +49 -31
  94. package/dist/src/lib/roles.d.ts +4 -4
  95. package/dist/src/lib/variants.d.ts +1 -1
  96. package/dist/src/primitives/registry.d.ts +81 -0
  97. package/dist/src/schema/client.d.ts +10 -10
  98. package/dist/src/schema/parse.d.ts +2 -2
  99. package/dist/src/schema/provider.d.ts +10 -3
  100. package/dist/src/schema/types.d.ts +18 -9
  101. package/dist/src/schema/useSchema.d.ts +5 -1
  102. package/dist/src/states/ErrorState.d.ts +43 -0
  103. package/dist/src/states/PendingButton.d.ts +16 -0
  104. package/dist/src/states/describe-error.d.ts +25 -0
  105. package/dist/src/states/hooks.d.ts +10 -0
  106. package/dist/src/theme/script.d.ts +12 -0
  107. package/dist/src/theme/theme.d.ts +45 -0
  108. package/dist/styles.css +174 -13
  109. package/dist/tailwind-preset.cjs +2 -2
  110. package/dist/tailwind-preset.cjs.map +1 -1
  111. package/dist/tailwind-preset.d.ts +83 -1
  112. package/dist/tailwind-preset.js +181 -62
  113. package/dist/tailwind-preset.js.map +1 -1
  114. package/dist/tailwind-v4.css +31 -0
  115. package/dist/vite-plugin.cjs +2 -0
  116. package/dist/vite-plugin.cjs.map +1 -0
  117. package/dist/vite-plugin.d.ts +19 -0
  118. package/dist/vite-plugin.js +16 -0
  119. package/dist/vite-plugin.js.map +1 -0
  120. package/eslint-plugin/index.js +4 -0
  121. package/eslint-plugin/rules/max-page-lines.js +1 -1
  122. package/eslint-plugin/rules/no-deep-imports.js +1 -1
  123. package/eslint-plugin/rules/no-direct-http.js +1 -1
  124. package/eslint-plugin/rules/no-raw-elements.js +1 -1
  125. package/eslint-plugin/rules/no-style-literals.js +222 -0
  126. package/eslint-plugin/rules.json +10 -4
  127. package/package.json +22 -19
@@ -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
- "useToast"
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
- /** Rendered for anonymous users and role mismatches; default nothing. */
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 while the auth status is still "loading"; default nothing. */
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
- /** Renders its children only for a signed-in user, optionally with one of the given roles; otherwise its `fallback`. */
12
- export declare function AuthGuard({ roles, fallback, loading, children, }: AuthGuardProps): ReactElement | null;
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 — the sign-in form every app needs (contract: auth).
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/`); without a handler the
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; navigate from here. */
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; `null` renders none. Default "Sign in". */
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
- /** Shown while the auth status is "loading" is irrelevant here; this disables the form. */
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
- /** Email and password sign-in form with inline errors and a callback for the two-factor step. */
29
- export declare function LoginForm({ onSuccess, on2FA, title, description, submitLabel, disabled, className, }: LoginFormProps): React.JSX.Element;
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
- * consulted (shape-guarded) for `authMode` before fetching the document.
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 (including a `getAccessToken`
53
- * the app passed to createKarniClient — an unmounted AuthProvider leaves
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; the retry's outcome is always surfaced.
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; any failure means anonymous. */
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) — requests then go out without the header and the
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; resolves true on success. */
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 — what `@karni/impersonation-ui` does
89
- * when a support user starts or stops impersonating (the backend answers
90
- * with tokens for the other identity). JWT mode only; in session mode the
91
- * server-side session already changed, so this just re-syncs `/me/`.
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"; when omitted, the mode comes from the schema root's
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; default localStorage adapter. */
11
+ /** JWT mode refresh-token storage. Defaults to the localStorage adapter. */
12
12
  tokenStorage?: TokenStorage;
13
- /** Session mode CSRF cookie name; default "csrftoken" (D6/A4). */
13
+ /** Session mode CSRF cookie name. Default "csrftoken" (D6/A4). */
14
14
  csrfCookieName?: string;
15
- /** Session mode CSRF header name; default "X-CSRFToken" (D6/A4). */
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; default "/auth". */
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; session mode re-syncs `/me/`),
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); application code should
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
- /** Holds the signed-in user and attaches credentials (JWT or session) to every request; render it inside `KarniProvider`. */
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`; throws a descriptive error outside `AuthProvider`. */
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
- * Keeps the refresh token in localStorage; the default storage. `key` defaults to "karni.refreshToken". Every storage
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.
@@ -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; every other field the
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
- * the 2FA UI is out of scope in v0.1, so consumers exchange the ephemeral
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) — the small variant at 32px and below, the full mark above.
7
+ * default): the small variant at 32px and below, the full mark above.
8
8
  */
9
9
  variant?: "auto" | "full" | "simple";
10
- /** Accessible name; pass an empty string to hide a purely decorative mark. */
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, inline SVG — no asset to load or bundle.
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; a project replaces it by passing its own
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>); the REAL accessible data
7
- * surface is a visually-hidden <table> rendered next to it. The hover
8
- * tooltip is a visual-only affordance rendered OUTSIDE the role="img"
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 — whole class names, per contract D7).
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; series cycle through --karni-chart-1..5. */
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
- /** Escape hatch: fully custom tooltip content. */
91
+ /** Fully custom tooltip content. Prefer the `Tooltip` slot. */
49
92
  renderTooltip?: (context: DataChartTooltipContext) => React.ReactNode;
50
- /** viewBox width (default 640) — rendered size is responsive. */
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 tooltip (default true). */
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
- * Escape hatches: `valueFormatter`/`xTickFormatter`, `renderTooltip`,
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: every visual part is a slot; `className` merges via cn();
18
- * extra children render under the action.
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>>;