@karni/ui 0.0.0-stage → 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 (142) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +928 -2
  3. package/dist/index.cjs +9 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.js +8699 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/manifest.json +102 -0
  8. package/dist/mockServiceWorker.js +361 -0
  9. package/dist/script-BVJ9BbPm.cjs +2 -0
  10. package/dist/script-BVJ9BbPm.cjs.map +1 -0
  11. package/dist/script-D1fn4Dm4.js +6 -0
  12. package/dist/script-D1fn4Dm4.js.map +1 -0
  13. package/dist/src/auth/AuthGuard.d.ts +77 -0
  14. package/dist/src/auth/LoginForm.d.ts +63 -0
  15. package/dist/src/auth/engine.d.ts +101 -0
  16. package/dist/src/auth/provider.d.ts +43 -0
  17. package/dist/src/auth/storage.d.ts +26 -0
  18. package/dist/src/auth/types.d.ts +32 -0
  19. package/dist/src/auth/usePermission.d.ts +7 -0
  20. package/dist/src/components/brand/karni-mark.d.ts +21 -0
  21. package/dist/src/components/brand/mark-paths.d.ts +8 -0
  22. package/dist/src/components/chart/data-chart.d.ts +129 -0
  23. package/dist/src/components/empty-state/empty-state.d.ts +40 -0
  24. package/dist/src/components/file-upload/FileUpload.d.ts +108 -0
  25. package/dist/src/components/layout/layout.d.ts +150 -0
  26. package/dist/src/components/layout/route-focus.d.ts +43 -0
  27. package/dist/src/components/modal/modal.d.ts +86 -0
  28. package/dist/src/components/page/page.d.ts +60 -0
  29. package/dist/src/components/primitives/button.d.ts +45 -0
  30. package/dist/src/components/primitives/checkbox.d.ts +8 -0
  31. package/dist/src/components/primitives/dropdown-menu.d.ts +24 -0
  32. package/dist/src/components/primitives/icons.d.ts +19 -0
  33. package/dist/src/components/primitives/input.d.ts +7 -0
  34. package/dist/src/components/primitives/label.d.ts +8 -0
  35. package/dist/src/components/primitives/select.d.ts +94 -0
  36. package/dist/src/components/primitives/skeleton.d.ts +7 -0
  37. package/dist/src/components/primitives/switch.d.ts +8 -0
  38. package/dist/src/components/primitives/tabs.d.ts +10 -0
  39. package/dist/src/components/primitives/textarea.d.ts +7 -0
  40. package/dist/src/components/resource/ResourceDetail.d.ts +7 -0
  41. package/dist/src/components/resource/ResourceForm.d.ts +8 -0
  42. package/dist/src/components/resource/ResourceSelect.d.ts +9 -0
  43. package/dist/src/components/resource/ResourceTable.d.ts +17 -0
  44. package/dist/src/components/resource/audit-fields.d.ts +17 -0
  45. package/dist/src/components/resource/detail/ResourceDetail.d.ts +128 -0
  46. package/dist/src/components/resource/displayLabel.d.ts +12 -0
  47. package/dist/src/components/resource/empty-value.d.ts +17 -0
  48. package/dist/src/components/resource/form/ResourceForm.d.ts +109 -0
  49. package/dist/src/components/resource/form/field-inputs.d.ts +34 -0
  50. package/dist/src/components/resource/form/fixtures.d.ts +24 -0
  51. package/dist/src/components/resource/form/form-states.d.ts +87 -0
  52. package/dist/src/components/resource/form/prepare.d.ts +54 -0
  53. package/dist/src/components/resource/form/server-errors.d.ts +14 -0
  54. package/dist/src/components/resource/history/ChangeHistory.d.ts +60 -0
  55. package/dist/src/components/resource/import/ResourceImport.d.ts +84 -0
  56. package/dist/src/components/resource/nouns.d.ts +12 -0
  57. package/dist/src/components/resource/query-state.d.ts +20 -0
  58. package/dist/src/components/resource/resource-names.d.ts +23 -0
  59. package/dist/src/components/resource/select/ResourceSelect.d.ts +67 -0
  60. package/dist/src/components/resource/select/select-states.d.ts +68 -0
  61. package/dist/src/components/resource/select/use-debounced-value.d.ts +2 -0
  62. package/dist/src/components/resource/select/use-option-pages.d.ts +37 -0
  63. package/dist/src/components/resource/select/venue-backend.d.ts +144 -0
  64. package/dist/src/components/resource/table/BulkActionsBar.d.ts +77 -0
  65. package/dist/src/components/resource/table/CardList.d.ts +31 -0
  66. package/dist/src/components/resource/table/ConfirmDialog.d.ts +28 -0
  67. package/dist/src/components/resource/table/ExportButtons.d.ts +17 -0
  68. package/dist/src/components/resource/table/FilterBar.d.ts +29 -0
  69. package/dist/src/components/resource/table/PaginationControls.d.ts +31 -0
  70. package/dist/src/components/resource/table/ResourceTable.d.ts +137 -0
  71. package/dist/src/components/resource/table/RowCard.d.ts +31 -0
  72. package/dist/src/components/resource/table/TableView.d.ts +29 -0
  73. package/dist/src/components/resource/table/ViewOptions.d.ts +19 -0
  74. package/dist/src/components/resource/table/access.d.ts +12 -0
  75. package/dist/src/components/resource/table/demo-fixtures.d.ts +25 -0
  76. package/dist/src/components/resource/table/derive-columns.d.ts +33 -0
  77. package/dist/src/components/resource/table/grid-navigation.d.ts +23 -0
  78. package/dist/src/components/resource/table/layout.d.ts +37 -0
  79. package/dist/src/components/resource/table/preferences.d.ts +12 -0
  80. package/dist/src/components/resource/table/slots.d.ts +41 -0
  81. package/dist/src/components/resource/table/states.d.ts +91 -0
  82. package/dist/src/components/resource/table/story-fixtures.d.ts +25 -0
  83. package/dist/src/components/resource/table/structural-columns.d.ts +25 -0
  84. package/dist/src/components/resource/table/table-icons.d.ts +16 -0
  85. package/dist/src/components/resource/table/table-utils.d.ts +48 -0
  86. package/dist/src/components/resource/table/url-state.d.ts +67 -0
  87. package/dist/src/components/resource/table/use-container-width.d.ts +10 -0
  88. package/dist/src/components/resource/table/use-debounced-value.d.ts +1 -0
  89. package/dist/src/components/resource/testapp-fixtures.d.ts +78 -0
  90. package/dist/src/components/stat/stat.d.ts +54 -0
  91. package/dist/src/components/toast/toast-store.d.ts +36 -0
  92. package/dist/src/components/toast/toaster.d.ts +66 -0
  93. package/dist/src/components/toast/use-toast.d.ts +24 -0
  94. package/dist/src/hooks/keys.d.ts +24 -0
  95. package/dist/src/hooks/multipart.d.ts +44 -0
  96. package/dist/src/hooks/params.d.ts +25 -0
  97. package/dist/src/hooks/types.d.ts +41 -0
  98. package/dist/src/hooks/useChangeHistory.d.ts +30 -0
  99. package/dist/src/hooks/useResourceImport.d.ts +107 -0
  100. package/dist/src/hooks/useResourceItem.d.ts +4 -0
  101. package/dist/src/hooks/useResourceList.d.ts +4 -0
  102. package/dist/src/hooks/useResourceMutations.d.ts +13 -0
  103. package/dist/src/hooks/useUnsavedChangesGuard.d.ts +31 -0
  104. package/dist/src/index.d.ts +113 -0
  105. package/dist/src/lib/cn.d.ts +8 -0
  106. package/dist/src/lib/roles.d.ts +45 -0
  107. package/dist/src/lib/variants.d.ts +33 -0
  108. package/dist/src/primitives/registry.d.ts +81 -0
  109. package/dist/src/schema/client.d.ts +90 -0
  110. package/dist/src/schema/parse.d.ts +101 -0
  111. package/dist/src/schema/provider.d.ts +20 -0
  112. package/dist/src/schema/types.d.ts +93 -0
  113. package/dist/src/schema/useSchema.d.ts +21 -0
  114. package/dist/src/schema/zod.d.ts +4 -0
  115. package/dist/src/states/ErrorState.d.ts +43 -0
  116. package/dist/src/states/PendingButton.d.ts +16 -0
  117. package/dist/src/states/describe-error.d.ts +25 -0
  118. package/dist/src/states/hooks.d.ts +10 -0
  119. package/dist/src/theme/script.d.ts +12 -0
  120. package/dist/src/theme/theme.d.ts +45 -0
  121. package/dist/styles.css +248 -0
  122. package/dist/tailwind-preset.cjs +2 -0
  123. package/dist/tailwind-preset.cjs.map +1 -0
  124. package/dist/tailwind-preset.d.ts +140 -0
  125. package/dist/tailwind-preset.js +183 -0
  126. package/dist/tailwind-preset.js.map +1 -0
  127. package/dist/tailwind-v4.css +31 -0
  128. package/dist/vite-plugin.cjs +2 -0
  129. package/dist/vite-plugin.cjs.map +1 -0
  130. package/dist/vite-plugin.d.ts +19 -0
  131. package/dist/vite-plugin.js +16 -0
  132. package/dist/vite-plugin.js.map +1 -0
  133. package/eslint-plugin/index.js +76 -0
  134. package/eslint-plugin/lib/docs.js +11 -0
  135. package/eslint-plugin/lib/glob.js +51 -0
  136. package/eslint-plugin/rules/max-page-lines.js +65 -0
  137. package/eslint-plugin/rules/no-deep-imports.js +61 -0
  138. package/eslint-plugin/rules/no-direct-http.js +128 -0
  139. package/eslint-plugin/rules/no-raw-elements.js +65 -0
  140. package/eslint-plugin/rules/no-style-literals.js +222 -0
  141. package/eslint-plugin/rules.json +32 -0
  142. package/package.json +156 -4
@@ -0,0 +1,101 @@
1
+ import { KarniClient } from '../schema/client';
2
+ import { TokenStorage } from './storage';
3
+ import { AuthStatus, KarniUser, LoginResult } from './types';
4
+ export type AuthMode = "jwt" | "session";
5
+ export interface AuthEngineOptions {
6
+ client: KarniClient;
7
+ tokenStorage: TokenStorage;
8
+ /** Skips schema-based mode detection entirely when set. */
9
+ modeOverride?: AuthMode;
10
+ /** Session-mode CSRF cookie name (A4 pin). Default "csrftoken". */
11
+ csrfCookieName?: string;
12
+ /** Session-mode CSRF header name (A4 pin). Default "X-CSRFToken". */
13
+ csrfHeaderName?: string;
14
+ /** Path prefix of the auth endpoints, relative to the client's baseUrl. */
15
+ authBasePath?: string;
16
+ /**
17
+ * Reads whatever the schema loader cached under karniKeys.schema(). The
18
+ * engine consults it (shape-guarded) for `authMode` before fetching the
19
+ * document.
20
+ */
21
+ readCachedSchema?: () => unknown;
22
+ }
23
+ export interface AuthSnapshot {
24
+ readonly status: AuthStatus;
25
+ readonly user: KarniUser | null;
26
+ readonly mode: AuthMode | null;
27
+ }
28
+ /**
29
+ * Reads the auth mode out of either a ParsedSchema (`authMode`) or a raw
30
+ * OpenAPI document (root `x-karni-auth-mode`), shape-guarded.
31
+ */
32
+ export declare function readAuthMode(value: unknown): AuthMode | undefined;
33
+ /** Validates/normalizes a `me` payload into a KarniUser (roles default []). */
34
+ export declare function normalizeUser(value: unknown): KarniUser | null;
35
+ export declare class AuthEngine {
36
+ private readonly client;
37
+ private readonly tokenStorage;
38
+ private readonly modeOverride;
39
+ private readonly csrfCookieName;
40
+ private readonly csrfHeaderName;
41
+ private readonly authBasePath;
42
+ private readonly readCachedSchema;
43
+ private mode;
44
+ private accessToken;
45
+ private refreshPromise;
46
+ private initPromise;
47
+ private readonly listeners;
48
+ private state;
49
+ constructor(options: AuthEngineOptions);
50
+ /** Registers the D6 hook points on the client. Idempotent. */
51
+ attach(): void;
52
+ /**
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.
56
+ */
57
+ detach(): void;
58
+ private csrfHeaders;
59
+ /**
60
+ * The client calls this at most once per 401-ing request (D6). Returning
61
+ * true makes the client retry that request exactly once with
62
+ * freshly-injected credentials. The retry's outcome is always surfaced.
63
+ */
64
+ private handleUnauthorized;
65
+ readonly subscribe: (listener: () => void) => (() => void);
66
+ readonly getSnapshot: () => AuthSnapshot;
67
+ private setState;
68
+ /** Idempotent: the first call kicks off mode resolution + session restore. */
69
+ ensureInitialized(): Promise<void>;
70
+ private initialize;
71
+ private fetchSchemaAuthMode;
72
+ /** Fetches `/me/` and mirrors the result. Any failure means anonymous. */
73
+ private syncUser;
74
+ /**
75
+ * Reads the CSRF cookie, bootstrapping it via `GET <authBasePath>/csrf/`
76
+ * when absent (A4). Returns null when the cookie stays unreadable (e.g.
77
+ * CSRF_COOKIE_HTTPONLY). Requests then go out without the header and the
78
+ * backend's 403 surfaces, which is the honest failure mode.
79
+ */
80
+ private ensureCsrfToken;
81
+ /** ONE shared in-flight refresh. Resolves true on success. */
82
+ private ensureRefresh;
83
+ private performRefresh;
84
+ login(email: string, password: string): Promise<LoginResult>;
85
+ private fetchMe;
86
+ /** Best-effort server logout, then always clears local state (anonymous). */
87
+ logout(): Promise<void>;
88
+ /**
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/`.
94
+ */
95
+ adoptTokens(access: string | null, refresh: string | null): Promise<void>;
96
+ /**
97
+ * Re-syncs the session: rotates the tokens in JWT mode (A3: /refresh/ is
98
+ * 405 in session mode, so session mode only re-fetches /me/).
99
+ */
100
+ refresh(): Promise<void>;
101
+ }
@@ -0,0 +1,43 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { TokenStorage } from './storage';
3
+ import { AuthStatus, KarniUser, LoginResult } from './types';
4
+ export interface AuthProviderProps {
5
+ /**
6
+ * "jwt" | "session". When omitted, the mode comes from the schema root's
7
+ * x-karni-auth-mode (D6), falling back to "jwt" when the schema does not
8
+ * declare one.
9
+ */
10
+ mode?: "jwt" | "session";
11
+ /** JWT mode refresh-token storage. Defaults to the localStorage adapter. */
12
+ tokenStorage?: TokenStorage;
13
+ /** Session mode CSRF cookie name. Default "csrftoken" (D6/A4). */
14
+ csrfCookieName?: string;
15
+ /** Session mode CSRF header name. Default "X-CSRFToken" (D6/A4). */
16
+ csrfHeaderName?: string;
17
+ /** Path prefix of the auth endpoints relative to baseUrl. Default "/auth". */
18
+ authBasePath?: string;
19
+ children?: ReactNode;
20
+ }
21
+ export interface AuthContextValue {
22
+ user: KarniUser | null;
23
+ roles: string[];
24
+ status: AuthStatus;
25
+ login(email: string, password: string): Promise<LoginResult>;
26
+ logout(): Promise<void>;
27
+ refresh(): Promise<void>;
28
+ /**
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
+ */
32
+ adoptTokens(access: string | null, refresh: string | null): Promise<void>;
33
+ }
34
+ /**
35
+ * The React mirror of the auth engine's state. Exported for advanced
36
+ * composition (stories, tests, custom providers). Application code should
37
+ * use <AuthProvider> + useAuth().
38
+ */
39
+ export declare const AuthContext: import('react').Context<AuthContextValue | null>;
40
+ /** Rendered inside `KarniProvider`, it holds the signed-in user and attaches credentials (JWT or session) to every request. */
41
+ export declare function AuthProvider({ mode, tokenStorage, csrfCookieName, csrfHeaderName, authBasePath, children, }: AuthProviderProps): ReactElement;
42
+ /** Returns the signed-in user, auth status, `login` and `logout`, and throws a descriptive error outside `AuthProvider`. */
43
+ export declare function useAuth(): AuthContextValue;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pluggable refresh-token storage for JWT mode (contract "Hooks + auth").
3
+ *
4
+ * The default adapter is `localStorage`: it survives reloads and is the
5
+ * simplest thing that works, but any XSS on your page can read it. Stricter
6
+ * setups use `createMemoryTokenStorage()` (logout on reload) or implement
7
+ * the three-method `TokenStorage` interface themselves (e.g. backed by a
8
+ * worker, or by an HttpOnly-cookie BFF). Session mode needs no storage.
9
+ */
10
+ export interface TokenStorage {
11
+ get(): string | null;
12
+ set(token: string): void;
13
+ clear(): void;
14
+ }
15
+ /**
16
+ * The default storage, which keeps the refresh token in localStorage. `key` defaults to "karni.refreshToken". Every storage
17
+ * access is guarded: when localStorage is unavailable (SSR, private windows,
18
+ * blocked site data) the adapter degrades to a no-op that returns `null`
19
+ * instead of throwing.
20
+ */
21
+ export declare function createLocalStorageTokenStorage(key?: string): TokenStorage;
22
+ /**
23
+ * In-memory adapter: most secure (nothing persisted anywhere), but a page
24
+ * reload logs the user out.
25
+ */
26
+ export declare function createMemoryTokenStorage(): TokenStorage;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Public auth types (contract "Hooks + auth" + D6, accounts contract A15).
3
+ */
4
+ /**
5
+ * Shape of the `/api/auth/me/` response. The accounts contract (A15)
6
+ * guarantees `roles: string[]` on MeSerializer. Every other field the
7
+ * backend's user serializer exposes rides along via the index signature.
8
+ */
9
+ export interface KarniUser {
10
+ id: string | number;
11
+ email: string;
12
+ roles: string[];
13
+ [extra: string]: unknown;
14
+ }
15
+ /**
16
+ * "loading" while the initial session restore runs, then "authenticated" or
17
+ * "anonymous". A failed token refresh always lands on "anonymous" (D6).
18
+ */
19
+ export type AuthStatus = "loading" | "authenticated" | "anonymous";
20
+ /**
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
24
+ * token themselves via `POST /api/auth/2fa/login/`.
25
+ */
26
+ export type LoginResult = {
27
+ ok: true;
28
+ user: KarniUser;
29
+ } | {
30
+ requires2fa: true;
31
+ ephemeralToken: string;
32
+ };
@@ -0,0 +1,7 @@
1
+ export interface PermissionHelpers {
2
+ /** True when the user has at least one of the given role names. */
3
+ hasRole(...names: string[]): boolean;
4
+ isAuthenticated: boolean;
5
+ }
6
+ /** `isAuthenticated` and `hasRole(...names)` for the signed-in user. */
7
+ export declare function usePermission(): PermissionHelpers;
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ export interface KarniMarkProps extends Omit<React.SVGAttributes<SVGSVGElement>, "children"> {
3
+ /** Rendered size in px (width and height). Default 28. */
4
+ size?: number;
5
+ /**
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.
8
+ */
9
+ variant?: "auto" | "full" | "simple";
10
+ /** Accessible name. Pass an empty string to hide a purely decorative mark. */
11
+ title?: string;
12
+ }
13
+ /**
14
+ * The Karni mark as inline SVG, so there is no asset to load or bundle.
15
+ *
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
18
+ * `sidebarHeader` (the scaffold already does). Do not recolour or restyle it:
19
+ * the mark is Karni's, see assets/brand/README.md in the repository.
20
+ */
21
+ export declare const KarniMark: React.ForwardRefExoticComponent<KarniMarkProps & React.RefAttributes<SVGSVGElement>>;
@@ -0,0 +1,8 @@
1
+ export declare const MARK_VIEWBOX = "0 0 1000 1000";
2
+ /** The full mark: ground, rings, strokes. */
3
+ export declare const MARK_PATHS: ReadonlyArray<{
4
+ readonly fill: string;
5
+ readonly d: string;
6
+ }>;
7
+ /** The small-size variant's SVG body (use at 32px and below). */
8
+ export declare const MARK_SIMPLE_BODY = "<path fill=\"#000000\" d=\"M495.8 884.4C291.9 874.2 135.4 724 114.7 518.5C97.9 351.3 160.2 221 290.9 150.2C372.3 106.1 439.7 86.4 513.6 85.2C686 82.3 812.9 171.5 867 333.5C903.9 444 890.2 573.8 828.6 698.6C766.8 823.4 644.3 891.8 495.8 884.4Z\"/>\n<circle cx=\"340\" cy=\"300\" r=\"44\" fill=\"#FB5D08\"/>\n<circle cx=\"500\" cy=\"276\" r=\"44\" fill=\"#FB5D08\"/>\n<circle cx=\"660\" cy=\"300\" r=\"44\" fill=\"#FB5D08\"/>\n<path fill=\"none\" stroke=\"#FFFFFF\" stroke-width=\"58\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M318 420v280l182-250 182 250V420M500 450 410 700M500 450l90 250\"/>";
@@ -0,0 +1,129 @@
1
+ import * as React from "react";
2
+ /**
3
+ * In-house SVG chart (contract: no chart dependency).
4
+ *
5
+ * Accessibility model (contract D10): the SVG is a labelled image
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.
12
+ *
13
+ * Colors come exclusively from the --karni-chart-1..5 tokens, via the
14
+ * Tailwind preset's chart color scale (stroke-chart-N / fill-chart-N /
15
+ * bg-chart-N classes, whole class names per contract D7).
16
+ */
17
+ export type DataChartKind = "line" | "bar";
18
+ export interface DataChartPoint {
19
+ label: string;
20
+ value: number;
21
+ }
22
+ export interface DataChartSeries {
23
+ name: string;
24
+ data: readonly DataChartPoint[];
25
+ }
26
+ export interface DataChartTooltipContext {
27
+ /** Category (x-axis) label under the pointer. */
28
+ label: string;
29
+ /** One entry per series that has a value at this category. */
30
+ points: ReadonlyArray<{
31
+ seriesName: string;
32
+ value: number;
33
+ swatchClassName: string;
34
+ }>;
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
+ }
72
+ export interface DataChartProps {
73
+ kind: DataChartKind;
74
+ /** Single-series shorthand (ignored when `series` is given). */
75
+ data?: readonly DataChartPoint[];
76
+ /** Multi-series data. Series cycle through --karni-chart-1..5. */
77
+ series?: readonly DataChartSeries[];
78
+ /** Chart title: SVG <title>, aria-label and the hidden table's caption. */
79
+ title: string;
80
+ /** Longer description for the SVG <desc>. */
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;
87
+ /** Formats values in the tooltip, y-axis ticks and the hidden table. */
88
+ valueFormatter?: (value: number) => string;
89
+ /** Formats x-axis tick labels (default: the category label itself). */
90
+ xTickFormatter?: (label: string) => string;
91
+ /** Fully custom tooltip content. Prefer the `Tooltip` slot. */
92
+ renderTooltip?: (context: DataChartTooltipContext) => React.ReactNode;
93
+ /** viewBox width (default 640). The rendered size is responsive. */
94
+ width?: number;
95
+ /** viewBox height (default 240). */
96
+ height?: number;
97
+ /** Approximate number of y-axis ticks (default 4). */
98
+ yTickCount?: number;
99
+ /** Show the series legend (default: only for 2+ series). */
100
+ showLegend?: boolean;
101
+ /** Show the tooltip on hover and keyboard focus (default true). */
102
+ showTooltip?: boolean;
103
+ /** Column header of the hidden table's category column (default "Label"). */
104
+ categoryHeader?: string;
105
+ /** Text shown when there is no plottable data. */
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>;
111
+ className?: string;
112
+ }
113
+ /** The default pieces of the chart, for wrapping one. */
114
+ export declare const defaultDataChartSlots: DataChartSlots;
115
+ /**
116
+ * Minimal line/bar chart driven by `{label, value}` points or named series.
117
+ *
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`,
125
+ * `width`/`height`/`yTickCount`, legend and tooltip toggles, `className`
126
+ * (merged via cn()). Colors are only themeable through the
127
+ * --karni-chart-1..5 CSS variables.
128
+ */
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;
@@ -0,0 +1,40 @@
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
+ }
17
+ export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
18
+ /** Optional illustration/icon slot, rendered above the title. */
19
+ icon?: React.ReactNode;
20
+ /** Short headline ("No events yet"). */
21
+ title: React.ReactNode;
22
+ /** Optional supporting copy under the title. */
23
+ description?: React.ReactNode;
24
+ /** Slot for a call to action (e.g. a "Create" button). */
25
+ action?: React.ReactNode;
26
+ /** Replaces the Icon, Title, Description or Actions. See EmptyStateSlots. */
27
+ slots?: Partial<EmptyStateSlots>;
28
+ children?: React.ReactNode;
29
+ }
30
+ /** The default pieces of the placeholder, for wrapping one. */
31
+ export declare const defaultEmptyStateSlots: EmptyStateSlots;
32
+ /**
33
+ * Centered placeholder for empty collections: icon, title, description and
34
+ * an action slot. Used by ResourceTable's `empty` slot and reusable anywhere.
35
+ *
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.
39
+ */
40
+ export declare const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,108 @@
1
+ import * as React from "react";
2
+ /** Why a file was refused by <FileUpload> (surfaced through `onReject`). */
3
+ export type FileUploadRejectReason = "accept" | "size";
4
+ /** What the `Dropzone` slot receives: the props of the button it replaces, plus its state. */
5
+ export interface FileUploadDropzoneProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
6
+ /** Attach to the button: focus returns to it after a file is removed. */
7
+ buttonRef: React.Ref<HTMLButtonElement>;
8
+ /** A file is being dragged over the dropzone. */
9
+ dragging: boolean;
10
+ multiple: boolean;
11
+ /** The `uploading` prop. */
12
+ uploading: boolean;
13
+ /** The `children` prop (custom content), or undefined for the default text. */
14
+ children?: React.ReactNode;
15
+ }
16
+ /** What the `FileRow` slot receives: one current file. Render an <li>. */
17
+ export interface FileUploadFileRowProps {
18
+ /** A pending `File`, or the URL string of a file already uploaded. */
19
+ file: File | string;
20
+ /** The file name (a URL's last path segment). */
21
+ name: string;
22
+ /** A pending file's size ("12 KB"), undefined for a URL. */
23
+ size?: string;
24
+ /** Removes the file. Undefined when it cannot be removed (clearable=false, disabled, uploading). */
25
+ onRemove?: () => void;
26
+ }
27
+ /** The parts of the picker a project can replace (`slots={{ FileRow: Thumbnail }}`). */
28
+ export interface FileUploadSlots {
29
+ /** The drop target: a button that opens the file picker. */
30
+ Dropzone: React.ComponentType<FileUploadDropzoneProps>;
31
+ /** One row of the current-files list. */
32
+ FileRow: React.ComponentType<FileUploadFileRowProps>;
33
+ }
34
+ export interface FileUploadProps {
35
+ /**
36
+ * Current value. A `string` is an existing uploaded file (the display URL a
37
+ * karni backend returns), and a `File` is a pending upload. Arrays are for
38
+ * `multiple` mode and may mix both.
39
+ */
40
+ value?: File | string | ReadonlyArray<File | string> | null;
41
+ /**
42
+ * Emits the newly chosen `File` (or `File[]` when `multiple`), and `null`
43
+ * when the value is cleared. Existing URL strings are display-only: they
44
+ * can be removed (emitting `null` / the remaining files) but never re-emitted.
45
+ */
46
+ onChange?: (value: File | File[] | null) => void;
47
+ /** Same grammar as the native `accept` attribute (".png,image/*,application/pdf"). */
48
+ accept?: string;
49
+ multiple?: boolean;
50
+ disabled?: boolean;
51
+ /** Maximum size per file, in bytes. Larger files are rejected via `onReject`. */
52
+ maxSize?: number;
53
+ /** Called once per refused file with the reason ("accept" | "size"). */
54
+ onReject?: (file: File, reason: FileUploadRejectReason) => void;
55
+ /**
56
+ * The sentence shown (and announced) for a refused file. Default: "notes.txt
57
+ * isn't an accepted file type." / "big.png is larger than 1.0 MB.". Return
58
+ * null to show nothing (when `onReject` reports it elsewhere).
59
+ */
60
+ rejectionMessage?: (file: File, reason: FileUploadRejectReason, limits: {
61
+ accept?: string;
62
+ maxSize?: number;
63
+ }) => React.ReactNode;
64
+ /** An error to show under the dropzone (announced). It marks the field invalid. */
65
+ error?: React.ReactNode;
66
+ /** True while the file uploads: the dropzone shows progress and takes no new files. */
67
+ uploading?: boolean;
68
+ id?: string;
69
+ /** Forwarded to the hidden native file input. */
70
+ name?: string;
71
+ className?: string;
72
+ /** Replaces the default dropzone content (escape hatch). */
73
+ children?: React.ReactNode;
74
+ /** Set false to hide the per-file remove affordance. Default true. */
75
+ clearable?: boolean;
76
+ /** Replaces the Dropzone or FileRow (see FileUploadSlots). */
77
+ slots?: Partial<FileUploadSlots>;
78
+ "aria-label"?: string;
79
+ /** Ids of elements that label the dropzone (or point a <label htmlFor> at `id`). */
80
+ "aria-labelledby"?: string;
81
+ /** Ids of elements that describe the dropzone (help text, a form's error message). */
82
+ "aria-describedby"?: string;
83
+ /** Marks the dropzone invalid (a destructive border). `error` sets it too. */
84
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"];
85
+ /** Called when focus leaves the dropzone (react-hook-form's onBlur). */
86
+ onBlur?: React.FocusEventHandler<HTMLButtonElement>;
87
+ }
88
+ /** The default pieces of the picker, for wrapping one. */
89
+ export declare const defaultFileUploadSlots: FileUploadSlots;
90
+ /**
91
+ * Drag-and-drop or click-to-browse file picker that shows the current file and the pending ones.
92
+ *
93
+ * Contract "FileUpload".
94
+ *
95
+ * Shows an existing uploaded value (URL string) as a link, pending `File`
96
+ * values with name and size, and emits `File` objects. The hooks layer turns
97
+ * them into multipart bodies per amendment D2.
98
+ *
99
+ * Keyboard and screen readers: the dropzone is a button (Enter or Space opens
100
+ * the picker), labelled by its text, `aria-label`, `aria-labelledby` or a
101
+ * <label htmlFor={id}>. A refused file and the `error` prop are announced and
102
+ * linked through aria-describedby. `aria-invalid` (or `error`) marks it
103
+ * invalid. Removing a file returns focus to the dropzone.
104
+ *
105
+ * Escape hatches: `slots` replaces the Dropzone or FileRow, `children`
106
+ * replaces the dropzone's content, `rejectionMessage` rewords refusals.
107
+ */
108
+ export declare const FileUpload: React.ForwardRefExoticComponent<FileUploadProps & React.RefAttributes<HTMLButtonElement>>;