@bsuite/ui 1.0.3 → 1.1.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.
@@ -0,0 +1,98 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ /**
3
+ * DataUnavailable — the honest state.
4
+ *
5
+ * WHY THIS EXISTS
6
+ * ---------------
7
+ * Five live surfaces across crm7 and business-suite-unified shared one idiom:
8
+ *
9
+ * const rows = query.data ?? HARDCODED_FALLBACK
10
+ *
11
+ * That line turns every failure into a confident-looking answer. The worst
12
+ * instance rendered a fabricated 95% compliance score — including "Fair Work
13
+ * Compliance — Award rates verified" — when the underlying query was denied by
14
+ * RLS. An authorization failure was presented to the user as a compliance
15
+ * assurance. Another exported invented dollar figures to a file that then looks
16
+ * authoritative to whoever opens it.
17
+ *
18
+ * The operator's standing rule is: never display mock data in the UI,
19
+ * especially for financial or account-related information.
20
+ *
21
+ * THE CONTRACT
22
+ * ------------
23
+ * This component renders the ABSENCE of data. It therefore:
24
+ *
25
+ * 1. Takes no numeric prop and renders no number of its own. There is
26
+ * nothing here that can be misread as a measurement.
27
+ * 2. Never uses the success tone. A failure is never green.
28
+ * 3. Separates "nothing exists" from "we could not find out", and separates
29
+ * "you may not see this" from "this broke". Collapsing those is precisely
30
+ * the defect it replaces.
31
+ * 4. Surfaces the real reason verbatim, in a monospace diagnostic block, so
32
+ * the reader can tell a permission denial from a network fault.
33
+ * 5. Carries `data-slot="data-unavailable"` and `data-state`, so tests and
34
+ * lints can assert that a surface degraded honestly.
35
+ *
36
+ * Pair it with `resolveDataState` so the call site never re-derives the
37
+ * branching, and the `??` idiom has nowhere to come back.
38
+ */
39
+ export type DataUnavailableState =
40
+ /** The query is in flight. Nothing is known yet. */
41
+ 'loading'
42
+ /** The query succeeded and the answer is genuinely "none". */
43
+ | 'empty'
44
+ /** The query failed. We do not know the answer. */
45
+ | 'error'
46
+ /** The query was refused. The data may exist; this caller may not see it. */
47
+ | 'forbidden'
48
+ /** No queryable source is reachable from this surface at all. */
49
+ | 'unavailable';
50
+ export interface DataUnavailableProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
51
+ state: DataUnavailableState;
52
+ /**
53
+ * What could not be shown, as a specific noun — "Budgets", "Compliance
54
+ * checks", "OAuth clients". Overrides the generic preset heading.
55
+ */
56
+ title?: ReactNode;
57
+ description?: ReactNode;
58
+ /**
59
+ * The real, verbatim failure reason — an error message, a Postgres code.
60
+ * Rendered as monospace diagnostics, never as prose. Pass the actual thing;
61
+ * a sanitised reason is how "unknown" becomes "fine" again.
62
+ */
63
+ reason?: ReactNode;
64
+ /** Retry handler. Omit when retrying cannot help (`forbidden`). */
65
+ onRetry?: () => void;
66
+ retryLabel?: string;
67
+ /** Escape hatch for a site-specific action (e.g. "Request access"). */
68
+ action?: ReactNode;
69
+ /** `compact` suits an inline card slot; `block` a full page region. */
70
+ size?: 'compact' | 'block';
71
+ }
72
+ export declare function DataUnavailable({ state, title, description, reason, onRetry, retryLabel, action, size, className, ...props }: DataUnavailableProps): import("react").JSX.Element;
73
+ /**
74
+ * Structural subset of a TanStack Query result. Declared structurally on
75
+ * purpose: `@bsuite/ui` must not take a dependency on the data layer, and this
76
+ * shape is equally satisfiable by a hand-rolled hook.
77
+ */
78
+ export interface QueryLike<T> {
79
+ data: T | undefined;
80
+ isPending?: boolean;
81
+ isLoading?: boolean;
82
+ isError?: boolean;
83
+ error?: unknown;
84
+ }
85
+ /** Human-readable reason string for the `reason` prop. */
86
+ export declare function describeError(error: unknown): string;
87
+ /**
88
+ * Map a query result to either `'ready'` (render the real data) or the honest
89
+ * state to render instead.
90
+ *
91
+ * The point is that there is no fourth answer. A call site cannot get a
92
+ * fallback out of this function, which is what makes the class fix stick.
93
+ *
94
+ * @param isEmpty how to decide the data is genuinely empty. Defaults to
95
+ * "an array with no elements". Pass explicitly for object-shaped payloads.
96
+ */
97
+ export declare function resolveDataState<T>(query: QueryLike<T>, isEmpty?: (data: T) => boolean): DataUnavailableState | 'ready';
98
+ //# sourceMappingURL=data-unavailable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-unavailable.d.ts","sourceRoot":"","sources":["../src/data-unavailable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAMH,MAAM,MAAM,oBAAoB;AAC9B,oDAAoD;AAClD,SAAS;AACX,8DAA8D;GAC5D,OAAO;AACT,mDAAmD;GACjD,OAAO;AACT,6EAA6E;GAC3E,WAAW;AACb,iEAAiE;GAC/D,aAAa,CAAA;AA0JjB,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzF,KAAK,EAAE,oBAAoB,CAAA;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,uEAAuE;IACvE,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;CAC3B;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,KAAK,EACL,WAAW,EACX,MAAM,EACN,OAAO,EACP,UAAwB,EACxB,MAAM,EACN,IAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EAAE,oBAAoB,+BAiEtB;AAMD;;;;GAIG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAwBD,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EACnB,OAAO,GAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAmD,GACxE,oBAAoB,GAAG,OAAO,CAKhC"}
@@ -0,0 +1,162 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from './utils.js';
3
+ const PRESETS = {
4
+ loading: {
5
+ tone: 'info',
6
+ title: 'Loading…',
7
+ description: 'Fetching the current figures.',
8
+ icon: 'spinner',
9
+ live: 'polite',
10
+ role: 'status',
11
+ },
12
+ empty: {
13
+ tone: 'neutral',
14
+ title: 'Nothing recorded yet',
15
+ description: 'This is a real answer, not a failure — no records exist for the current selection.',
16
+ icon: 'empty',
17
+ live: 'polite',
18
+ role: 'status',
19
+ },
20
+ error: {
21
+ tone: 'destructive',
22
+ title: 'Could not load this',
23
+ description: 'The request failed, so nothing is shown. This is not an empty result — the real value is unknown.',
24
+ icon: 'alert',
25
+ live: 'assertive',
26
+ role: 'alert',
27
+ },
28
+ forbidden: {
29
+ tone: 'warning',
30
+ title: 'You do not have access to this',
31
+ description: 'The request was refused. Data may exist that this account is not permitted to read, so nothing is shown.',
32
+ icon: 'lock',
33
+ live: 'assertive',
34
+ role: 'alert',
35
+ },
36
+ unavailable: {
37
+ tone: 'warning',
38
+ title: 'Not readable from here',
39
+ description: 'No queryable source for this information is reachable from this surface, so nothing is shown.',
40
+ icon: 'unplugged',
41
+ live: 'polite',
42
+ role: 'status',
43
+ },
44
+ };
45
+ // Role-bound only. `success` is deliberately absent: an honest state is never green.
46
+ const TONE_SURFACE = {
47
+ neutral: 'border-border bg-muted/30 text-muted-foreground',
48
+ info: 'border-primary/30 bg-primary/5 text-primary',
49
+ warning: 'border-status-warning/40 bg-status-warning/5 text-status-warning',
50
+ destructive: 'border-destructive/40 bg-destructive/5 text-destructive',
51
+ };
52
+ // ---------------------------------------------------------------------------
53
+ // Icons — inline SVG, `currentColor` only, so they inherit the tone token.
54
+ // (The package has no icon dependency; `loading-spinner.tsx` sets this
55
+ // precedent. Adding lucide-react here would push it onto every consumer.)
56
+ // ---------------------------------------------------------------------------
57
+ function StateIcon({ icon }) {
58
+ const common = {
59
+ viewBox: '0 0 24 24',
60
+ fill: 'none',
61
+ stroke: 'currentColor',
62
+ strokeWidth: 1.75,
63
+ strokeLinecap: 'round',
64
+ strokeLinejoin: 'round',
65
+ className: 'h-6 w-6',
66
+ 'aria-hidden': true,
67
+ };
68
+ switch (icon) {
69
+ case 'spinner':
70
+ return (_jsxs("svg", { ...common, className: cn(common.className, 'animate-spin'), children: [_jsx("circle", { cx: "12", cy: "12", r: "9", className: "opacity-25" }), _jsx("path", { d: "M21 12a9 9 0 0 0-9-9", className: "opacity-90" })] }));
71
+ case 'empty':
72
+ // An empty tray — "we looked, there was nothing".
73
+ return (_jsxs("svg", { ...common, children: [_jsx("path", { d: "M3 14h4l1.5 3h7L17 14h4" }), _jsx("path", { d: "M5 14 7 5h10l2 9v5H5z" })] }));
74
+ case 'alert':
75
+ return (_jsxs("svg", { ...common, children: [_jsx("path", { d: "M12 3.5 2.5 20h19L12 3.5z" }), _jsx("path", { d: "M12 10v4" }), _jsx("path", { d: "M12 17.5h.01" })] }));
76
+ case 'lock':
77
+ return (_jsxs("svg", { ...common, children: [_jsx("rect", { x: "4", y: "10", width: "16", height: "10", rx: "2" }), _jsx("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" }), _jsx("path", { d: "M12 14v2" })] }));
78
+ case 'unplugged':
79
+ return (_jsxs("svg", { ...common, children: [_jsx("path", { d: "M7 3v5" }), _jsx("path", { d: "M13 3v5" }), _jsx("path", { d: "M4 8h12v3a6 6 0 0 1-6 6v4" }), _jsx("path", { d: "m3 3 18 18" })] }));
80
+ }
81
+ }
82
+ /**
83
+ * Diagonal hatch. This is the load-bearing visual: hatched ground is not a
84
+ * texture any populated surface in the estate uses, so "this panel is not
85
+ * data" is legible before a single word is read. `currentColor` keeps it on
86
+ * the tone token in both themes.
87
+ */
88
+ function Hatch() {
89
+ return (_jsxs("svg", { "aria-hidden": true, className: "pointer-events-none absolute inset-0 h-full w-full opacity-[0.07]", preserveAspectRatio: "none", children: [_jsx("defs", { children: _jsx("pattern", { id: "bs-du-hatch", width: "8", height: "8", patternUnits: "userSpaceOnUse", patternTransform: "rotate(45)", children: _jsx("line", { x1: "0", y1: "0", x2: "0", y2: "8", stroke: "currentColor", strokeWidth: "2" }) }) }), _jsx("rect", { width: "100%", height: "100%", fill: "url(#bs-du-hatch)" })] }));
90
+ }
91
+ export function DataUnavailable({ state, title, description, reason, onRetry, retryLabel = 'Try again', action, size = 'block', className, ...props }) {
92
+ const preset = PRESETS[state];
93
+ const showRetry = Boolean(onRetry) && state !== 'forbidden';
94
+ return (_jsxs("div", { "data-slot": "data-unavailable", "data-state": state, role: preset.role, "aria-live": preset.live, "aria-busy": state === 'loading' || undefined, className: cn(
95
+ // Dashed + hatched: deliberately unlike every populated surface.
96
+ 'relative isolate flex flex-col items-center justify-center overflow-hidden', 'rounded-lg border border-dashed text-center', size === 'compact' ? 'min-h-32 gap-2 p-4' : 'min-h-48 gap-3 p-8', TONE_SURFACE[preset.tone], className), ...props, children: [_jsx(Hatch, {}), _jsxs("div", { className: "relative flex flex-col items-center gap-3", children: [_jsx(StateIcon, { icon: preset.icon }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-base font-semibold text-foreground", children: title ?? preset.title }), _jsx("p", { className: "mx-auto max-w-prose text-sm text-muted-foreground", children: description ?? preset.description })] }), reason ? (_jsxs("div", { className: "mx-auto w-full max-w-prose", children: [_jsx("p", { className: "mb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Reason" }), _jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-words rounded-md border border-border bg-card/80 px-3 py-2 text-left font-mono text-xs text-muted-foreground", children: reason })] })) : null, showRetry || action ? (_jsxs("div", { className: "mt-1 flex flex-wrap items-center justify-center gap-2", children: [showRetry ? (_jsx("button", { type: "button", onClick: onRetry, className: cn('inline-flex items-center rounded-md border border-border bg-card px-3 py-1.5', 'text-sm font-medium text-foreground', 'hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'), children: retryLabel })) : null, action] })) : null] })] }));
97
+ }
98
+ /**
99
+ * Postgres/PostgREST codes that mean "refused", not "broken".
100
+ *
101
+ * 42501 insufficient_privilege · PGRST301 JWT problem ·
102
+ * PGRST116 no rows returned where one was required under RLS.
103
+ */
104
+ const FORBIDDEN_MARKERS = ['42501', 'PGRST301', 'PGRST116', 'permission denied', 'row-level security'];
105
+ function looksForbidden(error) {
106
+ if (!error)
107
+ return false;
108
+ const parts = [];
109
+ if (typeof error === 'string')
110
+ parts.push(error);
111
+ else if (typeof error === 'object') {
112
+ const e = error;
113
+ if (typeof e.message === 'string')
114
+ parts.push(e.message);
115
+ if (typeof e.code === 'string')
116
+ parts.push(e.code);
117
+ if (e.status === 401 || e.status === 403)
118
+ return true;
119
+ }
120
+ const haystack = parts.join(' ').toLowerCase();
121
+ return FORBIDDEN_MARKERS.some((m) => haystack.includes(m.toLowerCase()));
122
+ }
123
+ /** Human-readable reason string for the `reason` prop. */
124
+ export function describeError(error) {
125
+ if (!error)
126
+ return 'Unknown error';
127
+ if (typeof error === 'string')
128
+ return error;
129
+ if (error instanceof Error)
130
+ return error.message;
131
+ if (typeof error === 'object') {
132
+ const e = error;
133
+ const code = typeof e.code === 'string' ? `[${e.code}] ` : '';
134
+ if (typeof e.message === 'string')
135
+ return `${code}${e.message}`;
136
+ }
137
+ try {
138
+ return JSON.stringify(error);
139
+ }
140
+ catch {
141
+ return String(error);
142
+ }
143
+ }
144
+ /**
145
+ * Map a query result to either `'ready'` (render the real data) or the honest
146
+ * state to render instead.
147
+ *
148
+ * The point is that there is no fourth answer. A call site cannot get a
149
+ * fallback out of this function, which is what makes the class fix stick.
150
+ *
151
+ * @param isEmpty how to decide the data is genuinely empty. Defaults to
152
+ * "an array with no elements". Pass explicitly for object-shaped payloads.
153
+ */
154
+ export function resolveDataState(query, isEmpty = (d) => Array.isArray(d) && d.length === 0) {
155
+ if (query.isError)
156
+ return looksForbidden(query.error) ? 'forbidden' : 'error';
157
+ if (query.isPending ?? query.isLoading ?? false)
158
+ return 'loading';
159
+ if (query.data === undefined || query.data === null)
160
+ return 'error';
161
+ return isEmpty(query.data) ? 'empty' : 'ready';
162
+ }
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { D2CDefaultLogo, type D2CDefaultLogoProps } from './default-logo.js';
7
7
  export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, } from './button.js';
8
8
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, type DialogContentProps, type DialogOverlayProps, } from './dialog.js';
9
9
  export { EmptyState, type EmptyStateProps } from './empty-state.js';
10
+ export { DataUnavailable, resolveDataState, describeError, type DataUnavailableProps, type DataUnavailableState, type QueryLike, } from './data-unavailable.js';
10
11
  export { ErrorBoundary, type ErrorBoundaryProps } from './error-boundary.js';
11
12
  export { LoadingSpinner, type LoadingSpinnerProps } from './loading-spinner.js';
12
13
  export { StatusBadge, type StatusBadgeProps, type StatusBadgeTone } from './status-badge.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA;AAC/B,OAAO,EACL,IAAI,EACJ,cAAc,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAG5F,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA;AAC/B,OAAO,EACL,IAAI,EACJ,cAAc,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,SAAS,GACf,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAG5F,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export { D2CDefaultLogo } from './default-logo.js';
7
7
  export { Button, } from './button.js';
8
8
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, } from './dialog.js';
9
9
  export { EmptyState } from './empty-state.js';
10
+ export { DataUnavailable, resolveDataState, describeError, } from './data-unavailable.js';
10
11
  export { ErrorBoundary } from './error-boundary.js';
11
12
  export { LoadingSpinner } from './loading-spinner.js';
12
13
  export { StatusBadge } from './status-badge.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/ui",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -51,6 +51,10 @@
51
51
  "import": "./dist/empty-state.js",
52
52
  "types": "./dist/empty-state.d.ts"
53
53
  },
54
+ "./data-unavailable": {
55
+ "import": "./dist/data-unavailable.js",
56
+ "types": "./dist/data-unavailable.d.ts"
57
+ },
54
58
  "./error-boundary": {
55
59
  "import": "./dist/error-boundary.js",
56
60
  "types": "./dist/error-boundary.d.ts"
@@ -89,8 +93,8 @@
89
93
  }
90
94
  },
91
95
  "devDependencies": {
92
- "@bsuite/theme": "^0.7.0",
93
- "@storybook/react-vite": "^10.4.3",
96
+ "@bsuite/theme": "^0.11.0",
97
+ "@storybook/react-vite": "^10.5.8",
94
98
  "@tailwindcss/vite": "^4.3.0",
95
99
  "@testing-library/jest-dom": "^6.9.1",
96
100
  "@testing-library/react": "^16.3.2",
@@ -101,7 +105,7 @@
101
105
  "jsdom": "^29.1.1",
102
106
  "react": "^19.2.7",
103
107
  "react-dom": "^19.2.7",
104
- "storybook": "^10.4.3",
108
+ "storybook": "^10.5.8",
105
109
  "tailwindcss": "^4.3.0",
106
110
  "typescript": "~6.0.3",
107
111
  "vite": "^8.0.16",