@bsuite/ui 0.2.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 +168 -3
- package/dist/branding/BrandingCard.d.ts +56 -0
- package/dist/branding/BrandingCard.d.ts.map +1 -0
- package/dist/branding/BrandingCard.js +73 -0
- package/dist/branding/ColorEditorSheet.d.ts +68 -0
- package/dist/branding/ColorEditorSheet.d.ts.map +1 -0
- package/dist/branding/ColorEditorSheet.js +73 -0
- package/dist/branding/OklchColorPicker.d.ts +18 -0
- package/dist/branding/OklchColorPicker.d.ts.map +1 -0
- package/dist/branding/OklchColorPicker.js +83 -0
- package/dist/branding/index.d.ts +7 -0
- package/dist/branding/index.d.ts.map +1 -0
- package/dist/branding/index.js +6 -0
- package/dist/button.d.ts +9 -0
- package/dist/button.d.ts.map +1 -0
- package/dist/button.js +20 -0
- package/dist/dialog.d.ts +15 -0
- package/dist/dialog.d.ts.map +1 -0
- package/dist/dialog.js +26 -0
- package/dist/dot-pattern.d.ts +1 -1
- package/dist/dot-pattern.js +1 -1
- package/dist/empty-state.d.ts +9 -0
- package/dist/empty-state.d.ts.map +1 -0
- package/dist/empty-state.js +5 -0
- package/dist/error-boundary.d.ts +15 -0
- package/dist/error-boundary.d.ts.map +1 -0
- package/dist/error-boundary.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/loading-spinner.d.ts +7 -0
- package/dist/loading-spinner.d.ts.map +1 -0
- package/dist/loading-spinner.js +10 -0
- package/dist/status-badge.d.ts +7 -0
- package/dist/status-badge.d.ts.map +1 -0
- package/dist/status-badge.js +12 -0
- package/package.json +39 -1
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,121 @@ import { DotPattern } from '@bsuite/ui'
|
|
|
21
53
|
width={28}
|
|
22
54
|
height={28}
|
|
23
55
|
cr={1.5}
|
|
24
|
-
className="opacity-[0.35]
|
|
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
|
|
31
|
-
|
|
62
|
+
Colour control via semantic Tailwind `text-*` utilities (dots use
|
|
63
|
+
`fill="currentColor"`).
|
|
64
|
+
|
|
65
|
+
### `Logo`
|
|
66
|
+
|
|
67
|
+
Slot-aware, theme-aware, branding-aware logo component for all BSuite apps.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { Logo } from '@bsuite/ui'
|
|
71
|
+
|
|
72
|
+
<Logo slot="header" colorScheme="auto" />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `D2CDefaultLogo`
|
|
76
|
+
|
|
77
|
+
Inline D2C Neon Electric SVG logo (no asset dependency).
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { D2CDefaultLogo } from '@bsuite/ui'
|
|
81
|
+
|
|
82
|
+
<D2CDefaultLogo className="h-8 w-auto" />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Branding Components (v0.3.0+)
|
|
86
|
+
|
|
87
|
+
### `OklchColorPicker`
|
|
88
|
+
|
|
89
|
+
A color input component for OKLCH color format (BSuite standard).
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { OklchColorPicker } from '@bsuite/ui'
|
|
93
|
+
|
|
94
|
+
<OklchColorPicker
|
|
95
|
+
value="oklch(0.546 0.215 262.9)"
|
|
96
|
+
onChange={(value) => console.log(value)}
|
|
97
|
+
label="Primary Color"
|
|
98
|
+
description="Your brand's primary color in OKLCH format"
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Features:**
|
|
103
|
+
- Live color preview
|
|
104
|
+
- Input fields for L (lightness), C (chroma), H (hue)
|
|
105
|
+
- Slider controls for easy adjustment
|
|
106
|
+
- Validation of OKLCH format
|
|
107
|
+
- Accessible labels and error messages
|
|
108
|
+
|
|
109
|
+
### `ColorEditorSheet`
|
|
110
|
+
|
|
111
|
+
A sheet/dialog for editing branding colors with a slot-based color editor.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { ColorEditorSheet, OklchColorPicker } from '@bsuite/ui'
|
|
115
|
+
|
|
116
|
+
<ColorEditorSheet
|
|
117
|
+
open={isOpen}
|
|
118
|
+
onOpenChange={setIsOpen}
|
|
119
|
+
title="Edit Primary Color"
|
|
120
|
+
description="Adjust the OKLCH color values"
|
|
121
|
+
value="oklch(0.546 0.215 262.9)"
|
|
122
|
+
onSave={(value) => updateBranding('primary', value)}
|
|
123
|
+
renderEditor={(props) => <OklchColorPicker {...props} />}
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Features:**
|
|
128
|
+
- Slide-out sheet interface
|
|
129
|
+
- Slot-based color editor (inject your own picker)
|
|
130
|
+
- Save/Cancel actions
|
|
131
|
+
- Responsive design
|
|
132
|
+
- Accessible dialog implementation
|
|
133
|
+
|
|
134
|
+
### `BrandingCard`
|
|
135
|
+
|
|
136
|
+
A card component for displaying and editing tenant branding configuration.
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { BrandingCard, OklchColorPicker } from '@bsuite/ui'
|
|
140
|
+
|
|
141
|
+
<BrandingCard
|
|
142
|
+
branding={{
|
|
143
|
+
primary: "oklch(0.546 0.215 262.9)",
|
|
144
|
+
accent: "oklch(0.769 0.132 191.7)",
|
|
145
|
+
logo_url: "https://example.com/logo.svg",
|
|
146
|
+
company_name: "Acme Corp"
|
|
147
|
+
}}
|
|
148
|
+
onUpdate={async (key, value) => {
|
|
149
|
+
await updateTenantBranding(key, value)
|
|
150
|
+
}}
|
|
151
|
+
renderColorEditor={(props) => <OklchColorPicker {...props} />}
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Features:**
|
|
156
|
+
- Display current branding values (colors, logos, company name)
|
|
157
|
+
- Edit buttons for each branding property
|
|
158
|
+
- Integrated color editing with ColorEditorSheet
|
|
159
|
+
- Image preview for logos
|
|
160
|
+
- Accessible and responsive design
|
|
161
|
+
- Support for light/dark mode logo variants
|
|
162
|
+
|
|
163
|
+
**Branding Properties:**
|
|
164
|
+
- `primary` - Primary action color (OKLCH)
|
|
165
|
+
- `accent` - Accent/secondary color (OKLCH)
|
|
166
|
+
- `logo_url` - Main logo URL
|
|
167
|
+
- `logo_light_url` - Light mode logo variant (optional)
|
|
168
|
+
- `logo_dark_url` - Dark mode logo variant (optional)
|
|
169
|
+
- `favicon_url` - Favicon URL (16x16 or 32x32)
|
|
170
|
+
- `company_name` - Company name for alt text
|
|
32
171
|
|
|
33
172
|
## Utils
|
|
34
173
|
|
|
@@ -38,6 +177,32 @@ shadcn-style className merger — `clsx` + `tailwind-merge`. Available for
|
|
|
38
177
|
consumers that want to compose `@bsuite/ui` components with local overrides
|
|
39
178
|
without pulling shadcn again.
|
|
40
179
|
|
|
180
|
+
## Module Exports
|
|
181
|
+
|
|
182
|
+
The package provides subpath exports for better tree-shaking:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// Main exports
|
|
186
|
+
import { Logo, BrandingCard, Button, StatusBadge } from '@bsuite/ui'
|
|
187
|
+
|
|
188
|
+
// Dot pattern only
|
|
189
|
+
import { DotPattern } from '@bsuite/ui/dot-pattern'
|
|
190
|
+
|
|
191
|
+
// Utils only
|
|
192
|
+
import { cn } from '@bsuite/ui/utils'
|
|
193
|
+
|
|
194
|
+
// Branding components only
|
|
195
|
+
import { BrandingCard, ColorEditorSheet, OklchColorPicker } from '@bsuite/ui/branding'
|
|
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
|
+
|
|
202
|
+
// SVG asset
|
|
203
|
+
import logoSvg from '@bsuite/ui/assets/d2c-default-logo.svg'
|
|
204
|
+
```
|
|
205
|
+
|
|
41
206
|
## Releases
|
|
42
207
|
|
|
43
208
|
1. Edit source under `src/`.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrandingCard — A card component for displaying and editing branding settings
|
|
3
|
+
*
|
|
4
|
+
* This component provides a UI for viewing and editing tenant branding
|
|
5
|
+
* configuration, including colors, logos, and other brand properties.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Display current branding values
|
|
9
|
+
* - Edit buttons for each branding property
|
|
10
|
+
* - Integration with ColorEditorSheet for color editing
|
|
11
|
+
* - Accessible and responsive design
|
|
12
|
+
* - Supports OKLCH color format
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* <BrandingCard
|
|
16
|
+
* branding={{
|
|
17
|
+
* primary: "oklch(0.546 0.215 262.9)",
|
|
18
|
+
* accent: "oklch(0.769 0.132 191.7)",
|
|
19
|
+
* logo_url: "https://example.com/logo.svg"
|
|
20
|
+
* }}
|
|
21
|
+
* onUpdate={(key, value) => console.log('Updated:', key, value)}
|
|
22
|
+
* renderColorEditor={(props) => <OklchColorPicker {...props} />}
|
|
23
|
+
* />
|
|
24
|
+
*/
|
|
25
|
+
import { type ReactNode } from 'react';
|
|
26
|
+
export interface BrandingCardProps {
|
|
27
|
+
/** Current branding configuration */
|
|
28
|
+
branding: {
|
|
29
|
+
primary?: string;
|
|
30
|
+
accent?: string;
|
|
31
|
+
logo_url?: string;
|
|
32
|
+
logo_light_url?: string;
|
|
33
|
+
logo_dark_url?: string;
|
|
34
|
+
favicon_url?: string;
|
|
35
|
+
company_name?: string;
|
|
36
|
+
font_stack?: string | null;
|
|
37
|
+
};
|
|
38
|
+
/** Callback when a branding property is updated */
|
|
39
|
+
onUpdate: (key: string, value: string) => void | Promise<void>;
|
|
40
|
+
/** Render function for the color editor component */
|
|
41
|
+
renderColorEditor: (props: {
|
|
42
|
+
value: string;
|
|
43
|
+
onChange: (value: string) => void;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
}) => ReactNode;
|
|
46
|
+
/** Optional className for the card */
|
|
47
|
+
className?: string;
|
|
48
|
+
/** Whether editing is disabled */
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
/** Optional title for the card */
|
|
51
|
+
title?: string;
|
|
52
|
+
/** Optional description for the card */
|
|
53
|
+
description?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function BrandingCard({ branding, onUpdate, renderColorEditor, className, disabled, title, description, }: BrandingCardProps): import("react/jsx-runtime").JSX.Element;
|
|
56
|
+
//# sourceMappingURL=BrandingCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrandingCard.d.ts","sourceRoot":"","sources":["../../src/branding/BrandingCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGhD,MAAM,WAAW,iBAAiB;IAChC,qCAAqC;IACrC,QAAQ,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,CAAA;IACD,mDAAmD;IACnD,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9D,qDAAqD;IACrD,iBAAiB,EAAE,CAAC,KAAK,EAAE;QACzB,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;QACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,KAAK,SAAS,CAAA;IACf,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,QAAgB,EAChB,KAAwB,EACxB,WAAW,GACZ,EAAE,iBAAiB,2CA8QnB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BrandingCard — A card component for displaying and editing branding settings
|
|
4
|
+
*
|
|
5
|
+
* This component provides a UI for viewing and editing tenant branding
|
|
6
|
+
* configuration, including colors, logos, and other brand properties.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Display current branding values
|
|
10
|
+
* - Edit buttons for each branding property
|
|
11
|
+
* - Integration with ColorEditorSheet for color editing
|
|
12
|
+
* - Accessible and responsive design
|
|
13
|
+
* - Supports OKLCH color format
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* <BrandingCard
|
|
17
|
+
* branding={{
|
|
18
|
+
* primary: "oklch(0.546 0.215 262.9)",
|
|
19
|
+
* accent: "oklch(0.769 0.132 191.7)",
|
|
20
|
+
* logo_url: "https://example.com/logo.svg"
|
|
21
|
+
* }}
|
|
22
|
+
* onUpdate={(key, value) => console.log('Updated:', key, value)}
|
|
23
|
+
* renderColorEditor={(props) => <OklchColorPicker {...props} />}
|
|
24
|
+
* />
|
|
25
|
+
*/
|
|
26
|
+
import { useState } from 'react';
|
|
27
|
+
import { cn } from '../utils.js';
|
|
28
|
+
export function BrandingCard({ branding, onUpdate, renderColorEditor, className, disabled = false, title = 'Brand Settings', description, }) {
|
|
29
|
+
const [colorEditor, setColorEditor] = useState({
|
|
30
|
+
open: false,
|
|
31
|
+
field: '',
|
|
32
|
+
label: '',
|
|
33
|
+
value: '',
|
|
34
|
+
});
|
|
35
|
+
const openColorEditor = (field, label, currentValue) => {
|
|
36
|
+
setColorEditor({
|
|
37
|
+
open: true,
|
|
38
|
+
field,
|
|
39
|
+
label,
|
|
40
|
+
value: currentValue || '',
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const closeColorEditor = () => {
|
|
44
|
+
setColorEditor(prev => ({ ...prev, open: false }));
|
|
45
|
+
};
|
|
46
|
+
const handleColorSave = async (value) => {
|
|
47
|
+
await onUpdate(colorEditor.field, value);
|
|
48
|
+
closeColorEditor();
|
|
49
|
+
};
|
|
50
|
+
const handleTextUpdate = async (field, value) => {
|
|
51
|
+
await onUpdate(field, value);
|
|
52
|
+
};
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn('rounded-lg border border-input bg-card text-card-foreground shadow-sm', className), children: [_jsxs("div", { className: "border-b border-border px-6 py-4", children: [_jsx("h3", { className: "text-lg font-semibold", children: title }), description && (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description }))] }), _jsxs("div", { className: "divide-y divide-border", children: [_jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "h-10 w-10 rounded border border-input shadow-sm", style: { backgroundColor: branding.primary || 'oklch(0.546 0.215 262.9)' }, "aria-label": "Primary color preview" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Primary Color" }), _jsx("div", { className: "text-sm text-muted-foreground", children: branding.primary || 'Not set' })] })] }), _jsx("button", { type: "button", onClick: () => openColorEditor('primary', 'Primary Color', branding.primary || 'oklch(0.546 0.215 262.9)'), disabled: disabled, className: cn('rounded-md border border-input bg-background px-4 py-2 text-sm font-medium', 'hover:bg-accent hover:text-accent-foreground', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:cursor-not-allowed disabled:opacity-50'), children: "Edit" })] }), _jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "h-10 w-10 rounded border border-input shadow-sm", style: { backgroundColor: branding.accent || 'oklch(0.769 0.132 191.7)' }, "aria-label": "Accent color preview" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Accent Color" }), _jsx("div", { className: "text-sm text-muted-foreground", children: branding.accent || 'Not set' })] })] }), _jsx("button", { type: "button", onClick: () => openColorEditor('accent', 'Accent Color', branding.accent || 'oklch(0.769 0.132 191.7)'), disabled: disabled, className: cn('rounded-md border border-input bg-background px-4 py-2 text-sm font-medium', 'hover:bg-accent hover:text-accent-foreground', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:cursor-not-allowed disabled:opacity-50'), children: "Edit" })] }), _jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [_jsx("label", { htmlFor: "company-name", className: "font-medium", children: "Company Name" }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("input", { id: "company-name", type: "text", value: branding.company_name || '', onChange: (e) => handleTextUpdate('company_name', e.target.value), disabled: disabled, placeholder: "Enter company name", className: cn('flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm', 'focus:outline-none focus:ring-2 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50') }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Used in logo alt text and other branding contexts" })] }), _jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [_jsx("label", { htmlFor: "logo-url", className: "font-medium", children: "Logo URL" }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("input", { id: "logo-url", type: "url", value: branding.logo_url || '', onChange: (e) => handleTextUpdate('logo_url', e.target.value), disabled: disabled, placeholder: "https://example.com/logo.svg", className: cn('flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm', 'focus:outline-none focus:ring-2 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50') }) }), branding.logo_url && (_jsx("div", { className: "mt-2 flex items-center gap-2", children: _jsx("img", { src: branding.logo_url, alt: "Logo preview", className: "h-12 object-contain", onError: (e) => {
|
|
54
|
+
e.currentTarget.style.display = 'none';
|
|
55
|
+
} }) }))] }), _jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [_jsx("label", { htmlFor: "logo-light-url", className: "font-medium", children: "Logo URL (Light Mode)" }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("input", { id: "logo-light-url", type: "url", value: branding.logo_light_url || '', onChange: (e) => handleTextUpdate('logo_light_url', e.target.value), disabled: disabled, placeholder: "https://example.com/logo-light.svg", className: cn('flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm', 'focus:outline-none focus:ring-2 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50') }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Optional: Use a different logo for light mode" })] }), _jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [_jsx("label", { htmlFor: "logo-dark-url", className: "font-medium", children: "Logo URL (Dark Mode)" }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("input", { id: "logo-dark-url", type: "url", value: branding.logo_dark_url || '', onChange: (e) => handleTextUpdate('logo_dark_url', e.target.value), disabled: disabled, placeholder: "https://example.com/logo-dark.svg", className: cn('flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm', 'focus:outline-none focus:ring-2 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50') }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Optional: Use a different logo for dark mode" })] }), _jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [_jsx("label", { htmlFor: "favicon-url", className: "font-medium", children: "Favicon URL" }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("input", { id: "favicon-url", type: "url", value: branding.favicon_url || '', onChange: (e) => handleTextUpdate('favicon_url', e.target.value), disabled: disabled, placeholder: "https://example.com/favicon.ico", className: cn('flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm', 'focus:outline-none focus:ring-2 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50') }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Square icon shown in browser tabs (16x16 or 32x32)" })] })] })] }), colorEditor.open && (_jsx(ColorEditorSheetWrapper, { open: colorEditor.open, title: `Edit ${colorEditor.label}`, description: "Adjust the OKLCH color values using the sliders below", value: colorEditor.value, onSave: handleColorSave, onCancel: closeColorEditor, renderEditor: renderColorEditor }))] }));
|
|
56
|
+
}
|
|
57
|
+
function ColorEditorSheetWrapper({ open, title, description, value, onSave, onCancel, renderEditor, }) {
|
|
58
|
+
const [editValue, setEditValue] = useState(value);
|
|
59
|
+
const [hasChanges, setHasChanges] = useState(false);
|
|
60
|
+
const handleChange = (newValue) => {
|
|
61
|
+
setEditValue(newValue);
|
|
62
|
+
setHasChanges(newValue !== value);
|
|
63
|
+
};
|
|
64
|
+
const handleSave = () => {
|
|
65
|
+
onSave(editValue);
|
|
66
|
+
};
|
|
67
|
+
if (!open)
|
|
68
|
+
return null;
|
|
69
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex", role: "dialog", "aria-modal": "true", "aria-labelledby": "color-editor-title", "aria-describedby": "color-editor-description", children: [_jsx("div", { className: "fixed inset-0 bg-background/80 backdrop-blur-sm", onClick: onCancel, "aria-hidden": "true" }), _jsxs("div", { className: "fixed inset-y-0 right-0 z-50 flex w-full flex-col border-l bg-background shadow-lg sm:max-w-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b px-6 py-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { id: "color-editor-title", className: "text-lg font-semibold text-foreground", children: title }), _jsx("p", { id: "color-editor-description", className: "text-sm text-muted-foreground", children: description })] }), _jsx("button", { type: "button", onClick: onCancel, className: cn('rounded-sm opacity-70 ring-offset-background transition-opacity', 'hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'), "aria-label": "Close", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-4 w-4", children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-6 py-6", children: renderEditor({
|
|
70
|
+
value: editValue,
|
|
71
|
+
onChange: handleChange,
|
|
72
|
+
}) }), _jsxs("div", { className: "flex items-center justify-end gap-2 border-t px-6 py-4", children: [_jsx("button", { type: "button", onClick: onCancel, className: cn('inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium', 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'), children: "Cancel" }), _jsx("button", { type: "button", onClick: handleSave, disabled: !hasChanges, className: cn('inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-text-on-primary hover:bg-primary/90', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:cursor-not-allowed disabled:opacity-50'), children: "Save Changes" })] })] })] }));
|
|
73
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColorEditorSheet — A sheet/dialog for editing branding colors
|
|
3
|
+
*
|
|
4
|
+
* This component provides a slide-out sheet interface for editing
|
|
5
|
+
* color values. It uses a slot-based design for the color editor
|
|
6
|
+
* to allow consumers to inject their own color picker implementation
|
|
7
|
+
* (e.g., OklchColorPicker, or a custom one).
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Slot-based color editor (renderEditor prop)
|
|
11
|
+
* - Save/Cancel actions
|
|
12
|
+
* - Accessible dialog implementation
|
|
13
|
+
* - Responsive design
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* <ColorEditorSheet
|
|
17
|
+
* open={isOpen}
|
|
18
|
+
* onOpenChange={setIsOpen}
|
|
19
|
+
* title="Edit Primary Color"
|
|
20
|
+
* value="oklch(0.546 0.215 262.9)"
|
|
21
|
+
* onSave={(value) => console.log('Saved:', value)}
|
|
22
|
+
* renderEditor={(props) => <OklchColorPicker {...props} />}
|
|
23
|
+
* />
|
|
24
|
+
*
|
|
25
|
+
* Note: Consumers must have shadcn Sheet component installed:
|
|
26
|
+
* npx shadcn@latest add sheet
|
|
27
|
+
*/
|
|
28
|
+
import { type ReactNode } from 'react';
|
|
29
|
+
export interface ColorEditorSheetProps {
|
|
30
|
+
/** Whether the sheet is open */
|
|
31
|
+
open: boolean;
|
|
32
|
+
/** Callback when open state changes */
|
|
33
|
+
onOpenChange: (open: boolean) => void;
|
|
34
|
+
/** Sheet title */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Description text */
|
|
37
|
+
description?: string;
|
|
38
|
+
/** Current color value */
|
|
39
|
+
value: string;
|
|
40
|
+
/** Callback when Save is clicked */
|
|
41
|
+
onSave: (value: string) => void;
|
|
42
|
+
/** Callback when Cancel is clicked (optional, defaults to closing sheet) */
|
|
43
|
+
onCancel?: () => void;
|
|
44
|
+
/** Render function for the color editor component */
|
|
45
|
+
renderEditor: (props: {
|
|
46
|
+
value: string;
|
|
47
|
+
onChange: (value: string) => void;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
}) => ReactNode;
|
|
50
|
+
/** Optional className for the sheet content */
|
|
51
|
+
className?: string;
|
|
52
|
+
/** Whether save button should be disabled */
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* ColorEditorSheet - Generic sheet for editing colors
|
|
57
|
+
*
|
|
58
|
+
* This is a "bring your own Sheet" component. Consumers must:
|
|
59
|
+
* 1. Have shadcn Sheet installed in their app
|
|
60
|
+
* 2. Pass this component to their app-specific Sheet wrapper
|
|
61
|
+
*
|
|
62
|
+
* For consumers WITHOUT shadcn Sheet, they can use this as a
|
|
63
|
+
* regular dialog/modal by wrapping it in their own dialog primitive.
|
|
64
|
+
*
|
|
65
|
+
* Internal implementation uses basic HTML + Tailwind for flexibility.
|
|
66
|
+
*/
|
|
67
|
+
export declare function ColorEditorSheet({ open, onOpenChange, title, description, value, onSave, onCancel, renderEditor, className, disabled, }: ColorEditorSheetProps): import("react/jsx-runtime").JSX.Element | null;
|
|
68
|
+
//# sourceMappingURL=ColorEditorSheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorEditorSheet.d.ts","sourceRoot":"","sources":["../../src/branding/ColorEditorSheet.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAG3D,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,uCAAuC;IACvC,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,oCAAoC;IACpC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,qDAAqD;IACrD,YAAY,EAAE,CAAC,KAAK,EAAE;QACpB,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;QACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,KAAK,SAAS,CAAA;IACf,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,EACZ,KAAoB,EACpB,WAAW,EACX,KAAK,EACL,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAgB,GACjB,EAAE,qBAAqB,kDAuIvB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ColorEditorSheet — A sheet/dialog for editing branding colors
|
|
4
|
+
*
|
|
5
|
+
* This component provides a slide-out sheet interface for editing
|
|
6
|
+
* color values. It uses a slot-based design for the color editor
|
|
7
|
+
* to allow consumers to inject their own color picker implementation
|
|
8
|
+
* (e.g., OklchColorPicker, or a custom one).
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Slot-based color editor (renderEditor prop)
|
|
12
|
+
* - Save/Cancel actions
|
|
13
|
+
* - Accessible dialog implementation
|
|
14
|
+
* - Responsive design
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* <ColorEditorSheet
|
|
18
|
+
* open={isOpen}
|
|
19
|
+
* onOpenChange={setIsOpen}
|
|
20
|
+
* title="Edit Primary Color"
|
|
21
|
+
* value="oklch(0.546 0.215 262.9)"
|
|
22
|
+
* onSave={(value) => console.log('Saved:', value)}
|
|
23
|
+
* renderEditor={(props) => <OklchColorPicker {...props} />}
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* Note: Consumers must have shadcn Sheet component installed:
|
|
27
|
+
* npx shadcn@latest add sheet
|
|
28
|
+
*/
|
|
29
|
+
import { useState, useEffect } from 'react';
|
|
30
|
+
import { cn } from '../utils.js';
|
|
31
|
+
/**
|
|
32
|
+
* ColorEditorSheet - Generic sheet for editing colors
|
|
33
|
+
*
|
|
34
|
+
* This is a "bring your own Sheet" component. Consumers must:
|
|
35
|
+
* 1. Have shadcn Sheet installed in their app
|
|
36
|
+
* 2. Pass this component to their app-specific Sheet wrapper
|
|
37
|
+
*
|
|
38
|
+
* For consumers WITHOUT shadcn Sheet, they can use this as a
|
|
39
|
+
* regular dialog/modal by wrapping it in their own dialog primitive.
|
|
40
|
+
*
|
|
41
|
+
* Internal implementation uses basic HTML + Tailwind for flexibility.
|
|
42
|
+
*/
|
|
43
|
+
export function ColorEditorSheet({ open, onOpenChange, title = 'Edit Color', description, value, onSave, onCancel, renderEditor, className, disabled = false, }) {
|
|
44
|
+
const [editValue, setEditValue] = useState(value);
|
|
45
|
+
const [hasChanges, setHasChanges] = useState(false);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (open) {
|
|
48
|
+
setEditValue(value);
|
|
49
|
+
setHasChanges(false);
|
|
50
|
+
}
|
|
51
|
+
}, [open, value]);
|
|
52
|
+
const handleChange = (newValue) => {
|
|
53
|
+
setEditValue(newValue);
|
|
54
|
+
setHasChanges(newValue !== value);
|
|
55
|
+
};
|
|
56
|
+
const handleSave = () => {
|
|
57
|
+
onSave(editValue);
|
|
58
|
+
onOpenChange(false);
|
|
59
|
+
};
|
|
60
|
+
const handleCancel = () => {
|
|
61
|
+
if (onCancel) {
|
|
62
|
+
onCancel();
|
|
63
|
+
}
|
|
64
|
+
onOpenChange(false);
|
|
65
|
+
};
|
|
66
|
+
if (!open)
|
|
67
|
+
return null;
|
|
68
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex", role: "dialog", "aria-modal": "true", "aria-labelledby": "color-editor-title", "aria-describedby": description ? 'color-editor-description' : undefined, children: [_jsx("div", { className: "fixed inset-0 bg-background/80 backdrop-blur-sm", onClick: handleCancel, "aria-hidden": "true" }), _jsxs("div", { className: "fixed inset-y-0 right-0 z-50 flex w-full flex-col border-l bg-background shadow-lg sm:max-w-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b px-6 py-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { id: "color-editor-title", className: "text-lg font-semibold text-foreground", children: title }), description && (_jsx("p", { id: "color-editor-description", className: "text-sm text-muted-foreground", children: description }))] }), _jsx("button", { type: "button", onClick: handleCancel, className: cn('rounded-sm opacity-70 ring-offset-background transition-opacity', 'hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:pointer-events-none'), "aria-label": "Close", disabled: disabled, children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-4 w-4", children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] }) })] }), _jsx("div", { className: cn('flex-1 overflow-y-auto px-6 py-6', className), children: renderEditor({
|
|
69
|
+
value: editValue,
|
|
70
|
+
onChange: handleChange,
|
|
71
|
+
disabled,
|
|
72
|
+
}) }), _jsxs("div", { className: "flex items-center justify-end gap-2 border-t px-6 py-4", children: [_jsx("button", { type: "button", onClick: handleCancel, disabled: disabled, className: cn('inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium', 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:cursor-not-allowed disabled:opacity-50'), children: "Cancel" }), _jsx("button", { type: "button", onClick: handleSave, disabled: disabled || !hasChanges, className: cn('inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-text-on-primary hover:bg-primary/90', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:cursor-not-allowed disabled:opacity-50'), children: "Save Changes" })] })] })] }));
|
|
73
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface OklchColorPickerProps {
|
|
2
|
+
/** Current OKLCH color value, e.g. "oklch(0.546 0.215 262.9)" */
|
|
3
|
+
value?: string;
|
|
4
|
+
/** Callback when color changes */
|
|
5
|
+
onChange?: (value: string) => void;
|
|
6
|
+
/** Label for the color picker */
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Whether the input is disabled */
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** Optional className for the container */
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Optional description/helper text */
|
|
13
|
+
description?: string;
|
|
14
|
+
/** Optional error message */
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function OklchColorPicker({ value, onChange, label, disabled, className, description, error, }: OklchColorPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=OklchColorPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OklchColorPicker.d.ts","sourceRoot":"","sources":["../../src/branding/OklchColorPicker.tsx"],"names":[],"mappings":"AAyBA,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kCAAkC;IAClC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA+BD,wBAAgB,gBAAgB,CAAC,EAC/B,KAA0B,EAC1B,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,SAAS,EACT,WAAW,EACX,KAAK,GACN,EAAE,qBAAqB,2CA8LvB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* OklchColorPicker — A color input component for OKLCH color format
|
|
4
|
+
*
|
|
5
|
+
* OKLCH is the BSuite standard color format for the D2C Neon Electric theme.
|
|
6
|
+
* This component provides a user-friendly interface for editing OKLCH colors
|
|
7
|
+
* with visual preview and validation.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Live preview of the selected color
|
|
11
|
+
* - Input fields for L (lightness), C (chroma), H (hue)
|
|
12
|
+
* - Validation of OKLCH format
|
|
13
|
+
* - Accessible labels and error messages
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* <OklchColorPicker
|
|
17
|
+
* value="oklch(0.546 0.215 262.9)"
|
|
18
|
+
* onChange={(value) => console.log(value)}
|
|
19
|
+
* label="Primary Color"
|
|
20
|
+
* />
|
|
21
|
+
*/
|
|
22
|
+
import { useState, useEffect } from 'react';
|
|
23
|
+
import { cn } from '../utils.js';
|
|
24
|
+
const OKLCH_RE = /^oklch\(\s*[\d.]+%?\s+[\d.]+%?\s+[\d.]+%?(?:\s*\/\s*[\d.]+%?)?\s*\)$/i;
|
|
25
|
+
function parseOklch(value) {
|
|
26
|
+
if (!value || !OKLCH_RE.test(value))
|
|
27
|
+
return null;
|
|
28
|
+
const match = value.match(/oklch\(\s*([\d.]+)%?\s+([\d.]+)%?\s+([\d.]+)%?(?:\s*\/\s*([\d.]+)%?)?\s*\)/i);
|
|
29
|
+
if (!match)
|
|
30
|
+
return null;
|
|
31
|
+
return {
|
|
32
|
+
l: parseFloat(match[1]),
|
|
33
|
+
c: parseFloat(match[2]),
|
|
34
|
+
h: parseFloat(match[3]),
|
|
35
|
+
alpha: match[4] ? parseFloat(match[4]) : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function formatOklch(components) {
|
|
39
|
+
const { l, c, h, alpha } = components;
|
|
40
|
+
if (alpha !== undefined && alpha !== 1) {
|
|
41
|
+
return `oklch(${l} ${c} ${h} / ${alpha})`;
|
|
42
|
+
}
|
|
43
|
+
return `oklch(${l} ${c} ${h})`;
|
|
44
|
+
}
|
|
45
|
+
export function OklchColorPicker({ value = 'oklch(0.5 0.1 0)', onChange, label, disabled = false, className, description, error, }) {
|
|
46
|
+
const parsed = parseOklch(value);
|
|
47
|
+
const [components, setComponents] = useState(parsed || { l: 0.5, c: 0.1, h: 0 });
|
|
48
|
+
const [inputValue, setInputValue] = useState(value);
|
|
49
|
+
const [isValid, setIsValid] = useState(true);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const newParsed = parseOklch(value);
|
|
52
|
+
if (newParsed) {
|
|
53
|
+
setComponents(newParsed);
|
|
54
|
+
setInputValue(value);
|
|
55
|
+
setIsValid(true);
|
|
56
|
+
}
|
|
57
|
+
}, [value]);
|
|
58
|
+
const handleComponentChange = (key, val) => {
|
|
59
|
+
const numVal = parseFloat(val);
|
|
60
|
+
if (isNaN(numVal))
|
|
61
|
+
return;
|
|
62
|
+
const newComponents = { ...components, [key]: numVal };
|
|
63
|
+
setComponents(newComponents);
|
|
64
|
+
const formatted = formatOklch(newComponents);
|
|
65
|
+
setInputValue(formatted);
|
|
66
|
+
setIsValid(true);
|
|
67
|
+
onChange?.(formatted);
|
|
68
|
+
};
|
|
69
|
+
const handleRawInputChange = (e) => {
|
|
70
|
+
const newValue = e.target.value;
|
|
71
|
+
setInputValue(newValue);
|
|
72
|
+
const newParsed = parseOklch(newValue);
|
|
73
|
+
if (newParsed) {
|
|
74
|
+
setComponents(newParsed);
|
|
75
|
+
setIsValid(true);
|
|
76
|
+
onChange?.(newValue);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
setIsValid(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return (_jsxs("div", { className: cn('space-y-3', className), children: [label && (_jsx("label", { className: "text-sm font-medium text-foreground", children: label })), description && (_jsx("p", { className: "text-sm text-muted-foreground", children: description })), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "h-12 w-12 rounded-md border-2 border-input shadow-sm", style: { backgroundColor: inputValue }, "aria-label": "Color preview" }), _jsx("div", { className: "flex-1", children: _jsx("input", { type: "text", value: inputValue, onChange: handleRawInputChange, disabled: disabled, className: cn('w-full rounded-md border border-input bg-background px-3 py-2 text-sm', 'focus:outline-none focus:ring-2 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50', !isValid && 'border-destructive'), placeholder: "oklch(0.5 0.1 0)", "aria-label": "OKLCH color value", "aria-invalid": !isValid }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "grid grid-cols-[80px_1fr_60px] items-center gap-2", children: [_jsx("label", { htmlFor: "oklch-l", className: "text-sm text-muted-foreground", children: "L (Light)" }), _jsx("input", { id: "oklch-l", type: "range", min: "0", max: "1", step: "0.01", value: components.l, onChange: (e) => handleComponentChange('l', e.target.value), disabled: disabled, className: "cursor-pointer disabled:cursor-not-allowed disabled:opacity-50", "aria-label": "Lightness" }), _jsx("input", { type: "number", min: "0", max: "1", step: "0.01", value: components.l.toFixed(2), onChange: (e) => handleComponentChange('l', e.target.value), disabled: disabled, className: cn('w-full rounded border border-input bg-background px-2 py-1 text-sm', 'focus:outline-none focus:ring-1 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50'), "aria-label": "Lightness value" })] }), _jsxs("div", { className: "grid grid-cols-[80px_1fr_60px] items-center gap-2", children: [_jsx("label", { htmlFor: "oklch-c", className: "text-sm text-muted-foreground", children: "C (Chroma)" }), _jsx("input", { id: "oklch-c", type: "range", min: "0", max: "0.4", step: "0.001", value: components.c, onChange: (e) => handleComponentChange('c', e.target.value), disabled: disabled, className: "cursor-pointer disabled:cursor-not-allowed disabled:opacity-50", "aria-label": "Chroma" }), _jsx("input", { type: "number", min: "0", max: "0.4", step: "0.001", value: components.c.toFixed(3), onChange: (e) => handleComponentChange('c', e.target.value), disabled: disabled, className: cn('w-full rounded border border-input bg-background px-2 py-1 text-sm', 'focus:outline-none focus:ring-1 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50'), "aria-label": "Chroma value" })] }), _jsxs("div", { className: "grid grid-cols-[80px_1fr_60px] items-center gap-2", children: [_jsx("label", { htmlFor: "oklch-h", className: "text-sm text-muted-foreground", children: "H (Hue)" }), _jsx("input", { id: "oklch-h", type: "range", min: "0", max: "360", step: "0.1", value: components.h, onChange: (e) => handleComponentChange('h', e.target.value), disabled: disabled, className: "cursor-pointer disabled:cursor-not-allowed disabled:opacity-50", "aria-label": "Hue" }), _jsx("input", { type: "number", min: "0", max: "360", step: "0.1", value: components.h.toFixed(1), onChange: (e) => handleComponentChange('h', e.target.value), disabled: disabled, className: cn('w-full rounded border border-input bg-background px-2 py-1 text-sm', 'focus:outline-none focus:ring-1 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50'), "aria-label": "Hue value" })] })] }), (error || !isValid) && (_jsx("p", { className: "text-sm text-destructive", children: error || 'Invalid OKLCH format. Expected: oklch(L C H) where L is 0-1, C is 0-0.4, H is 0-360' }))] }));
|
|
83
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bsuite/ui/branding — branding components
|
|
3
|
+
*/
|
|
4
|
+
export { BrandingCard, type BrandingCardProps } from './BrandingCard.js';
|
|
5
|
+
export { ColorEditorSheet, type ColorEditorSheetProps } from './ColorEditorSheet.js';
|
|
6
|
+
export { OklchColorPicker, type OklchColorPickerProps } from './OklchColorPicker.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/branding/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AACpF,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/button.d.ts
ADDED
|
@@ -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
|
+
});
|
package/dist/dialog.d.ts
ADDED
|
@@ -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
|
+
});
|
package/dist/dot-pattern.d.ts
CHANGED
|
@@ -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-
|
|
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
|
*/
|
package/dist/dot-pattern.js
CHANGED
|
@@ -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-
|
|
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,4 +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';
|
|
11
|
+
export { BrandingCard, ColorEditorSheet, OklchColorPicker, type BrandingCardProps, type ColorEditorSheetProps, type OklchColorPickerProps, } from './branding/index.js';
|
|
5
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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"}
|
|
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,3 +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';
|
|
11
|
+
// Branding components (v0.3.0+)
|
|
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
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -31,15 +31,47 @@
|
|
|
31
31
|
"import": "./dist/utils.js",
|
|
32
32
|
"types": "./dist/utils.d.ts"
|
|
33
33
|
},
|
|
34
|
+
"./branding": {
|
|
35
|
+
"import": "./dist/branding/index.js",
|
|
36
|
+
"types": "./dist/branding/index.d.ts"
|
|
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
|
+
},
|
|
34
62
|
"./assets/d2c-default-logo.svg": "./src/assets/d2c-default-logo.svg"
|
|
35
63
|
},
|
|
36
64
|
"scripts": {
|
|
37
65
|
"build": "tsc -p tsconfig.build.json",
|
|
66
|
+
"storybook": "storybook dev -p 6006",
|
|
67
|
+
"build-storybook": "storybook build",
|
|
38
68
|
"typecheck": "tsc --noEmit",
|
|
39
69
|
"test": "vitest run"
|
|
40
70
|
},
|
|
41
71
|
"dependencies": {
|
|
72
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
42
73
|
"clsx": "^2.1.1",
|
|
74
|
+
"react-error-boundary": "^6.1.2",
|
|
43
75
|
"tailwind-merge": "^3.5.0"
|
|
44
76
|
},
|
|
45
77
|
"peerDependencies": {
|
|
@@ -47,14 +79,20 @@
|
|
|
47
79
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
48
80
|
},
|
|
49
81
|
"devDependencies": {
|
|
82
|
+
"@bsuite/theme": "^0.4.1",
|
|
83
|
+
"@storybook/react-vite": "^10.4.2",
|
|
84
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
50
85
|
"@testing-library/jest-dom": "^6.9.1",
|
|
51
86
|
"@testing-library/react": "^16.3.2",
|
|
87
|
+
"@testing-library/user-event": "^14.6.1",
|
|
52
88
|
"@types/react": "^19.2.14",
|
|
53
89
|
"@types/react-dom": "^19.2.3",
|
|
54
90
|
"@vitejs/plugin-react": "^6.0.1",
|
|
55
91
|
"jsdom": "^29.1.1",
|
|
56
92
|
"react": "^19.2.5",
|
|
57
93
|
"react-dom": "^19.2.5",
|
|
94
|
+
"storybook": "^10.4.2",
|
|
95
|
+
"tailwindcss": "^4.3.0",
|
|
58
96
|
"typescript": "~5.9.3",
|
|
59
97
|
"vite": "^8.0.10",
|
|
60
98
|
"vitest": "^4.1.5"
|