@bsuite/ui 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,38 @@ the Vercel build context. Consumers install from npm with a pinned semver.
8
8
 
9
9
  ## Components
10
10
 
11
+ ### Core primitives (v0.4.0+)
12
+
13
+ The package now owns the shared Tier 1 UI primitives apps should converge on:
14
+
15
+ ```tsx
16
+ import {
17
+ Button,
18
+ Dialog,
19
+ DialogContent,
20
+ DialogHeader,
21
+ DialogTitle,
22
+ EmptyState,
23
+ ErrorBoundary,
24
+ LoadingSpinner,
25
+ StatusBadge,
26
+ } from '@bsuite/ui'
27
+ ```
28
+
29
+ These primitives use BSuite/shadcn semantic token classes only (`bg-primary`,
30
+ `text-muted-foreground`, `border-border`, `text-status-success`, etc.) so D2C apps can
31
+ skin them through `@bsuite/theme` and Braden can map the same roles to the
32
+ corporate palette.
33
+
34
+ ### Storybook
35
+
36
+ Run the shared primitive gallery from this package:
37
+
38
+ ```bash
39
+ pnpm -C packages/ui storybook
40
+ pnpm -C packages/ui build-storybook
41
+ ```
42
+
11
43
  ### `DotPattern`
12
44
 
13
45
  Full-viewport dot-pattern background. SVG `<pattern>` tiling — ~1 DOM node
@@ -21,14 +53,14 @@ import { DotPattern } from '@bsuite/ui'
21
53
  width={28}
22
54
  height={28}
23
55
  cr={1.5}
24
- className="opacity-[0.35] [color:var(--accent-primary)]"
56
+ className="opacity-[0.35] text-muted-foreground"
25
57
  />
26
58
  {/* page content */}
27
59
  </div>
28
60
  ```
29
61
 
30
- Colour control via Tailwind `text-*` utilities or inline `[color:var(...)]`
31
- (dots use `fill="currentColor"`).
62
+ Colour control via semantic Tailwind `text-*` utilities (dots use
63
+ `fill="currentColor"`).
32
64
 
33
65
  ### `Logo`
34
66
 
@@ -151,7 +183,7 @@ The package provides subpath exports for better tree-shaking:
151
183
 
152
184
  ```tsx
153
185
  // Main exports
154
- import { Logo, BrandingCard } from '@bsuite/ui'
186
+ import { Logo, BrandingCard, Button, StatusBadge } from '@bsuite/ui'
155
187
 
156
188
  // Dot pattern only
157
189
  import { DotPattern } from '@bsuite/ui/dot-pattern'
@@ -162,6 +194,11 @@ import { cn } from '@bsuite/ui/utils'
162
194
  // Branding components only
163
195
  import { BrandingCard, ColorEditorSheet, OklchColorPicker } from '@bsuite/ui/branding'
164
196
 
197
+ // Primitive subpaths
198
+ import { Button } from '@bsuite/ui/button'
199
+ import { DialogContent } from '@bsuite/ui/dialog'
200
+ import { EmptyState } from '@bsuite/ui/empty-state'
201
+
165
202
  // SVG asset
166
203
  import logoSvg from '@bsuite/ui/assets/d2c-default-logo.svg'
167
204
  ```
@@ -0,0 +1,9 @@
1
+ import { type ButtonHTMLAttributes } from 'react';
2
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link';
3
+ export type ButtonSize = 'sm' | 'md' | 'lg' | 'icon';
4
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5
+ variant?: ButtonVariant;
6
+ size?: ButtonSize;
7
+ }
8
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
9
+ //# sourceMappingURL=button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../src/button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAG7D,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAA;AAClG,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAA;AAEpD,MAAM,WAAW,WAAY,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC1E,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAkBD,eAAO,MAAM,MAAM,2GAmBjB,CAAA"}
package/dist/button.js ADDED
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { cn } from './utils.js';
4
+ const variantClasses = {
5
+ primary: 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',
6
+ secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
7
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
8
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
9
+ destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
10
+ link: 'text-primary underline-offset-4 hover:underline',
11
+ };
12
+ const sizeClasses = {
13
+ sm: 'h-8 rounded-md px-3 text-xs',
14
+ md: 'h-10 rounded-md px-4 py-2 text-sm',
15
+ lg: 'h-11 rounded-md px-8 text-base',
16
+ icon: 'h-10 w-10 rounded-md',
17
+ };
18
+ export const Button = forwardRef(function Button({ className, variant = 'primary', size = 'md', type = 'button', ...props }, ref) {
19
+ return (_jsx("button", { ref: ref, type: type, className: cn('inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', 'disabled:pointer-events-none disabled:opacity-50', variantClasses[variant], sizeClasses[size], className), ...props }));
20
+ });
@@ -0,0 +1,15 @@
1
+ import { type ComponentPropsWithoutRef, type HTMLAttributes } from 'react';
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
3
+ export declare const Dialog: import("react").FC<DialogPrimitive.DialogProps>;
4
+ export declare const DialogTrigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
5
+ export declare const DialogClose: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & import("react").RefAttributes<HTMLButtonElement>>;
6
+ export declare const DialogPortal: import("react").FC<DialogPrimitive.DialogPortalProps>;
7
+ export type DialogOverlayProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>;
8
+ export type DialogContentProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Content>;
9
+ export declare const DialogOverlay: import("react").ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
10
+ export declare const DialogContent: import("react").ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
11
+ export declare function DialogHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
12
+ export declare function DialogFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
13
+ export declare const DialogTitle: import("react").ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTitleProps & import("react").RefAttributes<HTMLHeadingElement>, "ref"> & import("react").RefAttributes<HTMLHeadingElement>>;
14
+ export declare const DialogDescription: import("react").ForwardRefExoticComponent<Omit<DialogPrimitive.DialogDescriptionProps & import("react").RefAttributes<HTMLParagraphElement>, "ref"> & import("react").RefAttributes<HTMLParagraphElement>>;
15
+ //# sourceMappingURL=dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../src/dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,wBAAwB,EAAqB,KAAK,cAAc,EAAE,MAAM,OAAO,CAAA;AACzG,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAGzD,eAAO,MAAM,MAAM,iDAAuB,CAAA;AAC1C,eAAO,MAAM,aAAa,kIAA0B,CAAA;AACpD,eAAO,MAAM,WAAW,gIAAwB,CAAA;AAChD,eAAO,MAAM,YAAY,uDAAyB,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,CAAA;AACzF,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,CAAA;AAEzF,eAAO,MAAM,aAAa,4LAexB,CAAA;AAEF,eAAO,MAAM,aAAa,4LA8BxB,CAAA;AAEF,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,cAAc,CAAC,2CAEnF;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,cAAc,CAAC,2CAOnF;AAED,eAAO,MAAM,WAAW,kMAWtB,CAAA;AAEF,eAAO,MAAM,iBAAiB,4MAW5B,CAAA"}
package/dist/dialog.js ADDED
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
4
+ import { cn } from './utils.js';
5
+ export const Dialog = DialogPrimitive.Root;
6
+ export const DialogTrigger = DialogPrimitive.Trigger;
7
+ export const DialogClose = DialogPrimitive.Close;
8
+ export const DialogPortal = DialogPrimitive.Portal;
9
+ export const DialogOverlay = forwardRef(function DialogOverlay({ className, ...props }, ref) {
10
+ return (_jsx(DialogPrimitive.Overlay, { ref: ref, className: cn('fixed inset-0 z-50 bg-background/80 backdrop-blur-sm', 'data-[state=closed]:animate-out data-[state=open]:animate-in', className), ...props }));
11
+ });
12
+ export const DialogContent = forwardRef(function DialogContent({ children, className, ...props }, ref) {
13
+ return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn('fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4', 'rounded-lg border border-border bg-card p-6 text-card-foreground shadow-lg', 'duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in', className), ...props, children: [children, _jsx(DialogPrimitive.Close, { "aria-label": "Close dialog", className: cn('absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'), children: _jsx("span", { "aria-hidden": "true", children: "x" }) })] })] }));
14
+ });
15
+ export function DialogHeader({ className, ...props }) {
16
+ return _jsx("div", { className: cn('flex flex-col space-y-1.5 text-center sm:text-left', className), ...props });
17
+ }
18
+ export function DialogFooter({ className, ...props }) {
19
+ return (_jsx("div", { className: cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className), ...props }));
20
+ }
21
+ export const DialogTitle = forwardRef(function DialogTitle({ className, ...props }, ref) {
22
+ return (_jsx(DialogPrimitive.Title, { ref: ref, className: cn('text-lg font-semibold leading-none tracking-tight', className), ...props }));
23
+ });
24
+ export const DialogDescription = forwardRef(function DialogDescription({ className, ...props }, ref) {
25
+ return (_jsx(DialogPrimitive.Description, { ref: ref, className: cn('text-sm text-muted-foreground', className), ...props }));
26
+ });
@@ -14,7 +14,7 @@ import React from 'react';
14
14
  * @keyframes rule in its CSS (kept out of this package so
15
15
  * apps can theme the glow to their brand).
16
16
  * @param className Additional classes merged via `tailwind-merge` so
17
- * consumers can override the default `text-neutral-400/80`
17
+ * consumers can override the default `text-muted-foreground/80`
18
18
  * colour (dots use `fill="currentColor"` so overriding
19
19
  * `text-*` is the supported way to set colour).
20
20
  */
@@ -18,5 +18,5 @@ import { cn } from './utils.js';
18
18
  */
19
19
  export function DotPattern({ width = 16, height = 16, x = 0, y = 0, cx = 1, cy = 1, cr = 1, className, glow = false, ...props }) {
20
20
  const id = useId();
21
- return (_jsxs("svg", { "aria-hidden": "true", className: cn('pointer-events-none absolute inset-0 h-full w-full text-neutral-400/80', glow && 'animate-dot-glow', className), ...props, children: [_jsx("defs", { children: _jsx("pattern", { id: `${id}-pattern`, x: x, y: y, width: width, height: height, patternUnits: "userSpaceOnUse", children: _jsx("circle", { cx: cx, cy: cy, r: cr, fill: "currentColor" }) }) }), _jsx("rect", { width: "100%", height: "100%", fill: `url(#${id}-pattern)` })] }));
21
+ return (_jsxs("svg", { "aria-hidden": "true", className: cn('pointer-events-none absolute inset-0 h-full w-full text-muted-foreground/80', glow && 'animate-dot-glow', className), ...props, children: [_jsx("defs", { children: _jsx("pattern", { id: `${id}-pattern`, x: x, y: y, width: width, height: height, patternUnits: "userSpaceOnUse", children: _jsx("circle", { cx: cx, cy: cy, r: cr, fill: "currentColor" }) }) }), _jsx("rect", { width: "100%", height: "100%", fill: `url(#${id}-pattern)` })] }));
22
22
  }
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ export interface EmptyStateProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
3
+ icon?: ReactNode;
4
+ title: ReactNode;
5
+ description?: ReactNode;
6
+ action?: ReactNode;
7
+ }
8
+ export declare function EmptyState({ icon, title, description, action, className, ...props }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=empty-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../src/empty-state.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACpF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,SAAS,CAAA;IAChB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,2CAejB"}
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from './utils.js';
3
+ export function EmptyState({ icon, title, description, action, className, ...props }) {
4
+ return (_jsxs("div", { className: cn('flex min-h-48 flex-col items-center justify-center rounded-lg border border-dashed border-border bg-card/60 p-8 text-center', className), ...props, children: [icon ? _jsx("div", { className: "mb-4 text-muted-foreground", children: icon }) : null, _jsx("h3", { className: "text-base font-semibold text-foreground", children: title }), description ? _jsx("p", { className: "mt-2 max-w-md text-sm text-muted-foreground", children: description }) : null, action ? _jsx("div", { className: "mt-6", children: action }) : null] }));
5
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type ErrorBoundaryPropsWithRender } from 'react-error-boundary';
3
+ export interface ErrorBoundaryProps {
4
+ children: ReactNode;
5
+ fallback?: ReactNode;
6
+ fallbackRender?: ErrorBoundaryPropsWithRender['fallbackRender'];
7
+ onError?: ErrorBoundaryPropsWithRender['onError'];
8
+ onReset?: ErrorBoundaryPropsWithRender['onReset'];
9
+ resetKeys?: ErrorBoundaryPropsWithRender['resetKeys'];
10
+ title?: ReactNode;
11
+ description?: ReactNode;
12
+ showErrorMessage?: boolean;
13
+ }
14
+ export declare function ErrorBoundary({ children, fallback, fallbackRender, onError, onReset, resetKeys, title, description, showErrorMessage, }: ErrorBoundaryProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=error-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../src/error-boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAEL,KAAK,4BAA4B,EAElC,MAAM,sBAAsB,CAAA;AAI7B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,cAAc,CAAC,EAAE,4BAA4B,CAAC,gBAAgB,CAAC,CAAA;IAC/D,OAAO,CAAC,EAAE,4BAA4B,CAAC,SAAS,CAAC,CAAA;IACjD,OAAO,CAAC,EAAE,4BAA4B,CAAC,SAAS,CAAC,CAAA;IACjD,SAAS,CAAC,EAAE,4BAA4B,CAAC,WAAW,CAAC,CAAA;IACrD,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AA0BD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,OAAO,EACP,OAAO,EACP,SAAS,EACT,KAAK,EACL,WAAW,EACX,gBAAgB,GACjB,EAAE,kBAAkB,2CA2CpB"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ErrorBoundary as ReactErrorBoundary, } from 'react-error-boundary';
3
+ import { Button } from './button.js';
4
+ import { EmptyState } from './empty-state.js';
5
+ function DefaultFallback({ error, resetErrorBoundary, title, description, showErrorMessage, }) {
6
+ const safeDescription = 'The view could not be rendered. Please try again.';
7
+ const debugDescription = error instanceof Error ? error.message : safeDescription;
8
+ return (_jsx(EmptyState, { role: "alert", title: title ?? 'Something went wrong', description: description ?? (showErrorMessage ? debugDescription : safeDescription), action: _jsx(Button, { variant: "outline", onClick: resetErrorBoundary, children: "Try again" }) }));
9
+ }
10
+ export function ErrorBoundary({ children, fallback, fallbackRender, onError, onReset, resetKeys, title, description, showErrorMessage, }) {
11
+ if (fallback !== undefined) {
12
+ return (_jsx(ReactErrorBoundary, { fallback: fallback, onError: onError, onReset: onReset, resetKeys: resetKeys, children: children }));
13
+ }
14
+ if (fallbackRender) {
15
+ return (_jsx(ReactErrorBoundary, { fallbackRender: fallbackRender, onError: onError, onReset: onReset, resetKeys: resetKeys, children: children }));
16
+ }
17
+ return (_jsx(ReactErrorBoundary, { fallbackRender: (props) => (_jsx(DefaultFallback, { ...props, title: title, description: description, showErrorMessage: showErrorMessage })), onError: onError, onReset: onReset, resetKeys: resetKeys, children: children }));
18
+ }
package/dist/index.d.ts CHANGED
@@ -2,5 +2,11 @@ export { DotPattern, type DotPatternProps } from './dot-pattern.js';
2
2
  export { cn } from './utils.js';
3
3
  export { Logo, resolveLogoUrl, type LogoProps, type LogoSlot, type LogoColorScheme, type LogoBranding, type ResolvedLogo, type AppSlug, } from './Logo.js';
4
4
  export { D2CDefaultLogo, type D2CDefaultLogoProps } from './default-logo.js';
5
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, } from './button.js';
6
+ export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, type DialogContentProps, type DialogOverlayProps, } from './dialog.js';
7
+ export { EmptyState, type EmptyStateProps } from './empty-state.js';
8
+ export { ErrorBoundary, type ErrorBoundaryProps } from './error-boundary.js';
9
+ export { LoadingSpinner, type LoadingSpinnerProps } from './loading-spinner.js';
10
+ export { StatusBadge, type StatusBadgeProps, type StatusBadgeTone } from './status-badge.js';
5
11
  export { BrandingCard, ColorEditorSheet, OklchColorPicker, type BrandingCardProps, type ColorEditorSheetProps, type OklchColorPickerProps, } from './branding/index.js';
6
12
  //# sourceMappingURL=index.d.ts.map
@@ -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,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;AAG5E,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,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,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"}
package/dist/index.js CHANGED
@@ -2,5 +2,11 @@ export { DotPattern } from './dot-pattern.js';
2
2
  export { cn } from './utils.js';
3
3
  export { Logo, resolveLogoUrl, } from './Logo.js';
4
4
  export { D2CDefaultLogo } from './default-logo.js';
5
+ export { Button, } from './button.js';
6
+ export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, } from './dialog.js';
7
+ export { EmptyState } from './empty-state.js';
8
+ export { ErrorBoundary } from './error-boundary.js';
9
+ export { LoadingSpinner } from './loading-spinner.js';
10
+ export { StatusBadge } from './status-badge.js';
5
11
  // Branding components (v0.3.0+)
6
12
  export { BrandingCard, ColorEditorSheet, OklchColorPicker, } from './branding/index.js';
@@ -0,0 +1,7 @@
1
+ import type { SVGAttributes } from 'react';
2
+ export interface LoadingSpinnerProps extends SVGAttributes<SVGSVGElement> {
3
+ label?: string;
4
+ size?: 'sm' | 'md' | 'lg';
5
+ }
6
+ export declare function LoadingSpinner({ className, label, size, ...props }: LoadingSpinnerProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=loading-spinner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loading-spinner.d.ts","sourceRoot":"","sources":["../src/loading-spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAG1C,MAAM,WAAW,mBAAoB,SAAQ,aAAa,CAAC,aAAa,CAAC;IACvE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;CAC1B;AAQD,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,KAAiB,EACjB,IAAW,EACX,GAAG,KAAK,EACT,EAAE,mBAAmB,2CAkBrB"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from './utils.js';
3
+ const sizeClasses = {
4
+ sm: 'h-4 w-4',
5
+ md: 'h-6 w-6',
6
+ lg: 'h-8 w-8',
7
+ };
8
+ export function LoadingSpinner({ className, label = 'Loading', size = 'md', ...props }) {
9
+ return (_jsxs("svg", { role: "status", "aria-label": label, className: cn('animate-spin text-primary', sizeClasses[size], className), viewBox: "0 0 24 24", fill: "none", ...props, children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z" })] }));
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ export type StatusBadgeTone = 'neutral' | 'info' | 'success' | 'warning' | 'destructive';
3
+ export interface StatusBadgeProps extends HTMLAttributes<HTMLSpanElement> {
4
+ tone?: StatusBadgeTone;
5
+ }
6
+ export declare function StatusBadge({ className, tone, ...props }: StatusBadgeProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=status-badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-badge.d.ts","sourceRoot":"","sources":["../src/status-badge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAG3C,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAA;AAExF,MAAM,WAAW,gBAAiB,SAAQ,cAAc,CAAC,eAAe,CAAC;IACvE,IAAI,CAAC,EAAE,eAAe,CAAA;CACvB;AAUD,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,IAAgB,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,2CAWtF"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from './utils.js';
3
+ const toneClasses = {
4
+ neutral: 'border-border bg-muted text-muted-foreground',
5
+ info: 'border-primary/20 bg-primary/10 text-primary',
6
+ success: 'border-status-success/20 bg-status-success/10 text-status-success',
7
+ warning: 'border-status-warning/20 bg-status-warning/10 text-status-warning',
8
+ destructive: 'border-destructive/20 bg-destructive/10 text-destructive',
9
+ };
10
+ export function StatusBadge({ className, tone = 'neutral', ...props }) {
11
+ return (_jsx("span", { className: cn('inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium', toneClasses[tone], className), ...props }));
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -35,15 +35,43 @@
35
35
  "import": "./dist/branding/index.js",
36
36
  "types": "./dist/branding/index.d.ts"
37
37
  },
38
+ "./button": {
39
+ "import": "./dist/button.js",
40
+ "types": "./dist/button.d.ts"
41
+ },
42
+ "./dialog": {
43
+ "import": "./dist/dialog.js",
44
+ "types": "./dist/dialog.d.ts"
45
+ },
46
+ "./empty-state": {
47
+ "import": "./dist/empty-state.js",
48
+ "types": "./dist/empty-state.d.ts"
49
+ },
50
+ "./error-boundary": {
51
+ "import": "./dist/error-boundary.js",
52
+ "types": "./dist/error-boundary.d.ts"
53
+ },
54
+ "./loading-spinner": {
55
+ "import": "./dist/loading-spinner.js",
56
+ "types": "./dist/loading-spinner.d.ts"
57
+ },
58
+ "./status-badge": {
59
+ "import": "./dist/status-badge.js",
60
+ "types": "./dist/status-badge.d.ts"
61
+ },
38
62
  "./assets/d2c-default-logo.svg": "./src/assets/d2c-default-logo.svg"
39
63
  },
40
64
  "scripts": {
41
65
  "build": "tsc -p tsconfig.build.json",
66
+ "storybook": "storybook dev -p 6006",
67
+ "build-storybook": "storybook build",
42
68
  "typecheck": "tsc --noEmit",
43
69
  "test": "vitest run"
44
70
  },
45
71
  "dependencies": {
72
+ "@radix-ui/react-dialog": "^1.1.15",
46
73
  "clsx": "^2.1.1",
74
+ "react-error-boundary": "^6.1.2",
47
75
  "tailwind-merge": "^3.5.0"
48
76
  },
49
77
  "peerDependencies": {
@@ -51,14 +79,20 @@
51
79
  "react-dom": "^18.3.0 || ^19.0.0"
52
80
  },
53
81
  "devDependencies": {
82
+ "@bsuite/theme": "^0.4.1",
83
+ "@storybook/react-vite": "^10.4.2",
84
+ "@tailwindcss/vite": "^4.3.0",
54
85
  "@testing-library/jest-dom": "^6.9.1",
55
86
  "@testing-library/react": "^16.3.2",
87
+ "@testing-library/user-event": "^14.6.1",
56
88
  "@types/react": "^19.2.14",
57
89
  "@types/react-dom": "^19.2.3",
58
90
  "@vitejs/plugin-react": "^6.0.1",
59
91
  "jsdom": "^29.1.1",
60
92
  "react": "^19.2.5",
61
93
  "react-dom": "^19.2.5",
94
+ "storybook": "^10.4.2",
95
+ "tailwindcss": "^4.3.0",
62
96
  "typescript": "~5.9.3",
63
97
  "vite": "^8.0.10",
64
98
  "vitest": "^4.1.5"