@drawnagency/primitives 0.1.55 → 0.1.57
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/dist/auth/cookies.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/{chunk-24SUF2BC.js → chunk-ICLXLWQ5.js} +13 -74
- package/dist/chunk-NSCT3AMV.js +32 -0
- package/dist/{chunk-KDGYHU36.js → chunk-PRKUXM7E.js} +35 -10
- package/dist/{chunk-PUNXQK4M.js → chunk-PYWS3MOJ.js} +12 -2
- package/dist/chunk-TG43X7JO.js +123 -0
- package/dist/chunk-VKAGMEKE.js +90 -0
- package/dist/{chunk-B5VYSTPB.js → chunk-XTK4BR27.js} +1 -1
- package/dist/components/editor/ChildBlockWrapper.d.ts +19 -0
- package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -0
- package/dist/components/editor/ColSpanControl.d.ts +9 -0
- package/dist/components/editor/ColSpanControl.d.ts.map +1 -0
- package/dist/components/editor/SectionWrapper.d.ts +1 -1
- package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
- package/dist/components/editor/SettingsForm.d.ts +5 -1
- package/dist/components/editor/SettingsForm.d.ts.map +1 -1
- package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
- package/dist/components/primitives/IconPicker.d.ts +7 -1
- package/dist/components/primitives/IconPicker.d.ts.map +1 -1
- package/dist/components/sections/Container/Container.d.ts +20 -0
- package/dist/components/sections/Container/Container.d.ts.map +1 -0
- package/dist/components/sections/Container/ContainerSettingsForm.d.ts +17 -0
- package/dist/components/sections/Container/ContainerSettingsForm.d.ts.map +1 -0
- package/dist/components/sections/Container/index.d.ts +11 -0
- package/dist/components/sections/Container/index.d.ts.map +1 -0
- package/dist/components/sections/IconList/IconList.d.ts +1 -0
- package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
- package/dist/components/sections/IconList/IconListSettings.d.ts +3 -4
- package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
- package/dist/components/sections/IconList/index.d.ts +1 -0
- package/dist/components/sections/IconList/index.d.ts.map +1 -1
- package/dist/components/sections/Media/MediaBlock.d.ts +19 -0
- package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -0
- package/dist/components/sections/{MediaGrid → Media}/index.d.ts +15 -25
- package/dist/components/sections/Media/index.d.ts.map +1 -0
- package/dist/components/sections/Prose/index.d.ts.map +1 -1
- package/dist/components/sections/Spacer/Spacer.d.ts +2 -0
- package/dist/components/sections/Spacer/Spacer.d.ts.map +1 -0
- package/dist/components/sections/Spacer/index.d.ts +6 -0
- package/dist/components/sections/Spacer/index.d.ts.map +1 -0
- package/dist/components/sections/all-sections.d.ts +140 -0
- package/dist/components/sections/all-sections.d.ts.map +1 -0
- package/dist/components/sections/register-schemas.d.ts.map +1 -1
- package/dist/components/sections/register.d.ts.map +1 -1
- package/dist/components/shared/Tabs.d.ts +24 -0
- package/dist/components/shared/Tabs.d.ts.map +1 -0
- package/dist/components/shell/EditorShell.d.ts +2 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
- package/dist/components/shell/blockMoveDispatch.d.ts +21 -0
- package/dist/components/shell/blockMoveDispatch.d.ts.map +1 -0
- package/dist/hooks/useBlockDnd.d.ts +48 -0
- package/dist/hooks/useBlockDnd.d.ts.map +1 -0
- package/dist/hooks/useEditorPublish.d.ts +2 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/index.js +69 -48
- package/dist/lib/block-dnd.d.ts +42 -0
- package/dist/lib/block-dnd.d.ts.map +1 -0
- package/dist/lib/block-move.d.ts +31 -0
- package/dist/lib/block-move.d.ts.map +1 -0
- package/dist/lib/container-grid.d.ts +29 -0
- package/dist/lib/container-grid.d.ts.map +1 -0
- package/dist/lib/container-ops.d.ts +44 -0
- package/dist/lib/container-ops.d.ts.map +1 -0
- package/dist/lib/dexie.d.ts +12 -1
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +28 -3
- package/dist/lib/index.js +10 -7
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/migrate-sections-transform.d.ts +12 -0
- package/dist/lib/migrate-sections-transform.d.ts.map +1 -0
- package/dist/lib/migrate-sections-transform.js +6 -0
- package/dist/lib/registry.d.ts +39 -2
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/registry.js +26 -0
- package/dist/lib/sanitize.d.ts.map +1 -1
- package/dist/schemas/block.d.ts +20 -0
- package/dist/schemas/block.d.ts.map +1 -0
- package/dist/schemas/block.js +14 -0
- package/dist/schemas/index.js +10 -2
- package/dist/schemas/link.d.ts +7 -0
- package/dist/schemas/link.d.ts.map +1 -1
- package/dist/schemas/rich-text.d.ts +9 -0
- package/dist/schemas/rich-text.d.ts.map +1 -0
- package/dist/schemas/sections.d.ts +2 -0
- package/dist/schemas/sections.d.ts.map +1 -1
- package/dist/schemas/shared.d.ts +31 -0
- package/dist/schemas/shared.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/types.d.ts +13 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +13 -1
- package/src/auth/cookies.ts +6 -1
- package/src/components/brandguide/Colors.tsx +35 -33
- package/src/components/editor/ChildBlockWrapper.tsx +108 -0
- package/src/components/editor/ColSpanControl.tsx +56 -0
- package/src/components/editor/SectionWrapper.tsx +44 -20
- package/src/components/editor/SettingsForm.tsx +100 -73
- package/src/components/primitives/EditableGrid.tsx +40 -36
- package/src/components/primitives/IconPicker.tsx +116 -26
- package/src/components/sections/Container/Container.tsx +354 -0
- package/src/components/sections/Container/ContainerSettingsForm.tsx +113 -0
- package/src/components/sections/Container/index.tsx +51 -0
- package/src/components/sections/IconList/IconList.tsx +113 -43
- package/src/components/sections/IconList/IconListSettings.tsx +2 -2
- package/src/components/sections/IconList/index.tsx +1 -1
- package/src/components/sections/Media/MediaBlock.tsx +103 -0
- package/src/components/sections/Media/index.tsx +85 -0
- package/src/components/sections/Prose/index.tsx +1 -0
- package/src/components/sections/Spacer/Spacer.tsx +6 -0
- package/src/components/sections/Spacer/index.tsx +18 -0
- package/src/components/sections/all-sections.ts +40 -0
- package/src/components/sections/register-schemas.ts +13 -18
- package/src/components/sections/register.ts +3 -17
- package/src/components/shared/Tabs.tsx +63 -0
- package/src/components/shell/EditorShell.tsx +147 -18
- package/src/components/shell/SiteSettingsModal.tsx +41 -51
- package/src/components/shell/blockMoveDispatch.ts +40 -0
- package/src/hooks/useBlockDnd.ts +144 -0
- package/src/hooks/useEditorPublish.ts +17 -4
- package/src/lib/block-dnd.ts +58 -0
- package/src/lib/block-move.ts +236 -0
- package/src/lib/container-grid.ts +58 -0
- package/src/lib/container-ops.ts +159 -0
- package/src/lib/dexie.ts +47 -0
- package/src/lib/loader.ts +16 -4
- package/src/lib/migrate-sections-transform.ts +147 -0
- package/src/lib/registry.ts +48 -2
- package/src/lib/sanitize.ts +22 -1
- package/src/schemas/block.ts +40 -0
- package/src/schemas/link.ts +19 -1
- package/src/schemas/rich-text.ts +11 -0
- package/src/schemas/sections.ts +5 -1
- package/src/schemas/shared.ts +16 -0
- package/src/schemas/site-config.ts +3 -3
- package/src/storage/index.ts +1 -0
- package/src/storage/types.ts +17 -0
- package/dist/components/brandguide/DoDontList.d.ts +0 -16
- package/dist/components/brandguide/DoDontList.d.ts.map +0 -1
- package/dist/components/brandguide/DoDontMediaGrid.d.ts +0 -16
- package/dist/components/brandguide/DoDontMediaGrid.d.ts.map +0 -1
- package/dist/components/primitives/MediaSettingsForms.d.ts +0 -23
- package/dist/components/primitives/MediaSettingsForms.d.ts.map +0 -1
- package/dist/components/sections/DoDontList/index.d.ts +0 -21
- package/dist/components/sections/DoDontList/index.d.ts.map +0 -1
- package/dist/components/sections/DoDontMediaGrid/index.d.ts +0 -55
- package/dist/components/sections/DoDontMediaGrid/index.d.ts.map +0 -1
- package/dist/components/sections/MediaGrid/MediaGrid.d.ts +0 -17
- package/dist/components/sections/MediaGrid/MediaGrid.d.ts.map +0 -1
- package/dist/components/sections/MediaGrid/index.d.ts.map +0 -1
- package/dist/components/sections/SplitContent/SplitContent.d.ts +0 -14
- package/dist/components/sections/SplitContent/SplitContent.d.ts.map +0 -1
- package/dist/components/sections/SplitContent/index.d.ts +0 -13
- package/dist/components/sections/SplitContent/index.d.ts.map +0 -1
- package/src/components/brandguide/DoDontList.d.ts.map +0 -1
- package/src/components/brandguide/DoDontList.tsx +0 -67
- package/src/components/brandguide/DoDontMediaGrid.d.ts.map +0 -1
- package/src/components/brandguide/DoDontMediaGrid.tsx +0 -19
- package/src/components/primitives/MediaSettingsForms.tsx +0 -128
- package/src/components/sections/DoDontList/index.d.ts.map +0 -1
- package/src/components/sections/DoDontList/index.tsx +0 -45
- package/src/components/sections/DoDontMediaGrid/index.d.ts.map +0 -1
- package/src/components/sections/DoDontMediaGrid/index.tsx +0 -63
- package/src/components/sections/MediaGrid/MediaGrid.d.ts.map +0 -1
- package/src/components/sections/MediaGrid/MediaGrid.tsx +0 -239
- package/src/components/sections/MediaGrid/index.d.ts.map +0 -1
- package/src/components/sections/MediaGrid/index.tsx +0 -57
- package/src/components/sections/SplitContent/SplitContent.d.ts.map +0 -1
- package/src/components/sections/SplitContent/SplitContent.tsx +0 -84
- package/src/components/sections/SplitContent/index.d.ts.map +0 -1
- package/src/components/sections/SplitContent/index.tsx +0 -55
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearRegistry,
|
|
3
|
+
createRegistry,
|
|
4
|
+
defineSection,
|
|
5
|
+
getAllSchemas,
|
|
6
|
+
getAllSections,
|
|
7
|
+
getRichTextFields,
|
|
8
|
+
getSchema,
|
|
9
|
+
getSection,
|
|
10
|
+
registerRichText,
|
|
11
|
+
registerSchema,
|
|
12
|
+
registerSection
|
|
13
|
+
} from "../chunk-VKAGMEKE.js";
|
|
14
|
+
export {
|
|
15
|
+
clearRegistry,
|
|
16
|
+
createRegistry,
|
|
17
|
+
defineSection,
|
|
18
|
+
getAllSchemas,
|
|
19
|
+
getAllSections,
|
|
20
|
+
getRichTextFields,
|
|
21
|
+
getSchema,
|
|
22
|
+
getSection,
|
|
23
|
+
registerRichText,
|
|
24
|
+
registerSchema,
|
|
25
|
+
registerSection
|
|
26
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* A block's participation in its parent container's layout — distinct from its
|
|
4
|
+
* content. Optional, and absent at the page root (the root is an implicit single
|
|
5
|
+
* column). Intentionally NOT strict so future keys (rowSpan, alignSelf) are
|
|
6
|
+
* forward-compatible.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LayoutEnvelopeSchema: z.ZodObject<{
|
|
9
|
+
colSpan: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type LayoutEnvelope = z.infer<typeof LayoutEnvelopeSchema>;
|
|
12
|
+
/** v1 nesting cap: a top-level container (depth 2) may hold leaves but not containers. */
|
|
13
|
+
export declare const MAX_BLOCK_DEPTH = 2;
|
|
14
|
+
/** Content-agnostic: any block whose `content.children` is an array is a container. */
|
|
15
|
+
export declare function getBlockChildren(block: unknown): unknown[];
|
|
16
|
+
/** Depth of a block tree: a leaf is 1; a container is 1 + max(child depths). */
|
|
17
|
+
export declare function blockDepth(block: unknown): number;
|
|
18
|
+
/** Throws if the block tree is deeper than `max`. */
|
|
19
|
+
export declare function assertMaxDepth(block: unknown, max?: number): void;
|
|
20
|
+
//# sourceMappingURL=block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../src/schemas/block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;iBAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,0FAA0F;AAC1F,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CAM1D;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIjD;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,MAAwB,GAAG,IAAI,CAKlF"}
|
package/dist/schemas/index.js
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
LinkTargetSchema,
|
|
6
6
|
LinkValueSchema,
|
|
7
7
|
MediaGridOptionsSchema,
|
|
8
|
+
isSafeHref,
|
|
8
9
|
slugifyAudienceName
|
|
9
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-PYWS3MOJ.js";
|
|
10
11
|
import {
|
|
11
12
|
AudienceSchema,
|
|
12
13
|
RoleSchema,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
import {
|
|
17
18
|
ColorItemSchema,
|
|
18
19
|
ColorSpaceSchema,
|
|
20
|
+
FontNameSchema,
|
|
19
21
|
HexColorSchema,
|
|
20
22
|
IndexSchema,
|
|
21
23
|
MediaReferenceSchema,
|
|
@@ -23,18 +25,21 @@ import {
|
|
|
23
25
|
PageStatusSchema,
|
|
24
26
|
RESERVED_SLUGS,
|
|
25
27
|
SectionMetaSchema,
|
|
28
|
+
SingleMediaReferenceSchema,
|
|
26
29
|
SiteConfigSchema,
|
|
27
30
|
TextLineSchema,
|
|
28
31
|
getSectionContentSchema,
|
|
29
32
|
getSectionSchema,
|
|
30
33
|
normalizeSiteIndex
|
|
31
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-ICLXLWQ5.js";
|
|
32
35
|
import {
|
|
33
36
|
ImageManifestSchema,
|
|
34
37
|
MediaConfigSchema,
|
|
35
38
|
MediaItemSchema,
|
|
36
39
|
VariantSchema
|
|
37
40
|
} from "../chunk-DKOUFIP6.js";
|
|
41
|
+
import "../chunk-NSCT3AMV.js";
|
|
42
|
+
import "../chunk-VKAGMEKE.js";
|
|
38
43
|
export {
|
|
39
44
|
AudienceColorSchema,
|
|
40
45
|
AudienceNameSchema,
|
|
@@ -42,6 +47,7 @@ export {
|
|
|
42
47
|
ColorItemSchema,
|
|
43
48
|
ColorSpaceSchema,
|
|
44
49
|
DEFAULT_LINK,
|
|
50
|
+
FontNameSchema,
|
|
45
51
|
HexColorSchema,
|
|
46
52
|
ImageManifestSchema,
|
|
47
53
|
IndexSchema,
|
|
@@ -57,12 +63,14 @@ export {
|
|
|
57
63
|
RoleSchema,
|
|
58
64
|
SectionMetaSchema,
|
|
59
65
|
SessionSchema,
|
|
66
|
+
SingleMediaReferenceSchema,
|
|
60
67
|
SiteConfigSchema,
|
|
61
68
|
SiteUserSchema,
|
|
62
69
|
TextLineSchema,
|
|
63
70
|
VariantSchema,
|
|
64
71
|
getSectionContentSchema,
|
|
65
72
|
getSectionSchema,
|
|
73
|
+
isSafeHref,
|
|
66
74
|
normalizeSiteIndex,
|
|
67
75
|
slugifyAudienceName
|
|
68
76
|
};
|
package/dist/schemas/link.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ export declare const LinkTargetSchema: z.ZodEnum<{
|
|
|
3
3
|
_self: "_self";
|
|
4
4
|
_blank: "_blank";
|
|
5
5
|
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Block dangerous href schemes (javascript:, data:, vbscript:) and protocol-
|
|
8
|
+
* relative URLs at the schema boundary. Allows empty (unset), relative paths /
|
|
9
|
+
* fragments / queries, and http(s)/mailto absolute URLs. Links are not walked by
|
|
10
|
+
* the recursive sanitizer, so this is the only XSS guard for stored link hrefs.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isSafeHref(href: string): boolean;
|
|
6
13
|
export declare const LinkValueSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
7
14
|
kind: z.ZodLiteral<"external">;
|
|
8
15
|
href: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/schemas/link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,gBAAgB;;;EAA8B,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;2BAQ1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,YAAY,EAAE,SAA2D,CAAC"}
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/schemas/link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,gBAAgB;;;EAA8B,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUhD;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;2BAQ1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,YAAY,EAAE,SAA2D,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Marks a schema field as HTML rich text. Readability marker only — the runtime
|
|
4
|
+
* source of truth for sanitization is each block definition's `richTextFields`
|
|
5
|
+
* list (see registry). Kept as a helper so schemas read self-documentingly
|
|
6
|
+
* (`body: richText()`) and a future Zod version could make it introspectable.
|
|
7
|
+
*/
|
|
8
|
+
export declare function richText(): z.ZodString;
|
|
9
|
+
//# sourceMappingURL=rich-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-text.d.ts","sourceRoot":"","sources":["../../src/schemas/rich-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,wBAAgB,QAAQ,gBAEvB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type LayoutEnvelope } from "./block";
|
|
2
3
|
export interface SectionContent<C = Record<string, unknown>> {
|
|
3
4
|
type: string;
|
|
4
5
|
content: C;
|
|
@@ -6,6 +7,7 @@ export interface SectionContent<C = Record<string, unknown>> {
|
|
|
6
7
|
}
|
|
7
8
|
export interface Section<C = Record<string, unknown>> extends SectionContent<C> {
|
|
8
9
|
id: string;
|
|
10
|
+
layout?: LayoutEnvelope;
|
|
9
11
|
}
|
|
10
12
|
export declare function getSectionContentSchema(): z.ZodType<SectionContent>;
|
|
11
13
|
export declare function getSectionSchema(): z.ZodType<Section>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../src/schemas/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../src/schemas/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAEpE,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,wBAAgB,uBAAuB,IAAI,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAMnE;AAED,wBAAgB,gBAAgB,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAIrD"}
|
package/dist/schemas/shared.d.ts
CHANGED
|
@@ -77,7 +77,38 @@ export declare const MediaReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
77
77
|
linkText: z.ZodOptional<z.ZodString>;
|
|
78
78
|
}, z.core.$strip>], "type">;
|
|
79
79
|
export type MediaReference = z.infer<typeof MediaReferenceSchema>;
|
|
80
|
+
/** A single image or video reference — for the standalone `media` block
|
|
81
|
+
* (excludes the grid-only doDontImage/linkedImage variants). */
|
|
82
|
+
export declare const SingleMediaReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
83
|
+
imageId: z.ZodDefault<z.ZodString>;
|
|
84
|
+
caption: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
85
|
+
background: z.ZodOptional<z.ZodString>;
|
|
86
|
+
invertFrom: z.ZodOptional<z.ZodString>;
|
|
87
|
+
border: z.ZodOptional<z.ZodBoolean>;
|
|
88
|
+
objectFit: z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
cover: "cover";
|
|
90
|
+
contain: "contain";
|
|
91
|
+
}>>;
|
|
92
|
+
type: z.ZodLiteral<"image">;
|
|
93
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
94
|
+
imageId: z.ZodDefault<z.ZodString>;
|
|
95
|
+
caption: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
96
|
+
background: z.ZodOptional<z.ZodString>;
|
|
97
|
+
invertFrom: z.ZodOptional<z.ZodString>;
|
|
98
|
+
border: z.ZodOptional<z.ZodBoolean>;
|
|
99
|
+
objectFit: z.ZodOptional<z.ZodEnum<{
|
|
100
|
+
cover: "cover";
|
|
101
|
+
contain: "contain";
|
|
102
|
+
}>>;
|
|
103
|
+
type: z.ZodLiteral<"video">;
|
|
104
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
105
|
+
autoplay: z.ZodOptional<z.ZodBoolean>;
|
|
106
|
+
loop: z.ZodOptional<z.ZodBoolean>;
|
|
107
|
+
muted: z.ZodOptional<z.ZodBoolean>;
|
|
108
|
+
}, z.core.$strip>], "type">;
|
|
109
|
+
export type SingleMediaReference = z.infer<typeof SingleMediaReferenceSchema>;
|
|
80
110
|
export declare const HexColorSchema: z.ZodString;
|
|
111
|
+
export declare const FontNameSchema: z.ZodString;
|
|
81
112
|
export declare const ColorSpaceSchema: z.ZodObject<{
|
|
82
113
|
hex: z.ZodOptional<z.ZodString>;
|
|
83
114
|
rgb: z.ZodOptional<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/schemas/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;2BAKzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAoCtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/schemas/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;2BAKzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAoCtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;iEACiE;AACjE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;2BAAqD,CAAC;AAE7F,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAG9E,eAAO,MAAM,cAAc,aAEiC,CAAC;AAO7D,eAAO,MAAM,cAAc,aAG6C,CAAC;AAEzE,eAAO,MAAM,gBAAgB;;;;;iBAQ5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;iBAG1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by `writeFiles` when `options.baseVersion` no longer matches the
|
|
3
|
+
* draft's current version — i.e. another editor (or the same user on another
|
|
4
|
+
* device) committed since this editor loaded. Callers should surface a 409 and
|
|
5
|
+
* prompt a reload rather than clobbering the concurrent work (last-write-wins).
|
|
6
|
+
*/
|
|
7
|
+
export declare class StorageConflictError extends Error {
|
|
8
|
+
readonly currentVersion: string | null;
|
|
9
|
+
constructor(currentVersion: string | null);
|
|
10
|
+
}
|
|
1
11
|
export interface StorageProvider {
|
|
2
|
-
writeFiles(files: FileWrite[], message: string
|
|
12
|
+
writeFiles(files: FileWrite[], message: string, options?: {
|
|
13
|
+
baseVersion?: string | null;
|
|
14
|
+
}): Promise<{
|
|
3
15
|
version: string;
|
|
4
16
|
}>;
|
|
5
17
|
readFile(path: string, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drawnagency/primitives",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./package.json": "./package.json",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/schemas/auth.d.ts",
|
|
17
17
|
"default": "./dist/schemas/auth.js"
|
|
18
18
|
},
|
|
19
|
+
"./schemas/block": {
|
|
20
|
+
"types": "./dist/schemas/block.d.ts",
|
|
21
|
+
"default": "./dist/schemas/block.js"
|
|
22
|
+
},
|
|
19
23
|
"./schemas/link": {
|
|
20
24
|
"types": "./dist/schemas/link.d.ts",
|
|
21
25
|
"default": "./dist/schemas/link.js"
|
|
@@ -32,6 +36,14 @@
|
|
|
32
36
|
"types": "./dist/lib/env.d.ts",
|
|
33
37
|
"default": "./dist/lib/env.js"
|
|
34
38
|
},
|
|
39
|
+
"./lib/registry": {
|
|
40
|
+
"types": "./dist/lib/registry.d.ts",
|
|
41
|
+
"default": "./dist/lib/registry.js"
|
|
42
|
+
},
|
|
43
|
+
"./lib/migrate-sections-transform": {
|
|
44
|
+
"types": "./dist/lib/migrate-sections-transform.d.ts",
|
|
45
|
+
"default": "./dist/lib/migrate-sections-transform.js"
|
|
46
|
+
},
|
|
35
47
|
"./auth": {
|
|
36
48
|
"types": "./dist/auth/index.d.ts",
|
|
37
49
|
"default": "./dist/auth/index.js"
|
package/src/auth/cookies.ts
CHANGED
|
@@ -5,7 +5,12 @@ import { requireSessionSecret } from "./security";
|
|
|
5
5
|
export const SESSION_COOKIE = "bp-session";
|
|
6
6
|
export const AUDIENCE_COOKIE = "bp-audience";
|
|
7
7
|
export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24;
|
|
8
|
-
|
|
8
|
+
// 24h, matching the editor session. A viewer audience JWT carries no rotation
|
|
9
|
+
// token, so rotating a leaked viewer password can't actively revoke an
|
|
10
|
+
// outstanding cookie — a short lifetime bounds the leak window instead of the
|
|
11
|
+
// previous 30 days. (A `token_version` claim checked at verify time would give
|
|
12
|
+
// immediate revocation without forcing daily re-auth; see backlog.)
|
|
13
|
+
export const AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24;
|
|
9
14
|
|
|
10
15
|
export async function signSessionToken(session: Session): Promise<string> {
|
|
11
16
|
return new jose.SignJWT({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import { Eye, EyeOff } from "lucide-react";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
|
-
import {
|
|
4
|
+
import { containerGridClass } from "../../lib/container-grid";
|
|
5
5
|
import { EditableGrid } from "../primitives/EditableGrid";
|
|
6
6
|
import { EditablePlainText } from "../primitives/EditablePlainText";
|
|
7
7
|
import type { ReactNode } from "react";
|
|
@@ -119,40 +119,42 @@ function ColorsView({
|
|
|
119
119
|
</div>
|
|
120
120
|
)}
|
|
121
121
|
|
|
122
|
-
<div className=
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<div className=
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
</div>
|
|
139
|
-
{expanded && (
|
|
140
|
-
<div className="space-y-1 bg-base-accent p-3 text-sm">
|
|
141
|
-
{color.spaces.map((space, j) =>
|
|
142
|
-
Object.entries(space).map(([key, value]) =>
|
|
143
|
-
value ? (
|
|
144
|
-
<button key={`${j}-${key}`} onClick={() => handleCopy(value, i)} className="cursor-pointer flex w-full justify-between hover:text-primary">
|
|
145
|
-
<span className="font-medium uppercase">{key}</span>
|
|
146
|
-
<span>{value}</span>
|
|
147
|
-
</button>
|
|
148
|
-
) : null
|
|
149
|
-
)
|
|
122
|
+
<div className="@container">
|
|
123
|
+
<div className={cn("grid gap-4", containerGridClass[columns] || containerGridClass[3])}>
|
|
124
|
+
{colors.map((color, i) => {
|
|
125
|
+
const hex = color.spaces[0]?.hex;
|
|
126
|
+
const contrast = getContrastClass(hex);
|
|
127
|
+
return (
|
|
128
|
+
<div key={i} className="overflow-hidden rounded-md border border-base-200">
|
|
129
|
+
<div className={cn("relative flex min-h-[80px] items-end p-3", contrast)} style={{ backgroundColor: hex || "#ccc" }}>
|
|
130
|
+
{color.name && <span className="text-sm font-bold">{color.name}</span>}
|
|
131
|
+
{copiedIndex === i && (
|
|
132
|
+
<span className={cn(
|
|
133
|
+
"absolute top-2 right-2 rounded-full px-2.5 py-0.5 text-xs font-medium",
|
|
134
|
+
getContrastClass(hex) === "text-black" ? "bg-black/15 text-black" : "bg-white/25 text-white",
|
|
135
|
+
)}>
|
|
136
|
+
Copied!
|
|
137
|
+
</span>
|
|
150
138
|
)}
|
|
151
139
|
</div>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
{expanded && (
|
|
141
|
+
<div className="space-y-1 bg-base-accent p-3 text-sm">
|
|
142
|
+
{color.spaces.map((space, j) =>
|
|
143
|
+
Object.entries(space).map(([key, value]) =>
|
|
144
|
+
value ? (
|
|
145
|
+
<button key={`${j}-${key}`} onClick={() => handleCopy(value, i)} className="cursor-pointer flex w-full justify-between hover:text-primary">
|
|
146
|
+
<span className="font-medium uppercase">{key}</span>
|
|
147
|
+
<span>{value}</span>
|
|
148
|
+
</button>
|
|
149
|
+
) : null
|
|
150
|
+
)
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
})}
|
|
157
|
+
</div>
|
|
156
158
|
</div>
|
|
157
159
|
</div>
|
|
158
160
|
);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { forwardRef, type ReactNode, type Ref } from "react";
|
|
2
|
+
import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
|
|
3
|
+
import { CopyPlus } from "lucide-react";
|
|
4
|
+
import { DragHandle } from "./DragHandle";
|
|
5
|
+
import { DeleteButton } from "./DeleteButton";
|
|
6
|
+
import { SettingsButton } from "./SettingsButton";
|
|
7
|
+
import { ColSpanControl } from "./ColSpanControl";
|
|
8
|
+
import { cn } from "../../lib/cn";
|
|
9
|
+
|
|
10
|
+
export interface ChildBlockWrapperProps {
|
|
11
|
+
label: string;
|
|
12
|
+
columns: number;
|
|
13
|
+
colSpan: number;
|
|
14
|
+
closestEdge: Edge | null;
|
|
15
|
+
isDragging: boolean;
|
|
16
|
+
hasSettings: boolean;
|
|
17
|
+
/** Forwarded to the drag handle button (pragmatic-dnd dragHandle target). */
|
|
18
|
+
dragHandleRef?: Ref<HTMLButtonElement>;
|
|
19
|
+
onDelete: () => void;
|
|
20
|
+
onDuplicate: () => void;
|
|
21
|
+
onColSpanChange: (span: number) => void;
|
|
22
|
+
onOpenSettings?: () => void;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ChildBlockWrapper = forwardRef<HTMLDivElement, ChildBlockWrapperProps>(
|
|
27
|
+
function ChildBlockWrapper(
|
|
28
|
+
{
|
|
29
|
+
label,
|
|
30
|
+
columns,
|
|
31
|
+
colSpan,
|
|
32
|
+
closestEdge,
|
|
33
|
+
isDragging,
|
|
34
|
+
hasSettings,
|
|
35
|
+
dragHandleRef,
|
|
36
|
+
onDelete,
|
|
37
|
+
onDuplicate,
|
|
38
|
+
onColSpanChange,
|
|
39
|
+
onOpenSettings,
|
|
40
|
+
children,
|
|
41
|
+
},
|
|
42
|
+
ref,
|
|
43
|
+
) {
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={cn(
|
|
48
|
+
"group/childblock relative h-full rounded-sm",
|
|
49
|
+
isDragging && "opacity-50",
|
|
50
|
+
"hover:outline hover:outline-2 hover:outline-primary/30",
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{/* Controls — vertical rail in the cell's LEFT gutter. Revealed on hover OR
|
|
54
|
+
focus-within, so an open col-span popover (which traps focus) keeps the rail
|
|
55
|
+
visible even when the pointer moves off the block to reach an option. */}
|
|
56
|
+
<div className="absolute top-0 left-0 -translate-x-full z-20 flex flex-col items-center gap-1 opacity-0 transition-opacity group-hover/childblock:opacity-100 group-focus-within/childblock:opacity-100 pointer-events-none">
|
|
57
|
+
<span className="sr-only">{label}</span>
|
|
58
|
+
<div className="pointer-events-auto">
|
|
59
|
+
<DragHandle ref={dragHandleRef} />
|
|
60
|
+
</div>
|
|
61
|
+
<div className="pointer-events-auto">
|
|
62
|
+
<ColSpanControl colSpan={colSpan} columns={columns} onChange={onColSpanChange} />
|
|
63
|
+
</div>
|
|
64
|
+
{hasSettings && onOpenSettings && (
|
|
65
|
+
<div className="pointer-events-auto">
|
|
66
|
+
<SettingsButton onClick={onOpenSettings} />
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
<div className="pointer-events-auto">
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
aria-label="Duplicate block"
|
|
73
|
+
onClick={onDuplicate}
|
|
74
|
+
className="cursor-pointer rounded p-1 text-base-contrast-light hover:text-primary"
|
|
75
|
+
>
|
|
76
|
+
<CopyPlus size={16} />
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="pointer-events-auto">
|
|
80
|
+
<DeleteButton onDelete={onDelete} />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Drop-edge indicators */}
|
|
85
|
+
{closestEdge === "left" && (
|
|
86
|
+
<div
|
|
87
|
+
data-drop-edge="left"
|
|
88
|
+
className="absolute bottom-0 left-0 top-0 z-10 w-0.5 -translate-x-1 bg-primary"
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
{closestEdge === "right" && (
|
|
92
|
+
<div
|
|
93
|
+
data-drop-edge="right"
|
|
94
|
+
className="absolute bottom-0 right-0 top-0 z-10 w-0.5 translate-x-1 bg-primary"
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
{closestEdge === "top" && (
|
|
98
|
+
<div data-drop-edge="top" className="absolute left-0 right-0 top-0 z-10 h-0.5 -translate-y-1 bg-primary" />
|
|
99
|
+
)}
|
|
100
|
+
{closestEdge === "bottom" && (
|
|
101
|
+
<div data-drop-edge="bottom" className="absolute left-0 right-0 bottom-0 z-10 h-0.5 translate-y-1 bg-primary" />
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
{children}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { Columns3 } from "lucide-react";
|
|
3
|
+
import { Popover } from "../shared/Popover";
|
|
4
|
+
import { PopoverItem } from "../shared/PopoverItem";
|
|
5
|
+
import { cn } from "../../lib/cn";
|
|
6
|
+
|
|
7
|
+
interface ColSpanControlProps {
|
|
8
|
+
colSpan: number;
|
|
9
|
+
columns: number;
|
|
10
|
+
onChange: (span: number) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Contextual col-span picker — only meaningful inside a multi-column container. */
|
|
14
|
+
export function ColSpanControl({ colSpan, columns, onChange }: ColSpanControlProps) {
|
|
15
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
16
|
+
const [open, setOpen] = useState(false);
|
|
17
|
+
|
|
18
|
+
if (columns <= 1) return null;
|
|
19
|
+
|
|
20
|
+
const current = Math.min(Math.max(colSpan, 1), columns);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="relative">
|
|
24
|
+
<button
|
|
25
|
+
ref={buttonRef}
|
|
26
|
+
type="button"
|
|
27
|
+
aria-label="Column span"
|
|
28
|
+
aria-haspopup="true"
|
|
29
|
+
aria-expanded={open}
|
|
30
|
+
onClick={() => setOpen((v) => !v)}
|
|
31
|
+
className="pointer-events-auto inline-flex cursor-pointer items-center gap-1 rounded p-1 text-xs text-base-contrast-light hover:text-primary"
|
|
32
|
+
>
|
|
33
|
+
<Columns3 size={14} />
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<Popover isOpen={open} onClose={() => setOpen(false)} anchorRef={buttonRef} className="min-w-28">
|
|
37
|
+
<ul role="list" className="py-1">
|
|
38
|
+
{Array.from({ length: columns }, (_, i) => i + 1).map((span) => (
|
|
39
|
+
<li key={span}>
|
|
40
|
+
<PopoverItem
|
|
41
|
+
aria-current={span === current}
|
|
42
|
+
onClick={() => {
|
|
43
|
+
onChange(span);
|
|
44
|
+
setOpen(false);
|
|
45
|
+
}}
|
|
46
|
+
className={cn(span === current && "font-semibold text-primary")}
|
|
47
|
+
>
|
|
48
|
+
Span {span}
|
|
49
|
+
</PopoverItem>
|
|
50
|
+
</li>
|
|
51
|
+
))}
|
|
52
|
+
</ul>
|
|
53
|
+
</Popover>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|