@nextlyhq/plugin-page-builder 0.0.2-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,212 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Core contracts for the page builder — isomorphic and runtime-React-free.
5
+ *
6
+ * Only *type-only* imports of React / Nextly are allowed here (they are erased at
7
+ * build, so the `.` bundle has no React/Nextly runtime dependency). The registry
8
+ * stores block-definition objects (including their `render` functions) but core
9
+ * never calls React itself.
10
+ */
11
+
12
+ /** Document-format version. Bumped only for envelope-shape changes (migrations). */
13
+ type DocumentVersion = 1;
14
+ /** Reserved doors (spec §13): templates/parts and i18n are designed-for, not built. */
15
+ type BlockDocumentKind = "page" | "template" | "part";
16
+ interface BlockDocument {
17
+ version: DocumentVersion;
18
+ /** Reserved — defaults to "page". Enables Theme-Builder-style templates later. */
19
+ kind?: BlockDocumentKind;
20
+ /** Reserved (i18n) — the locale this document's content is authored in. */
21
+ locale?: string;
22
+ /** Reserved (i18n) — links documents that are translations of one another. */
23
+ translationGroup?: string;
24
+ root: BlockNode;
25
+ /** Reserved — page-level settings (SEO, etc.). */
26
+ settings?: {
27
+ seo?: Record<string, unknown>;
28
+ };
29
+ /** Reserved — a usage index so media/relationship refs are trackable without a full walk. */
30
+ assets?: {
31
+ mediaIds?: string[];
32
+ };
33
+ }
34
+ interface BlockNode {
35
+ /** Stable unique id (crypto.randomUUID). Stable across locales; drives the scoped CSS class. */
36
+ id: string;
37
+ /** Namespaced block type, e.g. "core/heading". */
38
+ type: string;
39
+ /** Schema version of the block instance; compared to the definition's `version` for migrations. */
40
+ definitionVersion?: number;
41
+ /** Content/config values. Literal values only — bound values live in `bindings`. */
42
+ props: Record<string, unknown>;
43
+ /** Typed, responsive style overrides (spec §8). */
44
+ style?: ResponsiveStyle;
45
+ /** Responsive style overrides applied on `:hover` (spec §8, hover states). */
46
+ styleHover?: ResponsiveStyle;
47
+ /** Named child regions. "default" is the primary slot; only container blocks have slots. */
48
+ slots?: Record<string, BlockNode[]>;
49
+ /** Typed data bindings, keyed by the prop they fill. Kept separate from `props` (spec §10). */
50
+ bindings?: Record<string, Binding>;
51
+ /** Author escape hatch. */
52
+ customClass?: string;
53
+ }
54
+ type Breakpoint = string;
55
+ interface BoxSides {
56
+ top?: string;
57
+ right?: string;
58
+ bottom?: string;
59
+ left?: string;
60
+ }
61
+ /** A style value may be a literal or a design-token reference (spec §8). */
62
+ type TokenRef = {
63
+ token: string;
64
+ };
65
+ type StyleScalar = string | TokenRef;
66
+ interface StyleValues {
67
+ margin?: BoxSides;
68
+ padding?: BoxSides;
69
+ backgroundColor?: StyleScalar;
70
+ backgroundImage?: StyleScalar;
71
+ color?: StyleScalar;
72
+ fontSize?: StyleScalar;
73
+ lineHeight?: StyleScalar;
74
+ textAlign?: "left" | "center" | "right" | "justify";
75
+ width?: StyleScalar;
76
+ maxWidth?: StyleScalar;
77
+ height?: StyleScalar;
78
+ borderRadius?: StyleScalar;
79
+ display?: string;
80
+ gridTemplateColumns?: StyleScalar;
81
+ gap?: StyleScalar;
82
+ justifyContent?: string;
83
+ alignItems?: string;
84
+ }
85
+ /** Per-breakpoint style overrides. The base breakpoint holds defaults; others override. */
86
+ type ResponsiveStyle = Partial<Record<Breakpoint, StyleValues>>;
87
+ interface Binding {
88
+ source: "field";
89
+ /** Dot-path into the current Query Loop item, e.g. "title" or "author.name". */
90
+ path: string;
91
+ /** Optional display transform, e.g. "date:MMM d, yyyy". */
92
+ transform?: string;
93
+ }
94
+ type BlockCategory = "basic" | "layout" | "media" | "dynamic";
95
+ interface SlotSpec {
96
+ name: string;
97
+ /** Namespaced block types allowed in this slot. Omit for "any". */
98
+ allowedBlocks?: string[];
99
+ }
100
+ /** A reference from a block definition to a style control + the style key it edits. */
101
+ interface ControlRef {
102
+ /** Control type registered in the control registry, e.g. "spacing" | "color" | "dimension". */
103
+ control: string;
104
+ /** Style key this control writes (e.g. "padding", "backgroundColor"). */
105
+ styleKey: string;
106
+ label: string;
107
+ }
108
+ interface BlockRenderArgs<P = Record<string, unknown>> {
109
+ props: P;
110
+ node: BlockNode;
111
+ slots: Record<string, ReactNode>;
112
+ /** The scoped class the block MUST apply to its own root element (no wrapper div). */
113
+ className: string;
114
+ }
115
+ interface BlockDefinition<P = Record<string, unknown>> {
116
+ /** Namespaced type, e.g. "core/heading". */
117
+ type: string;
118
+ /** Schema version; drives per-block migrations. */
119
+ version: number;
120
+ label: string;
121
+ icon: string;
122
+ category: BlockCategory;
123
+ isContainer?: boolean;
124
+ slots?: SlotSpec[];
125
+ /**
126
+ * Content fields — reuse Nextly's field system so the inspector "Content" tab and
127
+ * dynamic-data binding are driven by one schema. Typed loosely here to avoid coupling
128
+ * core's compile to Nextly's full type surface; the admin narrows it to `FieldConfig[]`.
129
+ */
130
+ contentFields?: unknown[];
131
+ /** Style/visual controls (page-builder control registry) driving the Style/Responsive tabs. */
132
+ styleControls?: ControlRef[];
133
+ defaultProps: P;
134
+ defaultStyle?: ResponsiveStyle;
135
+ /** Prop keys that are translatable (i18n door; metadata only for MVP). */
136
+ localized?: string[];
137
+ /** Server-safe by default; may be a client component when interactive. */
138
+ render: (args: BlockRenderArgs<P>) => ReactNode;
139
+ /** Pure JSON→JSON upgrade for instances older than `version`. */
140
+ migrate?: (old: unknown, fromVersion: number) => {
141
+ props: P;
142
+ style?: ResponsiveStyle;
143
+ };
144
+ /** Extra per-instance validation beyond the core invariants. */
145
+ validate?: (node: BlockNode) => true | string;
146
+ }
147
+ interface ControlDef {
148
+ /** Control type key, e.g. "spacing" | "color" | "dimension" | "align" | "media" | "link". */
149
+ type: string;
150
+ /** The React control component (registered from the admin entry). Opaque to core. */
151
+ Component: unknown;
152
+ }
153
+ declare const MAX_DEPTH = 12;
154
+ declare const MAX_NODES = 5000;
155
+ declare const DEFAULT_SLOT = "default";
156
+
157
+ /**
158
+ * Open, string-keyed registries — the single extensibility seam (spec §7).
159
+ *
160
+ * The validator, renderer, and inspector all read the SAME block registry, so a
161
+ * third party adds a block with one `registerBlock()` call and no core edit.
162
+ * Types are namespaced (`core/heading`, `acme/pricing-table`) to stay collision-free.
163
+ */
164
+
165
+ interface BlockRegistry {
166
+ register(def: BlockDefinition): void;
167
+ get(type: string): BlockDefinition | undefined;
168
+ has(type: string): boolean;
169
+ all(): BlockDefinition[];
170
+ }
171
+ declare function createBlockRegistry(): BlockRegistry;
172
+ /** The default registry that built-in `core/*` blocks register into on import. */
173
+ declare const defaultBlockRegistry: BlockRegistry;
174
+ /**
175
+ * Declare a block and register it into the default registry. This is the Puck-style
176
+ * declarative model: one definition drives validator + renderer + inspector.
177
+ */
178
+ declare function defineBlock<P>(def: BlockDefinition<P>): BlockDefinition<P>;
179
+ interface ControlRegistry {
180
+ register(control: ControlDef): void;
181
+ get(type: string): ControlDef | undefined;
182
+ all(): ControlDef[];
183
+ }
184
+ declare function createControlRegistry(): ControlRegistry;
185
+ /** The default style/visual control registry (extensible — novel controls register here). */
186
+ declare const defaultControlRegistry: ControlRegistry;
187
+
188
+ interface BreakpointDef {
189
+ id: string;
190
+ maxWidth: number;
191
+ }
192
+ /** Desktop-first defaults (base = desktop; these override downward). */
193
+ declare const DEFAULT_BREAKPOINTS: BreakpointDef[];
194
+ interface CompileOptions {
195
+ breakpoints?: BreakpointDef[];
196
+ }
197
+ /**
198
+ * Default design-token palette (spec §8). Token refs (`{ token: "color.primary" }`)
199
+ * compile to `var(--nx-color-primary)`; these values back those vars out of the box so
200
+ * tokens work without extra config. A host can override via `PageRenderer`'s `tokens`
201
+ * prop (a fuller project-config surface is a future door).
202
+ */
203
+ declare const DEFAULT_TOKENS: Record<string, string>;
204
+ /** Emit the token palette as CSS custom properties on the page root. */
205
+ declare function compileTokensCss(rootClass: string, tokens?: Record<string, string>): string;
206
+ /** Deterministic, short, stable scoped class for a node id (FNV-1a → base36). */
207
+ declare function nodeClass(id: string): string;
208
+ declare function compileNodeCss(node: BlockNode, opts?: CompileOptions): string;
209
+ /** One <style> block worth of CSS for the whole document. */
210
+ declare function compileDocumentCss(doc: BlockDocument, opts?: CompileOptions): string;
211
+
212
+ export { defineBlock as A, type BlockNode as B, type CompileOptions as C, DEFAULT_BREAKPOINTS as D, nodeClass as E, MAX_DEPTH as M, type ResponsiveStyle as R, type SlotSpec as S, type TokenRef as T, type BlockRegistry as a, type BlockDocument as b, type Binding as c, type BlockCategory as d, type BlockDefinition as e, type BlockDocumentKind as f, type BlockRenderArgs as g, type BoxSides as h, type Breakpoint as i, type BreakpointDef as j, type ControlDef as k, type ControlRef as l, type ControlRegistry as m, DEFAULT_SLOT as n, DEFAULT_TOKENS as o, type DocumentVersion as p, MAX_NODES as q, type StyleScalar as r, type StyleValues as s, compileDocumentCss as t, compileNodeCss as u, compileTokensCss as v, createBlockRegistry as w, createControlRegistry as x, defaultBlockRegistry as y, defaultControlRegistry as z };
@@ -0,0 +1,412 @@
1
+ /*
2
+ * @nextlyhq/plugin-page-builder — admin editor styles.
3
+ *
4
+ * All colors come from the host admin's semantic tokens (`hsl(var(--token))`), so the
5
+ * editor looks native AND inherits dark mode automatically (the admin flips the tokens
6
+ * under `.adminapp.dark`). Everything is scoped under `.nx-pb-*` so it never leaks into
7
+ * the surrounding admin. Inline styles can't express :hover/:focus — these classes can.
8
+ */
9
+
10
+ /* ------------------------------------------------------------------ shell */
11
+ .nx-pb-editor {
12
+ display: flex;
13
+ flex-direction: column;
14
+ height: 78vh;
15
+ border: 1px solid hsl(var(--border));
16
+ border-radius: var(--radius);
17
+ overflow: hidden;
18
+ background: hsl(var(--background));
19
+ color: hsl(var(--foreground));
20
+ }
21
+
22
+ .nx-pb-toolbar {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 8px;
26
+ padding: 8px 10px;
27
+ border-bottom: 1px solid hsl(var(--border));
28
+ background: hsl(var(--card));
29
+ }
30
+
31
+ .nx-pb-body {
32
+ display: grid;
33
+ grid-template-columns: 232px 1fr 320px;
34
+ flex: 1;
35
+ min-height: 0;
36
+ }
37
+
38
+ .nx-pb-pane {
39
+ overflow: auto;
40
+ background: hsl(var(--sidebar-background));
41
+ }
42
+ .nx-pb-pane--left {
43
+ border-right: 1px solid hsl(var(--border));
44
+ }
45
+ .nx-pb-pane--right {
46
+ border-left: 1px solid hsl(var(--border));
47
+ }
48
+ .nx-pb-pane--center {
49
+ /* min-width:0 lets the 1fr canvas column shrink below the iframe's min-content instead
50
+ of forcing the 3-column grid wider than the editor — otherwise the editor's
51
+ overflow:hidden clips the right-hand inspector off-screen on narrower viewports. */
52
+ min-width: 0;
53
+ min-height: 0;
54
+ background: hsl(var(--muted));
55
+ }
56
+
57
+ .nx-pb-pane-header {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 6px;
61
+ padding: 10px 12px;
62
+ font-size: 11px;
63
+ font-weight: 700;
64
+ letter-spacing: 0.06em;
65
+ text-transform: uppercase;
66
+ color: hsl(var(--muted-foreground));
67
+ border-bottom: 1px solid hsl(var(--border));
68
+ position: sticky;
69
+ top: 0;
70
+ background: hsl(var(--sidebar-background));
71
+ z-index: 1;
72
+ }
73
+
74
+ /* ------------------------------------------------------------- segmented */
75
+ .nx-pb-seg {
76
+ display: inline-flex;
77
+ padding: 2px;
78
+ gap: 2px;
79
+ background: hsl(var(--muted));
80
+ border-radius: var(--radius);
81
+ }
82
+ .nx-pb-seg-btn {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ gap: 5px;
86
+ border: none;
87
+ cursor: pointer;
88
+ border-radius: var(--radius);
89
+ padding: 5px 10px;
90
+ font-size: 12px;
91
+ font-weight: 600;
92
+ color: hsl(var(--muted-foreground));
93
+ background: transparent;
94
+ transition:
95
+ background 0.12s ease,
96
+ color 0.12s ease;
97
+ }
98
+ .nx-pb-seg-btn:hover {
99
+ color: hsl(var(--foreground));
100
+ }
101
+ .nx-pb-seg-btn[aria-pressed="true"] {
102
+ color: hsl(var(--foreground));
103
+ background: hsl(var(--card));
104
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
105
+ }
106
+ .nx-pb-seg-btn:focus-visible {
107
+ outline: 2px solid hsl(var(--ring));
108
+ outline-offset: 1px;
109
+ }
110
+
111
+ /* ------------------------------------------------------------ generic btn */
112
+ .nx-pb-icon-btn {
113
+ display: inline-flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ gap: 6px;
117
+ border: 1px solid hsl(var(--border));
118
+ background: hsl(var(--card));
119
+ color: hsl(var(--foreground));
120
+ border-radius: var(--radius);
121
+ padding: 6px 10px;
122
+ font-size: 12px;
123
+ font-weight: 500;
124
+ cursor: pointer;
125
+ transition:
126
+ background 0.12s ease,
127
+ border-color 0.12s ease;
128
+ }
129
+ .nx-pb-icon-btn:hover {
130
+ background: hsl(var(--muted));
131
+ }
132
+ .nx-pb-icon-btn:focus-visible {
133
+ outline: 2px solid hsl(var(--ring));
134
+ outline-offset: 1px;
135
+ }
136
+ .nx-pb-icon-btn:disabled {
137
+ opacity: 0.45;
138
+ cursor: not-allowed;
139
+ }
140
+
141
+ /* --------------------------------------------------------------- library */
142
+ .nx-pb-lib-search {
143
+ position: relative;
144
+ padding: 10px 12px;
145
+ }
146
+ .nx-pb-lib-search svg {
147
+ position: absolute;
148
+ left: 22px;
149
+ top: 50%;
150
+ transform: translateY(-50%);
151
+ width: 15px;
152
+ height: 15px;
153
+ color: hsl(var(--muted-foreground));
154
+ pointer-events: none;
155
+ }
156
+ .nx-pb-lib-search input {
157
+ width: 100%;
158
+ padding: 7px 10px 7px 32px;
159
+ font-size: 13px;
160
+ color: hsl(var(--foreground));
161
+ background: hsl(var(--background));
162
+ border: 1px solid hsl(var(--input));
163
+ border-radius: var(--radius);
164
+ }
165
+ .nx-pb-lib-search input:focus-visible {
166
+ outline: 2px solid hsl(var(--ring));
167
+ outline-offset: 1px;
168
+ }
169
+
170
+ .nx-pb-lib-cat {
171
+ padding: 0 8px 8px;
172
+ }
173
+ .nx-pb-lib-cat-btn {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 6px;
177
+ width: 100%;
178
+ padding: 8px 6px;
179
+ border: none;
180
+ background: transparent;
181
+ cursor: pointer;
182
+ font-size: 11px;
183
+ font-weight: 700;
184
+ letter-spacing: 0.05em;
185
+ text-transform: uppercase;
186
+ color: hsl(var(--muted-foreground));
187
+ }
188
+ .nx-pb-lib-cat-btn svg {
189
+ width: 14px;
190
+ height: 14px;
191
+ transition: transform 0.12s ease;
192
+ }
193
+ .nx-pb-lib-grid {
194
+ display: grid;
195
+ grid-template-columns: 1fr 1fr;
196
+ gap: 6px;
197
+ }
198
+ .nx-pb-lib-item {
199
+ display: flex;
200
+ flex-direction: column;
201
+ align-items: center;
202
+ gap: 6px;
203
+ padding: 12px 6px 8px;
204
+ border: 1px solid hsl(var(--border));
205
+ border-radius: var(--radius);
206
+ background: hsl(var(--card));
207
+ color: hsl(var(--foreground));
208
+ cursor: grab;
209
+ text-align: center;
210
+ transition:
211
+ border-color 0.12s ease,
212
+ background 0.12s ease;
213
+ }
214
+ .nx-pb-lib-item:hover {
215
+ border-color: hsl(var(--ring));
216
+ background: hsl(var(--muted));
217
+ }
218
+ .nx-pb-lib-item[data-dragging="true"] {
219
+ opacity: 0.5;
220
+ }
221
+ .nx-pb-lib-item svg {
222
+ width: 20px;
223
+ height: 20px;
224
+ color: hsl(var(--muted-foreground));
225
+ }
226
+ .nx-pb-lib-item-label {
227
+ font-size: 12px;
228
+ font-weight: 500;
229
+ }
230
+ .nx-pb-lib-item-insert {
231
+ margin-top: 2px;
232
+ font-size: 10px;
233
+ color: hsl(var(--muted-foreground));
234
+ background: transparent;
235
+ border: none;
236
+ cursor: pointer;
237
+ text-decoration: underline;
238
+ }
239
+ .nx-pb-lib-item-insert:hover {
240
+ color: hsl(var(--foreground));
241
+ }
242
+ .nx-pb-lib-empty {
243
+ padding: 16px 12px;
244
+ font-size: 12px;
245
+ color: hsl(var(--muted-foreground));
246
+ text-align: center;
247
+ }
248
+
249
+ /* ------------------------------------------------------------- inspector */
250
+ .nx-pb-inspector {
251
+ display: flex;
252
+ flex-direction: column;
253
+ height: 100%;
254
+ }
255
+ .nx-pb-inspector-header {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 10px;
259
+ padding: 12px 12px 10px;
260
+ border-bottom: 1px solid hsl(var(--border));
261
+ }
262
+ .nx-pb-inspector-header .nx-pb-block-icon {
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ width: 30px;
267
+ height: 30px;
268
+ border-radius: var(--radius);
269
+ background: hsl(var(--muted));
270
+ color: hsl(var(--foreground));
271
+ flex: none;
272
+ }
273
+ .nx-pb-inspector-title {
274
+ font-weight: 700;
275
+ font-size: 14px;
276
+ color: hsl(var(--foreground));
277
+ }
278
+ .nx-pb-inspector-sub {
279
+ font-size: 11px;
280
+ color: hsl(var(--muted-foreground));
281
+ text-transform: capitalize;
282
+ }
283
+ .nx-pb-inspector-empty {
284
+ padding: 24px 16px;
285
+ font-size: 13px;
286
+ color: hsl(var(--muted-foreground));
287
+ text-align: center;
288
+ }
289
+
290
+ .nx-pb-section-label {
291
+ font-size: 10px;
292
+ font-weight: 700;
293
+ letter-spacing: 0.06em;
294
+ text-transform: uppercase;
295
+ color: hsl(var(--muted-foreground));
296
+ margin: 4px 0 8px;
297
+ }
298
+ .nx-pb-empty {
299
+ font-size: 12px;
300
+ color: hsl(var(--muted-foreground));
301
+ padding: 10px 0;
302
+ }
303
+ .nx-pb-control-label {
304
+ font-size: 12px;
305
+ color: hsl(var(--muted-foreground));
306
+ }
307
+ .nx-pb-device-badge {
308
+ display: inline-flex;
309
+ align-items: center;
310
+ gap: 4px;
311
+ font-size: 11px;
312
+ font-weight: 600;
313
+ padding: 2px 8px;
314
+ border-radius: var(--radius);
315
+ color: hsl(var(--muted-foreground));
316
+ }
317
+ .nx-pb-device-badge[data-active="true"] {
318
+ color: hsl(var(--primary-foreground));
319
+ background: hsl(var(--primary));
320
+ }
321
+
322
+ /* -------------------------------------------------------- align / choice */
323
+ .nx-pb-choice {
324
+ display: flex;
325
+ gap: 4px;
326
+ }
327
+ .nx-pb-choice-btn {
328
+ flex: 1;
329
+ padding: 6px 4px;
330
+ font-size: 12px;
331
+ cursor: pointer;
332
+ border: 1px solid hsl(var(--border));
333
+ border-radius: var(--radius);
334
+ background: hsl(var(--card));
335
+ color: hsl(var(--foreground));
336
+ transition: background 0.12s ease;
337
+ }
338
+ .nx-pb-choice-btn:hover {
339
+ background: hsl(var(--muted));
340
+ }
341
+ .nx-pb-choice-btn[aria-pressed="true"] {
342
+ background: hsl(var(--primary));
343
+ color: hsl(var(--primary-foreground));
344
+ border-color: hsl(var(--primary));
345
+ }
346
+
347
+ /* ----------------------------------------------------- spacing box-model */
348
+ .nx-pb-box {
349
+ display: flex;
350
+ align-items: flex-end;
351
+ gap: 4px;
352
+ }
353
+ .nx-pb-box-side {
354
+ flex: 1;
355
+ display: grid;
356
+ gap: 2px;
357
+ justify-items: center;
358
+ }
359
+ .nx-pb-box-side span {
360
+ font-size: 10px;
361
+ color: hsl(var(--muted-foreground));
362
+ }
363
+ .nx-pb-box-link {
364
+ display: inline-flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ width: 30px;
368
+ height: 30px;
369
+ border: 1px solid hsl(var(--border));
370
+ border-radius: var(--radius);
371
+ background: hsl(var(--card));
372
+ color: hsl(var(--muted-foreground));
373
+ cursor: pointer;
374
+ }
375
+ .nx-pb-box-link[aria-pressed="true"] {
376
+ color: hsl(var(--primary-foreground));
377
+ background: hsl(var(--primary));
378
+ border-color: hsl(var(--primary));
379
+ }
380
+
381
+ /* ------------------------------------------------------------- color ctl */
382
+ .nx-pb-color-row {
383
+ display: flex;
384
+ gap: 6px;
385
+ align-items: center;
386
+ }
387
+ .nx-pb-color-swatch {
388
+ width: 32px;
389
+ height: 32px;
390
+ padding: 0;
391
+ border: 1px solid hsl(var(--input));
392
+ border-radius: var(--radius);
393
+ background: none;
394
+ cursor: pointer;
395
+ }
396
+ .nx-pb-color-tokens {
397
+ display: flex;
398
+ gap: 5px;
399
+ flex-wrap: wrap;
400
+ margin-top: 6px;
401
+ }
402
+ .nx-pb-color-token {
403
+ width: 22px;
404
+ height: 22px;
405
+ border-radius: var(--radius);
406
+ cursor: pointer;
407
+ border: 1px solid hsl(var(--border));
408
+ }
409
+ .nx-pb-color-token[data-active="true"] {
410
+ outline: 2px solid hsl(var(--ring));
411
+ outline-offset: 1px;
412
+ }