From 830828435ac8502c6a44388f068528e726178724 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Thu, 2 Jan 2025 10:46:14 +0700 Subject: [PATCH] refactor: rewrite collapsible and accordion templates with jsx (#4675) Css variables in templates are easier to understand now. --- .../src/accordion.template.tsx | 116 +++++++++++ .../src/accordion.ws.ts | 189 ------------------ .../src/collapsible.template.tsx | 22 ++ .../src/collapsible.ws.ts | 51 ----- .../src/templates.ts | 2 + 5 files changed, 140 insertions(+), 240 deletions(-) create mode 100644 packages/sdk-components-react-radix/src/accordion.template.tsx create mode 100644 packages/sdk-components-react-radix/src/collapsible.template.tsx diff --git a/packages/sdk-components-react-radix/src/accordion.template.tsx b/packages/sdk-components-react-radix/src/accordion.template.tsx new file mode 100644 index 000000000000..e6adca8187b7 --- /dev/null +++ b/packages/sdk-components-react-radix/src/accordion.template.tsx @@ -0,0 +1,116 @@ +import { + $, + css, + PlaceholderValue, + type TemplateMeta, +} from "@webstudio-is/template"; +import { radix } from "./shared/proxy"; +import { + borderWidth, + colors, + fontSize, + fontSizeLineHeight, + height, + spacing, + transition, + weights, + width, +} from "./shared/theme"; +import { ChevronDownIcon } from "@webstudio-is/icons/svg"; + +const createAccordionItem = (triggerText: string, contentText: string) => { + return ( + + + svg]:rotate-180 + ws:style={css` + display: flex; + flex: 1 1 0; + align-items: center; + justify-content: between; + padding: ${spacing[4]} 0; + font-weight: ${weights.medium}; + --accordion-trigger-icon-transform: 0deg; + &:hover { + text-decoration-line: underline; + } + &[data-state="open"] { + --accordion-trigger-icon-transform: 180deg; + } + `} + > + {new PlaceholderValue(triggerText)} + + <$.Box + ws:label="Icon Container" + // h-4 w-4 shrink-0 transition-transform duration-200 + ws:style={css` + rotate: --accordion-trigger-icon-transform; + height: ${height[4]}; + width: ${width[4]}; + flex-shrink: 0; + transition: ${transition.all}; + transition-duration: 200ms; + `} + > + <$.HtmlEmbed ws:label="Chevron Icon" code={ChevronDownIcon} /> + + + + {new PlaceholderValue(contentText)} + + + ); +}; + +/** + * Styles source without animations: + * https://github.com/shadcn-ui/ui/blob/main/apps/www/registry/default/ui/accordion.tsx + * + * Attributions + * MIT License + * Copyright (c) 2023 shadcn + **/ +export const meta: TemplateMeta = { + category: "radix", + description: + "A vertically stacked set of interactive headings that each reveal an associated section of content. Clicking on the heading will open the item and close other items.", + order: 3, + template: ( + + {createAccordionItem( + "Is it accessible?", + "Yes. It adheres to the WAI-ARIA design pattern." + )} + {createAccordionItem( + "Is it styled?", + "Yes. It comes with default styles that matches the other components' aesthetic." + )} + {createAccordionItem( + "Is it animated?", + "Yes. It's animated by default, but you can disable it if you prefer." + )} + + ), +}; diff --git a/packages/sdk-components-react-radix/src/accordion.ws.ts b/packages/sdk-components-react-radix/src/accordion.ws.ts index 0c5658651c97..fece7c41d7f2 100644 --- a/packages/sdk-components-react-radix/src/accordion.ws.ts +++ b/packages/sdk-components-react-radix/src/accordion.ws.ts @@ -4,15 +4,12 @@ import { HeaderIcon, TriggerIcon, ContentIcon, - ChevronDownIcon, } from "@webstudio-is/icons/svg"; import { defaultStates, - type EmbedTemplateStyleDecl, type PresetStyle, type WsComponentMeta, type WsComponentPropsMeta, - type WsEmbedTemplate, } from "@webstudio-is/react-sdk"; import { div, h3, button } from "@webstudio-is/sdk/normalize.css"; import * as tc from "./theme/tailwind-classes"; @@ -29,202 +26,19 @@ const presetStyle = { div, } satisfies PresetStyle<"div">; -/** - * Styles source without animations: - * https://github.com/shadcn-ui/ui/blob/main/apps/www/registry/default/ui/accordion.tsx - * - * Attributions - * MIT License - * Copyright (c) 2023 shadcn - **/ - -// border-b -const accordionItemStyles: EmbedTemplateStyleDecl[] = [tc.borderB()].flat(); - -const createAccordionTrigger = ({ - children, -}: { - children: WsEmbedTemplate; -}): WsEmbedTemplate[number] => ({ - type: "instance", - component: "AccordionHeader", - // flex - styles: [tc.flex()].flat(), - children: [ - { - type: "instance", - component: "AccordionTrigger", - // flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180 - styles: [ - tc.flex(), - tc.flex(1), - tc.items("center"), - tc.justify("between"), - tc.py(4), - tc.font("medium"), - tc.hover([tc.underline()].flat()), - tc.property("--accordion-trigger-icon-transform", "0deg"), - tc.state( - [tc.property("--accordion-trigger-icon-transform", "180deg")], - "[data-state=open]" - ), - ].flat(), - children: [ - { - type: "instance", - component: "Text", - children, - }, - { - type: "instance", - component: "Box", - label: "Icon Container", - // h-4 w-4 shrink-0 transition-transform duration-200 - styles: [ - tc.property("rotate", "--accordion-trigger-icon-transform"), - tc.h(4), - tc.w(4), - tc.shrink(0), - tc.transition("all"), - tc.duration(200), - ].flat(), - children: [ - { - type: "instance", - component: "HtmlEmbed", - label: "Chevron Icon", - props: [ - { - type: "string", - name: "code", - value: ChevronDownIcon, - }, - ], - children: [], - }, - ], - }, - ], - }, - ], -}); - -// overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down -// pb-4 pt-0 -const accordionContentStyles: EmbedTemplateStyleDecl[] = [ - tc.overflow("hidden"), - tc.text("sm"), - // transition does not work with display: none - // tc.transition("all"), - tc.pb(4), -].flat(); - export const metaAccordion: WsComponentMeta = { - category: "radix", - order: 3, type: "container", icon: AccordionIcon, presetStyle, - description: - "A vertically stacked set of interactive headings that each reveal an associated section of content. Clicking on the heading will open the item and close other items.", constraints: [ { relation: "descendant", component: { $eq: "AccordionItem" }, }, ], - template: [ - { - type: "instance", - component: "Accordion", - props: [ - { type: "boolean", name: "collapsible", value: true }, - { type: "string", name: "defaultValue", value: "0" }, - ], - children: [ - { - type: "instance", - component: "AccordionItem", - styles: accordionItemStyles, - children: [ - createAccordionTrigger({ - children: [ - { type: "text", value: "Is it accessible?", placeholder: true }, - ], - }), - { - type: "instance", - component: "AccordionContent", - styles: accordionContentStyles, - children: [ - { - type: "text", - value: "Yes. It adheres to the WAI-ARIA design pattern.", - placeholder: true, - }, - ], - }, - ], - }, - - { - type: "instance", - component: "AccordionItem", - styles: accordionItemStyles, - children: [ - createAccordionTrigger({ - children: [ - { type: "text", value: "Is it styled?", placeholder: true }, - ], - }), - { - type: "instance", - component: "AccordionContent", - styles: accordionContentStyles, - children: [ - { - type: "text", - value: - "Yes. It comes with default styles that matches the other components' aesthetic.", - placeholder: true, - }, - ], - }, - ], - }, - - { - type: "instance", - component: "AccordionItem", - styles: accordionItemStyles, - children: [ - createAccordionTrigger({ - children: [ - { type: "text", value: "Is it animated?", placeholder: true }, - ], - }), - { - type: "instance", - component: "AccordionContent", - styles: accordionContentStyles, - children: [ - { - type: "text", - value: - "Yes. It's animated by default, but you can disable it if you prefer.", - placeholder: true, - }, - ], - }, - ], - }, - ], - }, - ], }; export const metaAccordionItem: WsComponentMeta = { - category: "hidden", type: "container", label: "Item", icon: ItemIcon, @@ -247,7 +61,6 @@ export const metaAccordionItem: WsComponentMeta = { }; export const metaAccordionHeader: WsComponentMeta = { - category: "hidden", type: "container", label: "Item Header", icon: HeaderIcon, @@ -267,7 +80,6 @@ export const metaAccordionHeader: WsComponentMeta = { }; export const metaAccordionTrigger: WsComponentMeta = { - category: "hidden", type: "container", label: "Item Trigger", icon: TriggerIcon, @@ -289,7 +101,6 @@ export const metaAccordionTrigger: WsComponentMeta = { }; export const metaAccordionContent: WsComponentMeta = { - category: "hidden", type: "container", label: "Item Content", icon: ContentIcon, diff --git a/packages/sdk-components-react-radix/src/collapsible.template.tsx b/packages/sdk-components-react-radix/src/collapsible.template.tsx new file mode 100644 index 000000000000..1606c1d5502c --- /dev/null +++ b/packages/sdk-components-react-radix/src/collapsible.template.tsx @@ -0,0 +1,22 @@ +import { $, PlaceholderValue, type TemplateMeta } from "@webstudio-is/template"; +import { radix } from "./shared/proxy"; +import { getButtonStyle } from "./shared/styles"; + +export const meta: TemplateMeta = { + category: "radix", + description: + "An interactive component which expands and collapses some content, triggered by a button.", + order: 5, + template: ( + + + <$.Button ws:style={getButtonStyle("outline")}> + {new PlaceholderValue("Click to toggle content")} + + + + <$.Text>{new PlaceholderValue("Collapsible Content")} + + + ), +}; diff --git a/packages/sdk-components-react-radix/src/collapsible.ws.ts b/packages/sdk-components-react-radix/src/collapsible.ws.ts index 598045c15daa..c774a9438384 100644 --- a/packages/sdk-components-react-radix/src/collapsible.ws.ts +++ b/packages/sdk-components-react-radix/src/collapsible.ws.ts @@ -14,15 +14,12 @@ import { propsCollapsibleContent, propsCollapsibleTrigger, } from "./__generated__/collapsible.props"; -import { getButtonStyles } from "./theme/styles"; const presetStyle = { div, } satisfies PresetStyle<"div">; export const metaCollapsible: WsComponentMeta = { - category: "radix", - order: 5, type: "container", constraints: [ { @@ -36,56 +33,9 @@ export const metaCollapsible: WsComponentMeta = { ], presetStyle, icon: CollapsibleIcon, - description: - "An interactive component which expands and collapses some content, triggered by a button.", - template: [ - { - type: "instance", - component: "Collapsible", - props: [], - children: [ - { - type: "instance", - component: "CollapsibleTrigger", - children: [ - { - type: "instance", - component: "Button", - styles: getButtonStyles("outline"), - children: [ - { - type: "text", - value: "Click to toggle content", - placeholder: true, - }, - ], - }, - ], - }, - { - type: "instance", - component: "CollapsibleContent", - children: [ - { - type: "instance", - component: "Text", - children: [ - { - type: "text", - value: "Collapsible Content", - placeholder: true, - }, - ], - }, - ], - }, - ], - }, - ], }; export const metaCollapsibleTrigger: WsComponentMeta = { - category: "hidden", type: "container", icon: TriggerIcon, stylable: false, @@ -96,7 +46,6 @@ export const metaCollapsibleTrigger: WsComponentMeta = { }; export const metaCollapsibleContent: WsComponentMeta = { - category: "hidden", type: "container", presetStyle, icon: ContentIcon, diff --git a/packages/sdk-components-react-radix/src/templates.ts b/packages/sdk-components-react-radix/src/templates.ts index bc6fa481d8c6..787a28b60ecb 100644 --- a/packages/sdk-components-react-radix/src/templates.ts +++ b/packages/sdk-components-react-radix/src/templates.ts @@ -4,3 +4,5 @@ export { meta as Sheet } from "./sheet.template"; export { meta as Dialog } from "./dialog.template"; export { meta as Switch } from "./switch.template"; export { meta as Checkbox } from "./checkbox.template"; +export { meta as Collapsible } from "./collapsible.template"; +export { meta as Accordion } from "./accordion.template";