Skip to content

Commit d24bfb7

Browse files
committed
WEB-111 Add basic components
1 parent 52b8f75 commit d24bfb7

8 files changed

Lines changed: 750 additions & 0 deletions

File tree

src/components/puck/button.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { ComponentConfig } from "@puckeditor/core";
2+
import { Button } from "@/components/ui/button";
3+
import { selectFrom } from "@/lib/puck/tokens";
4+
5+
const variantOptions = [
6+
"default",
7+
"secondary",
8+
"outline",
9+
"ghost",
10+
"destructive",
11+
"link",
12+
] as const;
13+
type Variant = (typeof variantOptions)[number];
14+
15+
const sizeOptions = ["sm", "default", "lg"] as const;
16+
type Size = (typeof sizeOptions)[number];
17+
18+
type PuckButtonProps = {
19+
label: string;
20+
href: string;
21+
variant: Variant;
22+
size: Size;
23+
};
24+
25+
export const PuckButton: ComponentConfig<PuckButtonProps> = {
26+
label: "Button",
27+
fields: {
28+
label: { type: "text", label: "Label" },
29+
href: { type: "text", label: "Link URL" },
30+
variant: selectFrom(variantOptions, "Variant"),
31+
size: {
32+
type: "select",
33+
label: "Size",
34+
options: [
35+
{ label: "Small", value: "sm" },
36+
{ label: "Default", value: "default" },
37+
{ label: "Large", value: "lg" },
38+
],
39+
},
40+
},
41+
defaultProps: {
42+
label: "Click me",
43+
href: "",
44+
variant: "default",
45+
size: "default",
46+
},
47+
render: ({ label, href, variant, size }) => {
48+
const button = (
49+
<Button variant={variant} size={size}>
50+
{label}
51+
</Button>
52+
);
53+
54+
if (href) {
55+
return <a href={href}>{button}</a>;
56+
}
57+
58+
return button;
59+
},
60+
};

src/components/puck/columns.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type { ComponentConfig } from "@puckeditor/core";
2+
import { cva } from "class-variance-authority";
3+
import { cn } from "@/lib/utils";
4+
import { fields, gapVariants, type Spacing } from "@/lib/puck/tokens";
5+
6+
const columnValues = ["2", "3", "4", "5", "6"] as const;
7+
type ColumnCount = (typeof columnValues)[number];
8+
9+
const columnsVariants = cva("grid", {
10+
variants: {
11+
columns: {
12+
"2": "grid-cols-1 md:grid-cols-2",
13+
"3": "grid-cols-1 md:grid-cols-3",
14+
"4": "grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
15+
"5": "grid-cols-1 md:grid-cols-3 lg:grid-cols-5",
16+
"6": "grid-cols-1 md:grid-cols-3 lg:grid-cols-6",
17+
},
18+
gap: gapVariants,
19+
},
20+
defaultVariants: {
21+
columns: "2",
22+
gap: "md",
23+
},
24+
});
25+
26+
type ColumnsProps = {
27+
column1: any;
28+
column2: any;
29+
column3: any;
30+
column4: any;
31+
column5: any;
32+
column6: any;
33+
columns: ColumnCount;
34+
gap: Spacing;
35+
};
36+
37+
const slotField = { type: "slot" as const };
38+
39+
export const Columns: ComponentConfig<ColumnsProps> = {
40+
label: "Columns",
41+
fields: {
42+
column1: slotField,
43+
column2: slotField,
44+
column3: slotField,
45+
column4: slotField,
46+
column5: slotField,
47+
column6: slotField,
48+
columns: {
49+
type: "select",
50+
label: "Columns",
51+
options: columnValues.map((v) => ({ label: v, value: v })),
52+
},
53+
gap: fields.gap(),
54+
},
55+
defaultProps: {
56+
columns: "2",
57+
gap: "md",
58+
} as ColumnsProps,
59+
resolveFields: (data) => {
60+
const f = { ...Columns.fields! };
61+
const colCount = parseInt(data.props.columns ?? "2", 10);
62+
63+
f.column1 = { ...f.column1, visible: colCount >= 1 };
64+
f.column2 = { ...f.column2, visible: colCount >= 2 };
65+
f.column3 = { ...f.column3, visible: colCount >= 3 };
66+
f.column4 = { ...f.column4, visible: colCount >= 4 };
67+
f.column5 = { ...f.column5, visible: colCount >= 5 };
68+
f.column6 = { ...f.column6, visible: colCount >= 6 };
69+
70+
return f;
71+
},
72+
render: ({
73+
column1: Column1,
74+
column2: Column2,
75+
column3: Column3,
76+
column4: Column4,
77+
column5: Column5,
78+
column6: Column6,
79+
columns,
80+
gap,
81+
}) => {
82+
const colCount = parseInt(columns ?? "2", 10);
83+
const columnSlots = [Column1, Column2, Column3, Column4, Column5, Column6];
84+
85+
return (
86+
<div className={cn(columnsVariants({ columns, gap }))}>
87+
{columnSlots
88+
.slice(0, colCount)
89+
.map((Col, i) => Col && <Col key={i} />)}
90+
</div>
91+
);
92+
},
93+
};

src/components/puck/container.tsx

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import type { ComponentConfig } from "@puckeditor/core";
2+
import { cva } from "class-variance-authority";
3+
import { cn } from "@/lib/utils";
4+
import {
5+
fields,
6+
paddingVariants,
7+
gapVariants,
8+
alignVariants,
9+
bgColorVariants,
10+
textColorVariants,
11+
radiusVariants,
12+
shadowVariants,
13+
type Spacing,
14+
type Color,
15+
type Radius,
16+
type Shadow,
17+
type Align,
18+
} from "@/lib/puck/tokens";
19+
20+
const tags = [
21+
"div",
22+
"section",
23+
"article",
24+
"header",
25+
"footer",
26+
"aside",
27+
"main",
28+
"nav",
29+
] as const;
30+
31+
type Tag = (typeof tags)[number];
32+
33+
const layoutValues = ["stack", "row"] as const;
34+
type Layout = (typeof layoutValues)[number];
35+
36+
const justifyValues = ["start", "center", "end", "between"] as const;
37+
type Justify = (typeof justifyValues)[number];
38+
39+
const widthValues = [
40+
"full",
41+
"prose",
42+
"screen-sm",
43+
"screen-md",
44+
"screen-lg",
45+
"screen-xl",
46+
] as const;
47+
type Width = (typeof widthValues)[number];
48+
49+
const containerVariants = cva("", {
50+
variants: {
51+
layout: {
52+
stack: "flex flex-col",
53+
row: "flex flex-row flex-wrap",
54+
},
55+
padding: paddingVariants,
56+
gap: gapVariants,
57+
align: alignVariants,
58+
justify: {
59+
start: "justify-start",
60+
center: "justify-center",
61+
end: "justify-end",
62+
between: "justify-between",
63+
},
64+
width: {
65+
full: "w-full",
66+
prose: "w-full max-w-prose",
67+
"screen-sm": "w-full max-w-screen-sm",
68+
"screen-md": "w-full max-w-screen-md",
69+
"screen-lg": "w-full max-w-screen-lg",
70+
"screen-xl": "w-full max-w-screen-xl",
71+
},
72+
bgColor: bgColorVariants,
73+
textColor: textColorVariants,
74+
radius: radiusVariants,
75+
shadow: shadowVariants,
76+
},
77+
defaultVariants: {
78+
layout: "stack",
79+
padding: "md",
80+
gap: "md",
81+
align: "left",
82+
justify: "start",
83+
width: "full",
84+
bgColor: "background",
85+
textColor: "foreground",
86+
radius: "none",
87+
shadow: "none",
88+
},
89+
});
90+
91+
type ContainerProps = {
92+
content: any;
93+
layout: Layout;
94+
padding: Spacing;
95+
gap: Spacing;
96+
align: Align;
97+
justify: Justify;
98+
width: Width;
99+
bgColor: Color;
100+
textColor: Color;
101+
radius: Radius;
102+
shadow: Shadow;
103+
tag: Tag;
104+
};
105+
106+
export const Container: ComponentConfig<ContainerProps> = {
107+
label: "Container",
108+
fields: {
109+
content: { type: "slot" },
110+
layout: {
111+
type: "radio",
112+
label: "Layout",
113+
options: [
114+
{ label: "Stack", value: "stack" },
115+
{ label: "Row", value: "row" },
116+
],
117+
},
118+
padding: fields.padding(),
119+
gap: fields.gap(),
120+
align: fields.align(),
121+
justify: {
122+
type: "radio",
123+
label: "Justify",
124+
options: [
125+
{ label: "Start", value: "start" },
126+
{ label: "Center", value: "center" },
127+
{ label: "End", value: "end" },
128+
{ label: "Between", value: "between" },
129+
],
130+
},
131+
width: {
132+
type: "select",
133+
label: "Max width",
134+
options: [
135+
{ label: "Full", value: "full" },
136+
{ label: "Prose", value: "prose" },
137+
{ label: "Small", value: "screen-sm" },
138+
{ label: "Medium", value: "screen-md" },
139+
{ label: "Large", value: "screen-lg" },
140+
{ label: "X-Large", value: "screen-xl" },
141+
],
142+
},
143+
bgColor: fields.bgColor(),
144+
textColor: fields.textColor(),
145+
radius: fields.radius(),
146+
shadow: fields.shadow(),
147+
tag: {
148+
type: "select",
149+
label: "HTML tag",
150+
options: tags.map((t) => ({ label: `<${t}>`, value: t })),
151+
},
152+
},
153+
defaultProps: {
154+
layout: "stack",
155+
padding: "md",
156+
gap: "md",
157+
align: "left",
158+
justify: "start",
159+
width: "full",
160+
bgColor: "background",
161+
textColor: "foreground",
162+
radius: "none",
163+
shadow: "none",
164+
tag: "div",
165+
} as ContainerProps,
166+
render: ({
167+
content: Content,
168+
layout,
169+
padding,
170+
gap,
171+
align,
172+
justify,
173+
width,
174+
bgColor,
175+
textColor,
176+
radius,
177+
shadow,
178+
tag,
179+
}) => {
180+
const Tag = tag as React.ElementType;
181+
182+
return (
183+
<Tag
184+
className={cn(
185+
containerVariants({
186+
layout,
187+
padding,
188+
gap,
189+
align,
190+
justify,
191+
width,
192+
bgColor,
193+
textColor,
194+
radius,
195+
shadow,
196+
}),
197+
)}
198+
>
199+
{Content && <Content />}
200+
</Tag>
201+
);
202+
},
203+
};

0 commit comments

Comments
 (0)