diff --git a/assets/docs/components/accordion.md b/assets/docs/components/accordion.md index 9630554..9246b76 100644 --- a/assets/docs/components/accordion.md +++ b/assets/docs/components/accordion.md @@ -8,9 +8,9 @@ order: 0 ## Accordion, A Vertically Stacked Set Of Interactive Headings That Each Reveal A Section Of Content. - -> Component `accordion` not found - +```python +from components.ui.accordion import Accordion +``` ```python from typing import Any @@ -217,7 +217,7 @@ class NativeAccordionPanel(CoreComponent): ) -class NativeAccordion(ComponentNamespace): +class Accordion(ComponentNamespace): root = staticmethod(NativeAccordionRoot.create) item = staticmethod(NativeAccordionItem.create) trigger = staticmethod(NativeAccordionTrigger.create) @@ -225,7 +225,7 @@ class NativeAccordion(ComponentNamespace): class_names = ClassNames -accordion = NativeAccordion() +accordion = Accordion() ``` # Examples diff --git a/assets/docs/components/collapsible.md b/assets/docs/components/collapsible.md new file mode 100644 index 0000000..d25427f --- /dev/null +++ b/assets/docs/components/collapsible.md @@ -0,0 +1,260 @@ +--- +title: "Collapsible" +description: "An interactive component which expands/collapses a panel." +order: 0 +--- + + +## Collapsible, An Interactive Component Which Expands/Collapses A Panel. + + +```python +from components.ui.collapsible import Collapsible +``` + +```python +from typing import Any + +from reflex.components.component import Component +from reflex.utils.imports import ImportVar +from reflex.vars import FunctionVar, Var +from reflex.vars.base import VarData + +PACKAGE_CN = "clsx-for-tailwind@1.0.0" +CN = Var( + "cn", + _var_data=VarData( + imports={ + PACKAGE_CN: ImportVar(tag="cn"), + }, + ), +).to(FunctionVar) + + +class CoreComponent(Component): + unstyled: Var[bool] + + @classmethod + def set_class_name( + cls, default_class_name: str | Var[str], props: dict[str, Any] + ) -> None: + + if "render_" in props: + return + + props_class_name = props.get("class_name", "") + + if props.pop("unstyled", False): + props["class_name"] = props_class_name + return + + props["class_name"] = cn(default_class_name, props_class_name) + + def _exclude_props(self) -> list[str]: + return [ + *super()._exclude_props(), + "unstyled", + ] + + +def cn(*classes: Var | str | tuple | list | None) -> Var: + return CN.call(*classes).to(str) +``` + +```python +import reflex as rx +from reflex.components.component import Component, ComponentNamespace +from reflex_components_core.el import Div + +from ..core.core import CoreComponent + + +class ClassNames: + ROOT = "flex flex-col justify-center text-secondary-12" + TRIGGER = ( + "flex items-center gap-2 cursor-pointer select-none list-none rounded-md " + "marker:content-none [&::-webkit-details-marker]:hidden " + "[details[open]>&]:bg-muted/50" + ) + PANEL = "text-sm" + + +class CollapsibleRoot(CoreComponent): + @classmethod + def create(cls, *children, default_open: bool = False, **props) -> Component: + props["data-slot"] = "collapsible" + if default_open: + props.setdefault("open", True) + cls.set_class_name(ClassNames.ROOT, props) + return rx.el.details(*children, **props) + + +class CollapsibleTrigger(CoreComponent): + @classmethod + def create(cls, *children, **props) -> Component: + props["data-slot"] = "collapsible-trigger" + cls.set_class_name(ClassNames.TRIGGER, props) + return rx.el.summary(*children, **props) + + +class CollapsiblePanel(Div, CoreComponent): + @classmethod + def create(cls, *children, **props) -> Div: + props["data-slot"] = "collapsible-panel" + cls.set_class_name(ClassNames.PANEL, props) + return super().create(*children, **props) + + +class Collapsible(ComponentNamespace): + root = staticmethod(CollapsibleRoot.create) + trigger = staticmethod(CollapsibleTrigger.create) + panel = staticmethod(CollapsiblePanel.create) + class_names = ClassNames + + +collapsible = Collapsible() +``` + +# Examples + +## Basic + +Built on native `
`/``, expand/collapse and keyboard support come from the browser, no JS required. The chevron rotation and open-state highlight use a structural selector (`[details[open]>&]`) rather than Tailwind's `group-open:`, since `group-open:` matches _any_ open ancestor rather than the nearest one — harmless here, but matters once collapsibles nest (see below). + +**Props used:** `default_open` on `collapsible.root` (starts the panel expanded). + +```python +def collapsible_basic() -> rx.Component: + return rx.el.div( + collapsible.root( + collapsible.trigger( + rx.el.div( + rx.el.p("How do I update my billing information?"), + hi( + "ArrowDown01Icon", + class_name="size-4 ml-auto [details[open]>summary_&]:rotate-180", + ), + class_name="w-full flex flex-row items-center justify-between", + ), + class_name=cn( + "w-full py-3 text-left border-b border-input", + button_variants(variant="ghost"), + ), + ), + collapsible.panel( + rx.el.div( + "You can update your card details directly inside your account settings dashboard under the billing tab.", + class_name="py-3 text-sm text-muted-foreground leading-relaxed px-3", + ), + ), + default_open=False, + ), + class_name="w-full max-w-sm", + ) +``` + +## Nested + +Collapsibles nest freely, each `
` is fully independent, so there's no coordination needed between levels (multiple folders can be open at once). The one thing that _does_ need care when nesting: chevron/highlight styling must use the structural `[details[open]>...]` selector, not `group-open:`. `group-open:` would match an open ancestor several folders up, which makes every icon inside it look rotated even if that specific folder is still closed. + +**Props used:** none beyond `collapsible.root` / `collapsible.trigger` / `collapsible.panel` — the tree is just `folder_item` calling itself recursively. + +```python +def collapsible_nested() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.span( + "WORKSPACE EXPLORER", + class_name="text-[10px] font-bold tracking-wider text-muted-foreground", + ), + class_name="px-2 pb-2 mb-1 border-b border-border/40", + ), + folder_item( + "src", + folder_item( + "components", + folder_item( + "ui", + file_item("button.py"), + file_item("card.py"), + file_item("collapsible.py"), + ), + file_item("navbar.py"), + file_item("sidebar.py"), + ), + folder_item( + "state", + file_item("base_state.py"), + file_item("auth_state.py"), + ), + file_item("main.py"), + ), + folder_item( + "public", + file_item("favicon.ico"), + file_item("logo.svg"), + ), + file_item("rxconfig.py", root_level=True), + file_item("requirements.txt", root_level=True), + class_name="w-full max-w-xs p-3 bg-background border border-input rounded-xl shadow-xs select-none", + ) +``` + +# API Reference + +## collapsible.root + +Renders a native `
` element. `default_open` sets the initial `open` attribute on first render. Since it's a real `
`, controlled usage also works without any extra plumbing — pass `open=State.is_open` and `on_toggle=State.handle_toggle` to drive or react to it from Reflex state, same as any other native attribute/event. + +```python +collapsible.root( + collapsible.trigger(...), + collapsible.panel(...), + default_open=False, +) +``` + +| Prop | Type | Default | +| -------------- | -------------- | ------- | +| `default_open` | `bool` | `False` | +| `open` | `bool` | `None` | +| `on_toggle` | `EventHandler` | `None` | +| `class_name` | `str` | `""` | + +## collapsible.trigger + +Renders a ``. Unlike a typical accordion trigger, it doesn't append a chevron for you — pass your own icon (and any other trigger content) as children, and drive its rotation off the parent's open state yourself. + +```python +collapsible.trigger( + rx.el.p("How do I update my billing information?"), + hi("ArrowDown01Icon", class_name="[details[open]>summary_&]:rotate-180"), +) +``` + +| Prop | Type | Default | +| ------------ | ----- | ------- | +| `class_name` | `str` | `""` | + +## collapsible.panel + +```python +collapsible.panel( + "You can update your card details directly inside your account settings dashboard.", +) +``` + +| Prop | Type | Default | +| ------------ | ----- | ------- | +| `class_name` | `str` | `""` | + +## Styling based on open state + +`collapsible.root` renders the actual `
`, so any open-state styling on the trigger or its children should key off that element directly rather than a Tailwind `group`: + +| Target | Selector | +| ------------------------------------------- | -------------------------------------- | +| The trigger itself (e.g. a highlight) | `[details[open]>&]:bg-muted/50` | +| Something inside the trigger (e.g. an icon) | `[details[open]>summary_&]:rotate-180` | + +Both are scoped to _that_ collapsible's own `
` — never a distant open ancestor — which is what makes them safe to reuse inside recursively nested collapsibles like the file-tree example above. diff --git a/assets/docs/components/table.md b/assets/docs/components/table.md index aaff179..62a82c7 100644 --- a/assets/docs/components/table.md +++ b/assets/docs/components/table.md @@ -61,6 +61,53 @@ def cn(*classes: Var | str | tuple | list | None) -> Var: return CN.call(*classes).to(str) ``` +```python +from reflex.components.component import ComponentNamespace +from reflex_components_core.el import Input as BaseInput + +from ..core.core import CoreComponent, cn + + +class ClassNames: + INPUT = ( + "w-full file:text-foreground placeholder:text-muted-foreground " + "selection:bg-primary selection:text-primary-foreground " + "dark:bg-input/30 border-input " + "h-8 w-full min-w-0 rounded-lg border bg-transparent px-3 py-1 text-base " + "outline-none " + "file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium " + "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 " + "md:text-sm " + "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] " + "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 " + "aria-invalid:border-destructive" + ) + + +class InputComponent(BaseInput, CoreComponent): + @classmethod + def create(cls, *children, **props) -> BaseInput: + + existing_class = props.get("class_name", "") + + props["class_name"] = cn(ClassNames.INPUT, existing_class) + + props.setdefault("data_slot", "input") + + if "type" not in props: + props["type"] = "text" + + return super().create(*children, **props) + + +class Input(ComponentNamespace): + __call__ = staticmethod(InputComponent.create) + class_name = ClassNames + + +input = Input() +``` + ```python import uuid from typing import Any, Literal diff --git a/assets/llms.txt b/assets/llms.txt index d7e2a52..d1f85b7 100644 --- a/assets/llms.txt +++ b/assets/llms.txt @@ -29,6 +29,7 @@ - [Button Group](https://native.buridan.dev/docs/components/button-group): The Button Group component. - [Card](https://native.buridan.dev/docs/components/card): The Card component. - [Checkbox](https://native.buridan.dev/docs/components/checkbox): The Checkbox component. +- [Collapsible](https://native.buridan.dev/docs/components/collapsible): The Collapsible component. - [Dialog](https://native.buridan.dev/docs/components/dialog): The Dialog component. - [Field](https://native.buridan.dev/docs/components/field): The Field component. - [Frame](https://native.buridan.dev/docs/components/frame): The Frame component. diff --git a/assets/social/accordion.webp b/assets/social/accordion.webp index 42417b3..22e797e 100644 Binary files a/assets/social/accordion.webp and b/assets/social/accordion.webp differ diff --git a/assets/social/area-chart.webp b/assets/social/area-chart.webp index 1187013..8170d1c 100644 Binary files a/assets/social/area-chart.webp and b/assets/social/area-chart.webp differ diff --git a/assets/social/attachment.webp b/assets/social/attachment.webp index 69ce90c..6ba743f 100644 Binary files a/assets/social/attachment.webp and b/assets/social/attachment.webp differ diff --git a/assets/social/avatar.webp b/assets/social/avatar.webp index ec2c3a2..0590799 100644 Binary files a/assets/social/avatar.webp and b/assets/social/avatar.webp differ diff --git a/assets/social/badge.webp b/assets/social/badge.webp index 93b4052..9446ee5 100644 Binary files a/assets/social/badge.webp and b/assets/social/badge.webp differ diff --git a/assets/social/bar-chart.webp b/assets/social/bar-chart.webp index 4fbe34c..9568f97 100644 Binary files a/assets/social/bar-chart.webp and b/assets/social/bar-chart.webp differ diff --git a/assets/social/blocks.webp b/assets/social/blocks.webp index 9859472..a2fcbb8 100644 Binary files a/assets/social/blocks.webp and b/assets/social/blocks.webp differ diff --git a/assets/social/bubble.webp b/assets/social/bubble.webp index 1354803..89fb011 100644 Binary files a/assets/social/bubble.webp and b/assets/social/bubble.webp differ diff --git a/assets/social/button-group.webp b/assets/social/button-group.webp index 27a5ac0..24543a6 100644 Binary files a/assets/social/button-group.webp and b/assets/social/button-group.webp differ diff --git a/assets/social/button.webp b/assets/social/button.webp index 8ac4c22..16160c8 100644 Binary files a/assets/social/button.webp and b/assets/social/button.webp differ diff --git a/assets/social/card.webp b/assets/social/card.webp index 66308d5..762b6c6 100644 Binary files a/assets/social/card.webp and b/assets/social/card.webp differ diff --git a/assets/social/changelog.webp b/assets/social/changelog.webp index 3348639..c8775c2 100644 Binary files a/assets/social/changelog.webp and b/assets/social/changelog.webp differ diff --git a/assets/social/chart.webp b/assets/social/chart.webp index ea5c77f..b552a71 100644 Binary files a/assets/social/chart.webp and b/assets/social/chart.webp differ diff --git a/assets/social/charts.webp b/assets/social/charts.webp index 2c2a52b..98761d0 100644 Binary files a/assets/social/charts.webp and b/assets/social/charts.webp differ diff --git a/assets/social/checkbox.webp b/assets/social/checkbox.webp index fa96d88..1d1fd57 100644 Binary files a/assets/social/checkbox.webp and b/assets/social/checkbox.webp differ diff --git a/assets/social/cli.webp b/assets/social/cli.webp index 657b1d7..e0c3a6e 100644 Binary files a/assets/social/cli.webp and b/assets/social/cli.webp differ diff --git a/assets/social/client-state-var.webp b/assets/social/client-state-var.webp index 7a3bf7b..de57b6f 100644 Binary files a/assets/social/client-state-var.webp and b/assets/social/client-state-var.webp differ diff --git a/assets/social/collapsible.webp b/assets/social/collapsible.webp new file mode 100644 index 0000000..dd655d9 Binary files /dev/null and b/assets/social/collapsible.webp differ diff --git a/assets/social/components.webp b/assets/social/components.webp index 2a7efeb..09b0b3f 100644 Binary files a/assets/social/components.webp and b/assets/social/components.webp differ diff --git a/assets/social/create.webp b/assets/social/create.webp index 8d9fcc1..8e4bf27 100644 Binary files a/assets/social/create.webp and b/assets/social/create.webp differ diff --git a/assets/social/dashboards.webp b/assets/social/dashboards.webp index 24dc8b0..1f8d72f 100644 Binary files a/assets/social/dashboards.webp and b/assets/social/dashboards.webp differ diff --git a/assets/social/dev.webp b/assets/social/dev.webp index 4236fe2..fa6fbe3 100644 Binary files a/assets/social/dev.webp and b/assets/social/dev.webp differ diff --git a/assets/social/dialog.webp b/assets/social/dialog.webp index 837a31e..e9c1791 100644 Binary files a/assets/social/dialog.webp and b/assets/social/dialog.webp differ diff --git a/assets/social/docs.webp b/assets/social/docs.webp index d0405e7..bf7b272 100644 Binary files a/assets/social/docs.webp and b/assets/social/docs.webp differ diff --git a/assets/social/events.webp b/assets/social/events.webp index 4c3f511..9b6db06 100644 Binary files a/assets/social/events.webp and b/assets/social/events.webp differ diff --git a/assets/social/field.webp b/assets/social/field.webp index a530bbf..862f991 100644 Binary files a/assets/social/field.webp and b/assets/social/field.webp differ diff --git a/assets/social/frame.webp b/assets/social/frame.webp index c813b43..fdd4e99 100644 Binary files a/assets/social/frame.webp and b/assets/social/frame.webp differ diff --git a/assets/social/index.webp b/assets/social/index.webp index 4e1f162..8bd50a7 100644 Binary files a/assets/social/index.webp and b/assets/social/index.webp differ diff --git a/assets/social/input-group.webp b/assets/social/input-group.webp index 095a17d..3aaebc8 100644 Binary files a/assets/social/input-group.webp and b/assets/social/input-group.webp differ diff --git a/assets/social/input.webp b/assets/social/input.webp index 4e73bf2..ab026d1 100644 Binary files a/assets/social/input.webp and b/assets/social/input.webp differ diff --git a/assets/social/installation.webp b/assets/social/installation.webp index aaf67a9..38eab2b 100644 Binary files a/assets/social/installation.webp and b/assets/social/installation.webp differ diff --git a/assets/social/introduction.webp b/assets/social/introduction.webp index 1ce3675..1ab1b2e 100644 Binary files a/assets/social/introduction.webp and b/assets/social/introduction.webp differ diff --git a/assets/social/javascript.webp b/assets/social/javascript.webp index 0202190..92e37f1 100644 Binary files a/assets/social/javascript.webp and b/assets/social/javascript.webp differ diff --git a/assets/social/line-chart.webp b/assets/social/line-chart.webp index 70014e9..1175203 100644 Binary files a/assets/social/line-chart.webp and b/assets/social/line-chart.webp differ diff --git a/assets/social/markdown.webp b/assets/social/markdown.webp index 82197c5..d3047ac 100644 Binary files a/assets/social/markdown.webp and b/assets/social/markdown.webp differ diff --git a/assets/social/marker.webp b/assets/social/marker.webp index 69e214f..3446984 100644 Binary files a/assets/social/marker.webp and b/assets/social/marker.webp differ diff --git a/assets/social/menu.webp b/assets/social/menu.webp index 64fcde8..cf90205 100644 Binary files a/assets/social/menu.webp and b/assets/social/menu.webp differ diff --git a/assets/social/message.webp b/assets/social/message.webp index eadb934..e95ccfd 100644 Binary files a/assets/social/message.webp and b/assets/social/message.webp differ diff --git a/assets/social/scroll-fade.webp b/assets/social/scroll-fade.webp index 625a6ea..1973ebf 100644 Binary files a/assets/social/scroll-fade.webp and b/assets/social/scroll-fade.webp differ diff --git a/assets/social/select.webp b/assets/social/select.webp index 7e1bc45..45158fb 100644 Binary files a/assets/social/select.webp and b/assets/social/select.webp differ diff --git a/assets/social/separator.webp b/assets/social/separator.webp index 946a41a..c0926d0 100644 Binary files a/assets/social/separator.webp and b/assets/social/separator.webp differ diff --git a/assets/social/shimmer.webp b/assets/social/shimmer.webp index 5bed932..975b42f 100644 Binary files a/assets/social/shimmer.webp and b/assets/social/shimmer.webp differ diff --git a/assets/social/skills.webp b/assets/social/skills.webp index 42a1693..f6e5f40 100644 Binary files a/assets/social/skills.webp and b/assets/social/skills.webp differ diff --git a/assets/social/spinner.webp b/assets/social/spinner.webp index 3b299b3..9b15297 100644 Binary files a/assets/social/spinner.webp and b/assets/social/spinner.webp differ diff --git a/assets/social/state.webp b/assets/social/state.webp index 9459458..aca81cb 100644 Binary files a/assets/social/state.webp and b/assets/social/state.webp differ diff --git a/assets/social/switch.webp b/assets/social/switch.webp index d97dd47..5efd8d5 100644 Binary files a/assets/social/switch.webp and b/assets/social/switch.webp differ diff --git a/assets/social/table.webp b/assets/social/table.webp index 55b2fbf..c05b58f 100644 Binary files a/assets/social/table.webp and b/assets/social/table.webp differ diff --git a/assets/social/tabs.webp b/assets/social/tabs.webp index 77198d3..6504366 100644 Binary files a/assets/social/tabs.webp and b/assets/social/tabs.webp differ diff --git a/assets/social/textarea.webp b/assets/social/textarea.webp index 5935ba3..57da22d 100644 Binary files a/assets/social/textarea.webp and b/assets/social/textarea.webp differ diff --git a/assets/social/theming.webp b/assets/social/theming.webp index d96698f..5155dbf 100644 Binary files a/assets/social/theming.webp and b/assets/social/theming.webp differ diff --git a/assets/social/typeset.webp b/assets/social/typeset.webp index 0e41448..01e5a82 100644 Binary files a/assets/social/typeset.webp and b/assets/social/typeset.webp differ diff --git a/assets/social/typography.webp b/assets/social/typography.webp index a8b8421..b5af496 100644 Binary files a/assets/social/typography.webp and b/assets/social/typography.webp differ diff --git a/assets/themes.css b/assets/themes.css deleted file mode 100644 index 7a78cb7..0000000 --- a/assets/themes.css +++ /dev/null @@ -1,12 +0,0 @@ -.preview-theme { - --background: white; - --foreground: black; -} - -.preview-theme .bg-background { - background: var(--background); -} - -.preview-theme .text-foreground { - color: var(--foreground); -} diff --git a/components/ui/accordion.py b/components/ui/accordion.py index ee06ac8..083f05c 100644 --- a/components/ui/accordion.py +++ b/components/ui/accordion.py @@ -86,7 +86,7 @@ def create(cls, *children, **props) -> rx.Component: ) -class NativeAccordion(ComponentNamespace): +class Accordion(ComponentNamespace): root = staticmethod(NativeAccordionRoot.create) item = staticmethod(NativeAccordionItem.create) trigger = staticmethod(NativeAccordionTrigger.create) @@ -94,4 +94,4 @@ class NativeAccordion(ComponentNamespace): class_names = ClassNames -accordion = NativeAccordion() +accordion = Accordion() diff --git a/components/ui/collapsible.py b/components/ui/collapsible.py new file mode 100644 index 0000000..11a11c4 --- /dev/null +++ b/components/ui/collapsible.py @@ -0,0 +1,51 @@ +import reflex as rx +from reflex.components.component import Component, ComponentNamespace +from reflex_components_core.el import Div + +from ..core.core import CoreComponent + + +class ClassNames: + ROOT = "flex flex-col justify-center text-secondary-12" + TRIGGER = ( + "flex items-center gap-2 cursor-pointer select-none list-none rounded-md " + "marker:content-none [&::-webkit-details-marker]:hidden " + "[details[open]>&]:bg-muted/50" + ) + PANEL = "text-sm" + + +class CollapsibleRoot(CoreComponent): + @classmethod + def create(cls, *children, default_open: bool = False, **props) -> Component: + props["data-slot"] = "collapsible" + if default_open: + props.setdefault("open", True) + cls.set_class_name(ClassNames.ROOT, props) + return rx.el.details(*children, **props) + + +class CollapsibleTrigger(CoreComponent): + @classmethod + def create(cls, *children, **props) -> Component: + props["data-slot"] = "collapsible-trigger" + cls.set_class_name(ClassNames.TRIGGER, props) + return rx.el.summary(*children, **props) + + +class CollapsiblePanel(Div, CoreComponent): + @classmethod + def create(cls, *children, **props) -> Div: + props["data-slot"] = "collapsible-panel" + cls.set_class_name(ClassNames.PANEL, props) + return super().create(*children, **props) + + +class Collapsible(ComponentNamespace): + root = staticmethod(CollapsibleRoot.create) + trigger = staticmethod(CollapsibleTrigger.create) + panel = staticmethod(CollapsiblePanel.create) + class_names = ClassNames + + +collapsible = Collapsible() diff --git a/docs/components/collapsible.md b/docs/components/collapsible.md new file mode 100644 index 0000000..ed923ff --- /dev/null +++ b/docs/components/collapsible.md @@ -0,0 +1,88 @@ +--- +title: "Collapsible" +description: "An interactive component which expands/collapses a panel." +order: 0 +--- + +--INTRO([Collapsible, An interactive component which expands/collapses a panel. ])-- + +--USAGE(collapsible)-- + +--SOURCE(collapsible)-- + +# Examples + +## Basic + +Built on native `
`/``, expand/collapse and keyboard support come from the browser, no JS required. The chevron rotation and open-state highlight use a structural selector (`[details[open]>&]`) rather than Tailwind's `group-open:`, since `group-open:` matches _any_ open ancestor rather than the nearest one — harmless here, but matters once collapsibles nest (see below). + +**Props used:** `default_open` on `collapsible.root` (starts the panel expanded). + +--DEMO(collapsible_basic)-- + +## Nested + +Collapsibles nest freely, each `
` is fully independent, so there's no coordination needed between levels (multiple folders can be open at once). The one thing that _does_ need care when nesting: chevron/highlight styling must use the structural `[details[open]>...]` selector, not `group-open:`. `group-open:` would match an open ancestor several folders up, which makes every icon inside it look rotated even if that specific folder is still closed. + +**Props used:** none beyond `collapsible.root` / `collapsible.trigger` / `collapsible.panel` — the tree is just `folder_item` calling itself recursively. + +--DEMO(collapsible_nested)-- + +# API Reference + +## collapsible.root + +Renders a native `
` element. `default_open` sets the initial `open` attribute on first render. Since it's a real `
`, controlled usage also works without any extra plumbing — pass `open=State.is_open` and `on_toggle=State.handle_toggle` to drive or react to it from Reflex state, same as any other native attribute/event. + +```python +collapsible.root( + collapsible.trigger(...), + collapsible.panel(...), + default_open=False, +) +``` + +| Prop | Type | Default | +| -------------- | -------------- | ------- | +| `default_open` | `bool` | `False` | +| `open` | `bool` | `None` | +| `on_toggle` | `EventHandler` | `None` | +| `class_name` | `str` | `""` | + +## collapsible.trigger + +Renders a ``. Unlike a typical accordion trigger, it doesn't append a chevron for you — pass your own icon (and any other trigger content) as children, and drive its rotation off the parent's open state yourself. + +```python +collapsible.trigger( + rx.el.p("How do I update my billing information?"), + hi("ArrowDown01Icon", class_name="[details[open]>summary_&]:rotate-180"), +) +``` + +| Prop | Type | Default | +| ------------ | ----- | ------- | +| `class_name` | `str` | `""` | + +## collapsible.panel + +```python +collapsible.panel( + "You can update your card details directly inside your account settings dashboard.", +) +``` + +| Prop | Type | Default | +| ------------ | ----- | ------- | +| `class_name` | `str` | `""` | + +## Styling based on open state + +`collapsible.root` renders the actual `
`, so any open-state styling on the trigger or its children should key off that element directly rather than a Tailwind `group`: + +| Target | Selector | +| ------------------------------------------- | -------------------------------------- | +| The trigger itself (e.g. a highlight) | `[details[open]>&]:bg-muted/50` | +| Something inside the trigger (e.g. an icon) | `[details[open]>summary_&]:rotate-180` | + +Both are scoped to _that_ collapsible's own `
` — never a distant open ancestor — which is what makes them safe to reuse inside recursively nested collapsibles like the file-tree example above. diff --git a/native/export.py b/native/export.py index 906a34c..b888cb1 100644 --- a/native/export.py +++ b/native/export.py @@ -67,7 +67,7 @@ def export(app: App): app.add_page( component=landing_page(), route="/", - title="The UI Library for Reflex Developers - buridan/native", + title="Native HTML UI components you can copy, paste, and ship in minutes - buridan/native", meta=generate_site_meta_tags( title="Buridan Native", url="/", diff --git a/native/hooks/stylesheets.py b/native/hooks/stylesheets.py index fc860f0..4afcd00 100644 --- a/native/hooks/stylesheets.py +++ b/native/hooks/stylesheets.py @@ -21,7 +21,6 @@ APP_STYLESHEETS: list[str] = [ "globals.css", "prism.css", - "themes.css", "typeset.css", GOOGLE_FONTS_URL, ] diff --git a/native/lib/components/collapsible_basic.py b/native/lib/components/collapsible_basic.py new file mode 100644 index 0000000..19b9e13 --- /dev/null +++ b/native/lib/components/collapsible_basic.py @@ -0,0 +1,35 @@ +import reflex as rx + +from components.core.core import cn +from components.core.hugeicon import hi +from components.ui.button import button_variants +from components.ui.collapsible import collapsible + + +def collapsible_basic() -> rx.Component: + return rx.el.div( + collapsible.root( + collapsible.trigger( + rx.el.div( + rx.el.p("How do I update my billing information?"), + hi( + "ArrowDown01Icon", + class_name="size-4 ml-auto [details[open]>summary_&]:rotate-180", + ), + class_name="w-full flex flex-row items-center justify-between", + ), + class_name=cn( + "w-full py-3 text-left border-b border-input", + button_variants(variant="ghost"), + ), + ), + collapsible.panel( + rx.el.div( + "You can update your card details directly inside your account settings dashboard under the billing tab.", + class_name="py-3 text-sm text-muted-foreground leading-relaxed px-3", + ), + ), + default_open=False, + ), + class_name="w-full max-w-sm", + ) diff --git a/native/lib/components/collapsible_nested.py b/native/lib/components/collapsible_nested.py new file mode 100644 index 0000000..2b391c6 --- /dev/null +++ b/native/lib/components/collapsible_nested.py @@ -0,0 +1,75 @@ +import reflex as rx + +from components.core.hugeicon import hi +from components.ui.collapsible import collapsible + + +def file_item(name: str, root_level: bool = False) -> rx.Component: + padding_class = "pl-2" if root_level else "pl-9" + + return rx.el.div( + hi("DocumentCodeIcon", class_name="size-4 text-muted-foreground/70 shrink-0"), + rx.el.span(name, class_name="text-sm tracking-tight text-foreground/80"), + class_name=f"flex items-center gap-2 {padding_class} py-1 hover:bg-muted/40 rounded-md cursor-pointer", + ) + + +def folder_item(name: str, *children: rx.Component) -> rx.Component: + return collapsible.root( + collapsible.trigger( + hi("FolderIcon", class_name="size-4 text-muted-foreground shrink-0"), + rx.el.span(name, class_name="text-sm font-medium truncate"), + hi( + "ArrowRight01Icon", + class_name="size-3.5 ml-auto text-muted-foreground/70 [details[open]>summary_&]:rotate-90", + ), + class_name="w-full justify-start gap-2 h-8 px-2 rounded-md font-normal hover:bg-muted/60", + ), + collapsible.panel( + rx.el.div( + *children, + class_name="pl-4 border-l border-border/60 ml-4 mt-0.5 flex flex-col gap-0.5", + ), + ), + class_name="w-full", + ) + + +def collapsible_nested() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.span( + "WORKSPACE EXPLORER", + class_name="text-[10px] font-bold tracking-wider text-muted-foreground", + ), + class_name="px-2 pb-2 mb-1 border-b border-border/40", + ), + folder_item( + "src", + folder_item( + "components", + folder_item( + "ui", + file_item("button.py"), + file_item("card.py"), + file_item("collapsible.py"), + ), + file_item("navbar.py"), + file_item("sidebar.py"), + ), + folder_item( + "state", + file_item("base_state.py"), + file_item("auth_state.py"), + ), + file_item("main.py"), + ), + folder_item( + "public", + file_item("favicon.ico"), + file_item("logo.svg"), + ), + file_item("rxconfig.py", root_level=True), + file_item("requirements.txt", root_level=True), + class_name="w-full max-w-xs p-3 bg-background border border-input rounded-xl shadow-xs select-none", + ) diff --git a/native/registry/anatomy.py b/native/registry/anatomy.py index eddc84b..b136b06 100644 --- a/native/registry/anatomy.py +++ b/native/registry/anatomy.py @@ -48,6 +48,10 @@ )""", "checkbox": """checkbox.root( checkbox.indicator(), +)""", + "collapsible": """collapsible.root( + collapsible.trigger(), + collapsible.panel(), )""", "dialog": """dialog.root( dialog.trigger(), diff --git a/native/registry/anatomy/collapsible.py b/native/registry/anatomy/collapsible.py new file mode 100644 index 0000000..1a6494b --- /dev/null +++ b/native/registry/anatomy/collapsible.py @@ -0,0 +1,6 @@ +from components.ui.collapsible import collapsible + +COMPOSITION = collapsible.root( + collapsible.trigger(), + collapsible.panel(), +) diff --git a/native/registry/components.py b/native/registry/components.py index e0caff1..f4e87be 100644 --- a/native/registry/components.py +++ b/native/registry/components.py @@ -58,6 +58,10 @@ "files": ["components/ui/checkbox.py"], "dependencies": ["core", "hugeicon"], }, + "collapsible": { + "files": ["components/ui/collapsible.py"], + "dependencies": ["core"], + }, "dialog": { "files": ["components/ui/dialog.py"], "dependencies": ["core"], @@ -108,7 +112,7 @@ }, "table": { "files": ["components/ui/table.py"], - "dependencies": ["core"], + "dependencies": ["core", "input"], }, "tabs": { "files": ["components/ui/tabs.py"], @@ -118,4 +122,54 @@ "files": ["components/ui/textarea.py"], "dependencies": ["core"], }, + + # --- native/lib/blocks --- + "area_chart_01": { + "files": ["native/lib/blocks/area_chart_01.py"], + "dependencies": ["chart_tooltip"], + }, + "area_chart_02": { + "files": ["native/lib/blocks/area_chart_02.py"], + "dependencies": ["chart_tooltip"], + }, + "bar_chart_01": { + "files": ["native/lib/blocks/bar_chart_01.py"], + "dependencies": ["chart_tooltip", "checkbox", "field"], + }, + "bar_chart_02": { + "files": ["native/lib/blocks/bar_chart_02.py"], + "dependencies": ["chart_tooltip"], + }, + "bar_chart_03": { + "files": ["native/lib/blocks/bar_chart_03.py"], + "dependencies": ["chart_tooltip"], + }, + "bar_chart_04": { + "files": ["native/lib/blocks/bar_chart_04.py"], + "dependencies": ["chart_tooltip"], + }, + "kpi_card_01": { + "files": ["native/lib/blocks/kpi_card_01.py"], + "dependencies": ["card"], + }, + "kpi_card_02": { + "files": ["native/lib/blocks/kpi_card_02.py"], + "dependencies": ["card", "chart_tooltip"], + }, + "line_chart_01": { + "files": ["native/lib/blocks/line_chart_01.py"], + "dependencies": ["card", "chart_tooltip"], + }, + "line_chart_02": { + "files": ["native/lib/blocks/line_chart_02.py"], + "dependencies": ["card", "chart_tooltip"], + }, + "line_chart_03": { + "files": ["native/lib/blocks/line_chart_03.py"], + "dependencies": ["card", "chart_tooltip"], + }, + "line_chart_04": { + "files": ["native/lib/blocks/line_chart_04.py"], + "dependencies": ["card", "chart_tooltip"], + }, } diff --git a/native/templates/sidebar.py b/native/templates/sidebar.py index cd7a6d2..9a24ded 100644 --- a/native/templates/sidebar.py +++ b/native/templates/sidebar.py @@ -25,7 +25,7 @@ } """ -NEW_COMP = ["Attachment", "Bubble", "Marker", "Message", "Shimmer", "Scroll Fade"] +NEW_COMP = [] @dataclass @@ -38,10 +38,10 @@ class SidebarSection: SIDEBAR_SECTIONS = [ SidebarSection(title="Getting Started", routes=routes.GET_STARTED_URLS), - SidebarSection(title="Utilities", routes=routes.UTILITIES), SidebarSection(title="Resources", routes=routes.RESOURCES_URLS), - SidebarSection(title="Charts", routes=routes.CHARTS_URLS), SidebarSection(title="Components", routes=routes.BASE_UI_COMPONENTS), + SidebarSection(title="Charts", routes=routes.CHARTS_URLS), + SidebarSection(title="Utilities", routes=routes.UTILITIES), ] @@ -78,7 +78,7 @@ def create_menu_item(data: dict): rx.el.div(class_name="size-2 rounded-full bg-blue-500"), ), variant="ghost", - class_name="w-fit flex items-center", + class_name="w-fit flex items-center !text-[0.8rem]", id=data["url"], ) diff --git a/scripts/generate_preview_cards.py b/scripts/generate_preview_cards.py index b693d77..54e461d 100644 --- a/scripts/generate_preview_cards.py +++ b/scripts/generate_preview_cards.py @@ -228,7 +228,7 @@ def create_social_card( img.save( output_path, format="WEBP", - quality=80, + quality=50, )