@dynamic-labs-sdk/droplet 0.27.1 → 0.27.3
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 +116 -20
- package/dist/components/toggle-card/index.d.ts +2 -0
- package/dist/components/toggle-card/index.d.ts.map +1 -0
- package/dist/components/toggle-card/toggle-card.d.ts +113 -0
- package/dist/components/toggle-card/toggle-card.d.ts.map +1 -0
- package/dist/components/toggle-card/toggle-card.stories.d.ts +45 -0
- package/dist/components/toggle-card/toggle-card.stories.d.ts.map +1 -0
- package/dist/index.cjs +184 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +214 -30
- package/dist/index.esm.js.map +1 -1
- package/package.json +11 -2
- package/src/styles/globals.css +16 -12
package/README.md
CHANGED
|
@@ -1,38 +1,30 @@
|
|
|
1
1
|
# `@dynamic-labs-sdk/droplet`
|
|
2
2
|
|
|
3
|
-
Dynamic Labs'
|
|
3
|
+
Dynamic Labs' React component library. Built on Radix UI primitives, Tailwind CSS v4, and an OKLCH-based design token system. Ships with 35+ components, animated icons, dark mode, and a live Storybook.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
}
|
|
9
|
+
```bash
|
|
10
|
+
npm install @dynamic-labs-sdk/droplet
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @dynamic-labs-sdk/droplet
|
|
13
|
+
# or
|
|
14
|
+
yarn add @dynamic-labs-sdk/droplet
|
|
17
15
|
```
|
|
18
16
|
|
|
17
|
+
**Peer dependencies:** `react >= 18`, `react-dom >= 18`, `tailwindcss >= 4`. Next.js (`>= 14`) is optional.
|
|
18
|
+
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## Setup
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Droplet ships Tailwind CSS v4 utility classes baked into its compiled output. You must (1) have Tailwind v4 configured in your app, (2) import the design tokens stylesheet, and (3) tell Tailwind to scan droplet's `dist/` so its utility classes are emitted into your stylesheet.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
import '@dynamic-labs-sdk/droplet/styles/globals.css';
|
|
27
|
-
```
|
|
25
|
+
### 1. Install and configure Tailwind v4
|
|
28
26
|
|
|
29
|
-
If you'
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import '../../packages/droplet/src/styles/globals.css';
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Your bundler must support Tailwind CSS v4 via the `@tailwindcss/vite` plugin:
|
|
27
|
+
If you don't already have Tailwind v4, follow [the Tailwind v4 install guide](https://tailwindcss.com/docs/installation). The `@tailwindcss/vite` plugin is the simplest path for Vite/Next:
|
|
36
28
|
|
|
37
29
|
```ts
|
|
38
30
|
// vite.config.ts
|
|
@@ -44,6 +36,26 @@ export default defineConfig({
|
|
|
44
36
|
});
|
|
45
37
|
```
|
|
46
38
|
|
|
39
|
+
### 2. Import the design tokens stylesheet
|
|
40
|
+
|
|
41
|
+
Import once at your app root. This provides the full color, shadow, radius, and typography token system and is required for all components to render correctly.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import '@dynamic-labs-sdk/droplet/styles/globals.css';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3. Add droplet to Tailwind's `@source` paths
|
|
48
|
+
|
|
49
|
+
Tailwind v4 only scans the consuming project by default, so it will miss the utility classes inside droplet's compiled JS. Add an `@source` directive to your app's main stylesheet:
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
/* app.css */
|
|
53
|
+
@import 'tailwindcss';
|
|
54
|
+
@source "../node_modules/@dynamic-labs-sdk/droplet/dist";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Adjust the relative path to match where your stylesheet lives. Without this step, components will render unstyled.
|
|
58
|
+
|
|
47
59
|
---
|
|
48
60
|
|
|
49
61
|
## Usage
|
|
@@ -194,6 +206,90 @@ import {
|
|
|
194
206
|
|
|
195
207
|
---
|
|
196
208
|
|
|
209
|
+
## Theming
|
|
210
|
+
|
|
211
|
+
Droplet's theme is a layer of CSS custom properties. Tailwind utility classes resolve through them (`bg-brand-default` → `var(--brand-default)`), and every component reads from the same vars — so you can fully rebrand by **redefining the vars in your own stylesheet**. No Tailwind config changes, no rebuild, no fork.
|
|
212
|
+
|
|
213
|
+
### Quick start: change the brand color
|
|
214
|
+
|
|
215
|
+
```css
|
|
216
|
+
/* your-theme.css — imported AFTER @dynamic-labs-sdk/droplet/styles/globals.css */
|
|
217
|
+
|
|
218
|
+
:root {
|
|
219
|
+
--brand-default: oklch(0.67 0.21 30); /* your light-mode brand */
|
|
220
|
+
--brand-hover: oklch(0.72 0.19 30);
|
|
221
|
+
--brand-accented: oklch(0.58 0.22 30);
|
|
222
|
+
--brand-light: oklch(0.97 0.03 30);
|
|
223
|
+
--ring: oklch(0.67 0.21 30);
|
|
224
|
+
--action: oklch(0.67 0.21 30);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.dark {
|
|
228
|
+
--brand-default: oklch(0.6 0.22 30); /* your dark-mode brand */
|
|
229
|
+
--brand-hover: oklch(0.68 0.19 30);
|
|
230
|
+
--brand-accented: oklch(0.52 0.23 30);
|
|
231
|
+
--brand-light: oklch(0.27 0.07 30);
|
|
232
|
+
--ring: oklch(0.6 0.22 30);
|
|
233
|
+
--action: oklch(0.6 0.22 30);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
// in your app entry, AFTER the droplet import
|
|
239
|
+
import '@dynamic-labs-sdk/droplet/styles/globals.css';
|
|
240
|
+
import './your-theme.css';
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
That's it. Buttons, links, focus rings, text-selection highlights, sidebars — all switch to the new brand.
|
|
244
|
+
|
|
245
|
+
### Cascade order matters
|
|
246
|
+
|
|
247
|
+
Your theme stylesheet **must load after** `@dynamic-labs-sdk/droplet/styles/globals.css`, or the same selectors (`:root`, `.dark`) in droplet's stylesheet will win and your overrides will silently do nothing. If you `@import` in a single CSS file, put droplet's import **first**:
|
|
248
|
+
|
|
249
|
+
```css
|
|
250
|
+
@import '@dynamic-labs-sdk/droplet/styles/globals.css';
|
|
251
|
+
@import './your-theme.css';
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### What's themeable
|
|
255
|
+
|
|
256
|
+
Anything in the [Design tokens](#design-tokens) table below — colors, shadows, radii. The most common things to override:
|
|
257
|
+
|
|
258
|
+
| What you want to change | Tokens |
|
|
259
|
+
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
260
|
+
| Brand color (buttons, focus rings, links, selection) | `--brand-default`, `--brand-hover`, `--brand-accented`, `--brand-light`, `--ring`, `--action` |
|
|
261
|
+
| Surfaces (page bg, cards, sidebar) | `--background`, `--card`, `--bg-bottom`, `--bg-default`, `--sidebar` |
|
|
262
|
+
| Text colors | `--foreground`, `--muted-foreground`, `--text-link` |
|
|
263
|
+
| Radius scale | `--radius-sm` through `--radius-4xl` |
|
|
264
|
+
| Selection highlight | `--selection-bg` |
|
|
265
|
+
| Solid button top-highlight | `--button-highlight` |
|
|
266
|
+
| Checkbox / radio hover fills | `--checkbox-hover-fill`, `--radio-hover-fill` |
|
|
267
|
+
|
|
268
|
+
For semantic colors (success/danger/warning/process), override the matching `--<state>-default`, `--<state>-light`, `--text-<state>` triplet.
|
|
269
|
+
|
|
270
|
+
### Scoping a theme to part of your app
|
|
271
|
+
|
|
272
|
+
If you only want droplet themed inside a subtree (e.g., a marketing site reusing one component), put the overrides on a wrapper class instead of `:root`:
|
|
273
|
+
|
|
274
|
+
```css
|
|
275
|
+
.my-theme {
|
|
276
|
+
--brand-default: oklch(0.67 0.21 30);
|
|
277
|
+
/* ... */
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<div className="my-theme">
|
|
283
|
+
<Button>Themed</Button>
|
|
284
|
+
</div>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Color space
|
|
288
|
+
|
|
289
|
+
Droplet uses [OKLCH](https://oklch.com/) throughout. If your design system is in hex/rgb, tools like [oklch.com](https://oklch.com) or [oklch.fyi](https://oklch.fyi) will convert. You don't *have* to use OKLCH in your overrides — `oklch()`, `hex`, `rgb()`, and `hsl()` all interoperate fine — but staying in OKLCH gives you perceptually uniform lightness ramps when you tune hover/accented variants.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
197
293
|
## Design tokens
|
|
198
294
|
|
|
199
295
|
All tokens are CSS custom properties defined in `globals.css`. Tailwind utility classes map directly to them — `bg-brand-default` resolves to `var(--brand-default)`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,GAChC,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToggleCard — droplet's rich toggle composite.
|
|
3
|
+
*
|
|
4
|
+
* Rebuilt from Northstar's SingleToggleCard to support the redcoast dashboard
|
|
5
|
+
* migration. ToggleRow (sibling primitive) remains the minimal/canonical
|
|
6
|
+
* single-line toggle; ToggleCard is the multi-feature card variant with:
|
|
7
|
+
* - Accordion expand/collapse for nested children
|
|
8
|
+
* - Pluggable input type: switch (default), radio, or checkbox
|
|
9
|
+
* - Optional left icon
|
|
10
|
+
* - Tags row above the title and a RightSideTags slot on the right
|
|
11
|
+
* - Optional notification dot in the trigger
|
|
12
|
+
* - Optional `actionLink` rendered below content
|
|
13
|
+
* - Optional `customActionIcon` rendered alongside the trigger affordance
|
|
14
|
+
* - Whole-card click handler
|
|
15
|
+
*
|
|
16
|
+
* The API mirrors Northstar SingleToggleCard so a redcoast migration can be a
|
|
17
|
+
* mechanical rename. Where the Northstar API surfaced layout-tuning props
|
|
18
|
+
* (`disableHover`, `dontSeparateContentFromHeader`, `descriptionModificators`,
|
|
19
|
+
* `titleModificators`) the equivalent expression here is via Tailwind classes
|
|
20
|
+
* on `className` or `triggerClassName`.
|
|
21
|
+
*
|
|
22
|
+
* Authored in droplet so a single canonical card is available across teams.
|
|
23
|
+
* Backed by Card + Switch/Checkbox/RadioGroup + Radix Accordion.
|
|
24
|
+
*/
|
|
25
|
+
import { type ChangeEvent, type FC, type ReactElement, type ReactNode } from 'react';
|
|
26
|
+
type ToggleCardInputType = 'toggle' | 'radio' | 'checkbox';
|
|
27
|
+
export type ToggleCardInputProps = {
|
|
28
|
+
checked?: boolean;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
/** Hide the input control entirely (useful when the row is informational). */
|
|
31
|
+
hidden?: boolean;
|
|
32
|
+
id?: string;
|
|
33
|
+
/** When true, renders a lock badge instead of a switch — signals the value
|
|
34
|
+
* is fixed and cannot be toggled via this UI. Implies disabled. */
|
|
35
|
+
isLocked?: boolean;
|
|
36
|
+
/** Indicates the saved/unsaved state via styling — informational, optional. */
|
|
37
|
+
isSaved?: boolean;
|
|
38
|
+
/** When true, paints the input with the warning accent (informational). */
|
|
39
|
+
isWarning?: boolean;
|
|
40
|
+
name?: string;
|
|
41
|
+
/** Native onChange — receives a synthetic event for parity with HTML inputs. */
|
|
42
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
43
|
+
/** Controls which control element renders next to the title. */
|
|
44
|
+
type?: ToggleCardInputType;
|
|
45
|
+
};
|
|
46
|
+
export type ToggleCardTag = {
|
|
47
|
+
Tag: ReactElement;
|
|
48
|
+
id: string;
|
|
49
|
+
};
|
|
50
|
+
export type ToggleCardActionLink = {
|
|
51
|
+
onClick: () => void;
|
|
52
|
+
text: string;
|
|
53
|
+
};
|
|
54
|
+
export type ToggleCardCustomActionIcon = {
|
|
55
|
+
Icon: ReactElement;
|
|
56
|
+
className?: string;
|
|
57
|
+
};
|
|
58
|
+
export type ToggleCardProps = {
|
|
59
|
+
/** Optional icon rendered before the title. */
|
|
60
|
+
Icon?: ReactElement;
|
|
61
|
+
/** Tags shown on the RIGHT side of the trigger (e.g. status badges). */
|
|
62
|
+
RightSideTags?: ToggleCardTag[];
|
|
63
|
+
/** Tags shown ABOVE the title (e.g. status pills). */
|
|
64
|
+
Tags?: ToggleCardTag[];
|
|
65
|
+
/** Required key for Radix Accordion item identity. */
|
|
66
|
+
accordionKey: string;
|
|
67
|
+
/** Action button rendered at the bottom of expanded content. */
|
|
68
|
+
actionLink?: ToggleCardActionLink;
|
|
69
|
+
/** Whether the accordion CAN expand. Defaults to true. */
|
|
70
|
+
allowExpand?: boolean;
|
|
71
|
+
/** Children — rendered inside the AccordionContent when expanded. */
|
|
72
|
+
children?: ReactNode;
|
|
73
|
+
/** className applied to the outer Accordion root. */
|
|
74
|
+
className?: string;
|
|
75
|
+
/** Whether the accordion can be collapsed (Radix `collapsible`). */
|
|
76
|
+
collapsible?: boolean;
|
|
77
|
+
/** Replaces the default chevron with a custom icon. */
|
|
78
|
+
customActionIcon?: ToggleCardCustomActionIcon;
|
|
79
|
+
/** Whether the accordion is expanded by default. */
|
|
80
|
+
defaultExpanded?: boolean;
|
|
81
|
+
/** Optional secondary line below the title. */
|
|
82
|
+
description?: string;
|
|
83
|
+
/** Disables the entire card (input + trigger). */
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
/** Inputs (toggle/radio/checkbox) rendered alongside the title. */
|
|
86
|
+
inputProps?: ToggleCardInputProps;
|
|
87
|
+
/** When true, draws a notification dot on the trigger. */
|
|
88
|
+
notification?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Click handler for the whole card surface.
|
|
91
|
+
*
|
|
92
|
+
* @deprecated Currently a no-op: attaching onClick to a wrapper that nests
|
|
93
|
+
* interactive children (AccordionTrigger, Switch / Checkbox / RadioGroup)
|
|
94
|
+
* is an a11y anti-pattern. Kept in the type signature for Northstar
|
|
95
|
+
* mechanical-rename parity. A future iteration may re-introduce this with
|
|
96
|
+
* a dedicated click target (e.g. an overlay button) outside the nested
|
|
97
|
+
* interactives.
|
|
98
|
+
*/
|
|
99
|
+
onCardClick?: () => void;
|
|
100
|
+
/** Accordion value-change handler. */
|
|
101
|
+
onValueChange?: (value: string | undefined) => void;
|
|
102
|
+
/** Card title — rendered as the primary label inside the trigger. */
|
|
103
|
+
title?: string;
|
|
104
|
+
/** className applied to the trigger row. */
|
|
105
|
+
triggerClassName?: string;
|
|
106
|
+
/** Test id applied to the accordion trigger element. */
|
|
107
|
+
triggerTestId?: string;
|
|
108
|
+
/** Controlled accordion value. */
|
|
109
|
+
value?: string;
|
|
110
|
+
};
|
|
111
|
+
export declare const ToggleCard: FC<ToggleCardProps>;
|
|
112
|
+
export {};
|
|
113
|
+
//# sourceMappingURL=toggle-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle-card.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-card/toggle-card.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,EAAE,EACP,KAAK,YAAY,EACjB,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAcf,KAAK,mBAAmB,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;uEACmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAC1D,gEAAgE;IAChE,IAAI,CAAC,EAAE,mBAAmB,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,YAAY,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,wEAAwE;IACxE,aAAa,CAAC,EAAE,aAAa,EAAE,CAAC;IAChC,sDAAsD;IACtD,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC;IACvB,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;IAC9C,oDAAoD;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,0DAA0D;IAC1D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,sCAAsC;IACtC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACpD,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAuFF,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA6L1C,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { StoryObj } from '@storybook/react';
|
|
3
|
+
declare const meta: {
|
|
4
|
+
args: {
|
|
5
|
+
accordionKey: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputProps: {
|
|
8
|
+
checked: true;
|
|
9
|
+
type: "toggle";
|
|
10
|
+
};
|
|
11
|
+
title: string;
|
|
12
|
+
};
|
|
13
|
+
argTypes: {
|
|
14
|
+
allowExpand: {
|
|
15
|
+
control: "boolean";
|
|
16
|
+
};
|
|
17
|
+
disabled: {
|
|
18
|
+
control: "boolean";
|
|
19
|
+
};
|
|
20
|
+
notification: {
|
|
21
|
+
control: "boolean";
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
component: React.FC<import("./toggle-card").ToggleCardProps>;
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: string;
|
|
27
|
+
};
|
|
28
|
+
tags: string[];
|
|
29
|
+
title: string;
|
|
30
|
+
};
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof meta>;
|
|
33
|
+
export declare const Default: Story;
|
|
34
|
+
export declare const WithDescription: Story;
|
|
35
|
+
export declare const WithTags: Story;
|
|
36
|
+
export declare const WithRightSideTags: Story;
|
|
37
|
+
export declare const Expandable: Story;
|
|
38
|
+
export declare const WithActionLink: Story;
|
|
39
|
+
export declare const IsLocked: Story;
|
|
40
|
+
export declare const Notification: Story;
|
|
41
|
+
export declare const RadioInput: Story;
|
|
42
|
+
export declare const CheckboxInput: Story;
|
|
43
|
+
export declare const Disabled: Story;
|
|
44
|
+
export declare const WholeCardClickable: Story;
|
|
45
|
+
//# sourceMappingURL=toggle-card.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle-card.stories.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-card/toggle-card.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAKvD,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgByB,CAAC;AAEpC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAiBrB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAkB7B,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAuBtB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAqB/B,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAwBxB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,KA6B5B,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAetB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAuB1B,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAmBxB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAmB3B,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAmBtB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,KAqBhC,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -2192,6 +2192,189 @@ const StatusBadge = ({ status, label, className }) => {
|
|
|
2192
2192
|
});
|
|
2193
2193
|
};
|
|
2194
2194
|
|
|
2195
|
+
//#endregion
|
|
2196
|
+
//#region src/components/toggle-card/toggle-card.tsx
|
|
2197
|
+
/**
|
|
2198
|
+
* ToggleCard — droplet's rich toggle composite.
|
|
2199
|
+
*
|
|
2200
|
+
* Rebuilt from Northstar's SingleToggleCard to support the redcoast dashboard
|
|
2201
|
+
* migration. ToggleRow (sibling primitive) remains the minimal/canonical
|
|
2202
|
+
* single-line toggle; ToggleCard is the multi-feature card variant with:
|
|
2203
|
+
* - Accordion expand/collapse for nested children
|
|
2204
|
+
* - Pluggable input type: switch (default), radio, or checkbox
|
|
2205
|
+
* - Optional left icon
|
|
2206
|
+
* - Tags row above the title and a RightSideTags slot on the right
|
|
2207
|
+
* - Optional notification dot in the trigger
|
|
2208
|
+
* - Optional `actionLink` rendered below content
|
|
2209
|
+
* - Optional `customActionIcon` rendered alongside the trigger affordance
|
|
2210
|
+
* - Whole-card click handler
|
|
2211
|
+
*
|
|
2212
|
+
* The API mirrors Northstar SingleToggleCard so a redcoast migration can be a
|
|
2213
|
+
* mechanical rename. Where the Northstar API surfaced layout-tuning props
|
|
2214
|
+
* (`disableHover`, `dontSeparateContentFromHeader`, `descriptionModificators`,
|
|
2215
|
+
* `titleModificators`) the equivalent expression here is via Tailwind classes
|
|
2216
|
+
* on `className` or `triggerClassName`.
|
|
2217
|
+
*
|
|
2218
|
+
* Authored in droplet so a single canonical card is available across teams.
|
|
2219
|
+
* Backed by Card + Switch/Checkbox/RadioGroup + Radix Accordion.
|
|
2220
|
+
*/
|
|
2221
|
+
const inputCommon = "shrink-0 touch-manipulation cursor-pointer disabled:cursor-not-allowed disabled:opacity-50";
|
|
2222
|
+
const renderInput = (input, id) => {
|
|
2223
|
+
if (!input || input.hidden) return null;
|
|
2224
|
+
if (input.isLocked) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2225
|
+
"aria-label": "Locked",
|
|
2226
|
+
title: "Locked",
|
|
2227
|
+
className: "shrink-0 inline-flex items-center justify-center size-5 rounded-full bg-muted text-muted-foreground mt-0.5",
|
|
2228
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2229
|
+
className: "size-3",
|
|
2230
|
+
viewBox: "0 0 24 24",
|
|
2231
|
+
fill: "none",
|
|
2232
|
+
stroke: "currentColor",
|
|
2233
|
+
strokeWidth: "2",
|
|
2234
|
+
strokeLinecap: "round",
|
|
2235
|
+
strokeLinejoin: "round",
|
|
2236
|
+
"aria-hidden": true,
|
|
2237
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
2238
|
+
x: "3",
|
|
2239
|
+
y: "11",
|
|
2240
|
+
width: "18",
|
|
2241
|
+
height: "11",
|
|
2242
|
+
rx: "2",
|
|
2243
|
+
ry: "2"
|
|
2244
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M7 11V7a5 5 0 0110 0v4" })]
|
|
2245
|
+
})
|
|
2246
|
+
});
|
|
2247
|
+
const type = input.type ?? "toggle";
|
|
2248
|
+
const handleChange = (next) => {
|
|
2249
|
+
if (!input.onChange) return;
|
|
2250
|
+
input.onChange({ target: {
|
|
2251
|
+
checked: next,
|
|
2252
|
+
name: input.name ?? ""
|
|
2253
|
+
} });
|
|
2254
|
+
};
|
|
2255
|
+
if (type === "toggle") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_toggle_row.Switch, {
|
|
2256
|
+
id: input.id ?? id,
|
|
2257
|
+
checked: Boolean(input.checked),
|
|
2258
|
+
disabled: input.disabled,
|
|
2259
|
+
onCheckedChange: handleChange,
|
|
2260
|
+
className: require_toggle_row.cn(inputCommon, "mt-0.5")
|
|
2261
|
+
});
|
|
2262
|
+
if (type === "checkbox") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
|
|
2263
|
+
id: input.id ?? id,
|
|
2264
|
+
checked: Boolean(input.checked),
|
|
2265
|
+
disabled: input.disabled,
|
|
2266
|
+
onCheckedChange: (state) => handleChange(state === true),
|
|
2267
|
+
className: require_toggle_row.cn(inputCommon, "mt-1")
|
|
2268
|
+
});
|
|
2269
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadioGroup, {
|
|
2270
|
+
value: input.checked ? input.id ?? id : "",
|
|
2271
|
+
onValueChange: (v) => handleChange(v === (input.id ?? id)),
|
|
2272
|
+
disabled: input.disabled,
|
|
2273
|
+
className: "shrink-0 mt-0.5",
|
|
2274
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadioGroupItem, {
|
|
2275
|
+
id: input.id ?? id,
|
|
2276
|
+
value: input.id ?? id,
|
|
2277
|
+
className: inputCommon
|
|
2278
|
+
})
|
|
2279
|
+
});
|
|
2280
|
+
};
|
|
2281
|
+
const ToggleCard = ({ accordionKey, title, description, inputProps, allowExpand = true, defaultExpanded, value, onValueChange, collapsible = true, Tags, RightSideTags, notification, actionLink, customActionIcon, onCardClick: _onCardClick, children, disabled, triggerTestId, className, triggerClassName }) => {
|
|
2282
|
+
const generatedId = (0, react.useId)();
|
|
2283
|
+
const inputId = inputProps?.id ?? generatedId;
|
|
2284
|
+
const isParentMode = !allowExpand && Boolean(children);
|
|
2285
|
+
const renderContent = () => {
|
|
2286
|
+
const actionLinkButton = actionLink ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2287
|
+
type: "button",
|
|
2288
|
+
onClick: actionLink.onClick,
|
|
2289
|
+
className: "mt-3 text-sm font-medium text-(--action) hover:underline underline-offset-4 cursor-pointer",
|
|
2290
|
+
children: actionLink.text
|
|
2291
|
+
}) : null;
|
|
2292
|
+
if (allowExpand) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_accordion.Content, {
|
|
2293
|
+
className: "border-t border-border-divider px-5 py-4",
|
|
2294
|
+
children: [children, actionLinkButton]
|
|
2295
|
+
});
|
|
2296
|
+
if (!children && !actionLink) return null;
|
|
2297
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2298
|
+
className: "border-t border-border-divider bg-card px-5 py-3 space-y-2",
|
|
2299
|
+
children: [children, actionLinkButton]
|
|
2300
|
+
});
|
|
2301
|
+
};
|
|
2302
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_accordion.Root, {
|
|
2303
|
+
type: "single",
|
|
2304
|
+
collapsible,
|
|
2305
|
+
defaultValue: defaultExpanded ? accordionKey : void 0,
|
|
2306
|
+
value,
|
|
2307
|
+
onValueChange,
|
|
2308
|
+
"data-slot": "toggle-card",
|
|
2309
|
+
className: require_toggle_row.cn("rounded-xl bg-card shadow-card overflow-hidden", disabled && "opacity-60", className),
|
|
2310
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_accordion.Item, {
|
|
2311
|
+
value: accordionKey,
|
|
2312
|
+
className: "border-0",
|
|
2313
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_accordion.Header, {
|
|
2314
|
+
className: "m-0",
|
|
2315
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2316
|
+
className: require_toggle_row.cn("group flex items-start gap-3 px-5 py-4", isParentMode && "bg-bg-bottom", triggerClassName),
|
|
2317
|
+
children: [
|
|
2318
|
+
renderInput(inputProps, inputId),
|
|
2319
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2320
|
+
className: "flex-1 min-w-0",
|
|
2321
|
+
children: [
|
|
2322
|
+
title && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2323
|
+
className: "flex items-baseline gap-1.5 flex-wrap leading-5",
|
|
2324
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2325
|
+
htmlFor: inputId,
|
|
2326
|
+
className: require_toggle_row.cn("text-sm font-medium select-none leading-5", disabled && "text-muted-foreground"),
|
|
2327
|
+
children: [title, notification && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2328
|
+
"aria-hidden": true,
|
|
2329
|
+
className: "inline-block ml-1.5 size-1.5 rounded-full bg-destructive align-middle"
|
|
2330
|
+
})]
|
|
2331
|
+
}), Tags && Tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: Tags.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2332
|
+
className: "leading-5",
|
|
2333
|
+
children: t.Tag
|
|
2334
|
+
}, t.id)) })]
|
|
2335
|
+
}),
|
|
2336
|
+
!title && Tags && Tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2337
|
+
className: "flex items-baseline gap-1.5 mb-1 leading-5",
|
|
2338
|
+
children: Tags.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2339
|
+
className: "leading-5",
|
|
2340
|
+
children: t.Tag
|
|
2341
|
+
}, t.id))
|
|
2342
|
+
}),
|
|
2343
|
+
description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2344
|
+
className: "text-sm text-muted-foreground mt-0.5 text-pretty",
|
|
2345
|
+
children: description
|
|
2346
|
+
})
|
|
2347
|
+
]
|
|
2348
|
+
}),
|
|
2349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2350
|
+
className: "flex items-center gap-2 shrink-0 self-start",
|
|
2351
|
+
children: [RightSideTags && RightSideTags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2352
|
+
className: "flex items-center gap-1.5",
|
|
2353
|
+
children: RightSideTags.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.Tag }, t.id))
|
|
2354
|
+
}), allowExpand && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_accordion.Trigger, {
|
|
2355
|
+
"data-testid": triggerTestId,
|
|
2356
|
+
className: require_toggle_row.cn("shrink-0 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-[transform,background-color] duration-150 hover:bg-overlay-hover [&[data-state=open]>svg]:rotate-180", customActionIcon?.className),
|
|
2357
|
+
disabled: !allowExpand,
|
|
2358
|
+
children: customActionIcon ? customActionIcon.Icon : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2359
|
+
className: "size-4",
|
|
2360
|
+
viewBox: "0 0 24 24",
|
|
2361
|
+
fill: "none",
|
|
2362
|
+
stroke: "currentColor",
|
|
2363
|
+
strokeWidth: "2",
|
|
2364
|
+
strokeLinecap: "round",
|
|
2365
|
+
strokeLinejoin: "round",
|
|
2366
|
+
"aria-hidden": true,
|
|
2367
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "6 9 12 15 18 9" })
|
|
2368
|
+
})
|
|
2369
|
+
})]
|
|
2370
|
+
})
|
|
2371
|
+
]
|
|
2372
|
+
})
|
|
2373
|
+
}), renderContent()]
|
|
2374
|
+
})
|
|
2375
|
+
});
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2195
2378
|
//#endregion
|
|
2196
2379
|
//#region src/components/icons/arrow-up-right/arrow-up-right.tsx
|
|
2197
2380
|
const ArrowUpRightIcon = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedIcon, {
|
|
@@ -3858,6 +4041,7 @@ exports.TabsTrigger = TabsTrigger;
|
|
|
3858
4041
|
exports.TerminalIcon = TerminalIcon;
|
|
3859
4042
|
exports.Textarea = Textarea;
|
|
3860
4043
|
exports.Toaster = Toaster;
|
|
4044
|
+
exports.ToggleCard = ToggleCard;
|
|
3861
4045
|
exports.ToggleRow = require_toggle_row.ToggleRow;
|
|
3862
4046
|
exports.Tooltip = Tooltip;
|
|
3863
4047
|
exports.TooltipContent = TooltipContent;
|