From 036dbd84b835069f47597bffc38ebd31db416e0a Mon Sep 17 00:00:00 2001 From: Aaron Reisman Date: Mon, 6 Jul 2026 22:10:45 +0700 Subject: [PATCH 1/4] Refine Propel slot component APIs --- .../accordion/accordion-trigger.tsx | 18 +- .../accordion/accordion.stories.tsx | 16 +- .../alert-dialog/alert-dialog-close.tsx | 10 +- .../alert-dialog/alert-dialog-trigger.tsx | 10 +- .../alert-dialog/alert-dialog.stories.tsx | 73 +++---- .../anchor-button/anchor-button.stories.tsx | 49 +++-- .../anchor-button/anchor-button.tsx | 27 +-- .../autocomplete-field.stories.tsx | 11 +- .../autocomplete/autocomplete-input-group.tsx | 7 +- .../autocomplete/autocomplete.stories.tsx | 43 ++-- .../src/components/badge/badge.stories.tsx | 47 +++-- .../propel/src/components/badge/badge.tsx | 23 +- .../src/components/banner/banner.stories.tsx | 57 +++-- .../propel/src/components/banner/banner.tsx | 24 ++- .../breadcrumb-ellipsis-trigger.tsx | 12 +- .../breadcrumb/breadcrumb-menu-trigger.tsx | 20 +- .../breadcrumb/breadcrumb.stories.tsx | 37 ++-- .../src/components/button/button.stories.tsx | 88 +++----- .../propel/src/components/button/button.tsx | 29 ++- .../components/checkbox/checkbox.stories.tsx | 14 +- .../src/components/checkbox/checkbox.tsx | 12 +- .../combobox-field/combobox-field.stories.tsx | 21 +- .../combobox/combobox-input-group.tsx | 7 +- .../src/components/combobox/combobox-item.tsx | 12 +- .../components/combobox/combobox.stories.tsx | 61 +++--- .../context-menu-checkbox-item.tsx | 21 +- .../context-menu/context-menu-item.tsx | 25 +-- .../context-menu/context-menu-link-item.tsx | 25 +-- .../context-menu/context-menu-radio-item.tsx | 21 +- .../context-menu-submenu-trigger.tsx | 23 +- .../context-menu/context-menu.stories.tsx | 122 +++++------ .../src/components/dialog/dialog-close.tsx | 13 +- .../src/components/dialog/dialog-trigger.tsx | 10 +- .../src/components/dialog/dialog.stories.tsx | 155 ++++++-------- .../src/components/drawer/drawer-close.tsx | 10 +- .../src/components/drawer/drawer-trigger.tsx | 10 +- .../src/components/drawer/drawer.stories.tsx | 137 ++++++++---- .../src/components/form/form.stories.tsx | 43 ++-- .../icon-button/icon-button.stories.tsx | 15 +- .../components/icon-button/icon-button.tsx | 20 +- packages/propel/src/components/icon/index.tsx | 39 ++++ .../input-field/input-field.stories.tsx | 13 +- .../components/input-field/input-field.tsx | 9 +- .../src/components/list/list.stories.tsx | 26 +-- .../components/menu/menu-checkbox-item.tsx | 24 ++- .../propel/src/components/menu/menu-item.tsx | 28 +-- .../src/components/menu/menu-link-item.tsx | 24 ++- .../src/components/menu/menu-radio-item.tsx | 24 ++- .../components/menu/menu-submenu-trigger.tsx | 29 ++- .../src/components/menu/menu.stories.tsx | 199 +++++++----------- .../components/menubar/menubar.stories.tsx | 60 +++--- .../navigation-menu-trigger.tsx | 8 +- .../navigation-menu.stories.tsx | 6 +- .../number-field/number-field.stories.tsx | 31 +-- .../src/components/pagination/pagination.tsx | 5 +- .../propel/src/components/pill/icon-pill.tsx | 17 +- .../src/components/pill/pill-switch.tsx | 17 +- .../src/components/pill/pill.stories.tsx | 73 +++---- packages/propel/src/components/pill/pill.tsx | 21 +- .../components/popover/popover.stories.tsx | 57 ++--- .../preview-card/preview-card.stories.tsx | 7 +- .../src/components/radio/radio.stories.tsx | 11 +- .../components/select-field/select-field.tsx | 14 +- .../propel/src/components/select/index.tsx | 2 +- .../src/components/select/select-item.tsx | 16 +- .../src/components/select/select-trigger.tsx | 15 +- .../src/components/select/select.stories.tsx | 24 +-- .../src/components/slider/slider.stories.tsx | 11 +- .../src/components/switch/switch.stories.tsx | 11 +- .../src/components/table/table-cell.tsx | 13 +- .../src/components/table/table-head.tsx | 10 +- .../src/components/table/table.stories.tsx | 75 +++---- packages/propel/src/components/tabs/tab.tsx | 21 +- .../src/components/tabs/tabs.stories.tsx | 46 ++-- .../src/components/toast/toast.stories.tsx | 11 +- .../toggle-group/toggle-group.stories.tsx | 41 ++-- .../src/components/toggle/toggle.stories.tsx | 41 ++-- .../propel/src/components/toggle/toggle.tsx | 9 +- .../src/components/toolbar/toolbar-button.tsx | 9 +- .../src/components/toolbar/toolbar-link.tsx | 12 +- .../toolbar/toolbar-menu-trigger.tsx | 13 +- .../src/components/toolbar/toolbar-toggle.tsx | 9 +- .../components/toolbar/toolbar.stories.tsx | 147 ++++--------- .../components/tooltip/tooltip.stories.tsx | 20 +- .../propel/src/components/tooltip/tooltip.tsx | 14 +- packages/propel/src/internal/icon.tsx | 3 + .../propel/src/patterns/comment.stories.tsx | 90 +++----- .../propel/src/patterns/sidebar.stories.tsx | 97 +++------ .../workspace-settings-sidebar.stories.tsx | 10 +- .../propel/src/storybook/icon-control.tsx | 38 ++-- 90 files changed, 1430 insertions(+), 1498 deletions(-) create mode 100644 packages/propel/src/components/icon/index.tsx diff --git a/packages/propel/src/components/accordion/accordion-trigger.tsx b/packages/propel/src/components/accordion/accordion-trigger.tsx index b4964ddb..b27b9c86 100644 --- a/packages/propel/src/components/accordion/accordion-trigger.tsx +++ b/packages/propel/src/components/accordion/accordion-trigger.tsx @@ -7,14 +7,18 @@ import { AccordionTriggerTitle, } from "../../elements/accordion"; import { DisclosureIndicator } from "../../internal/disclosure-indicator"; -import { Icon } from "../../internal/icon"; -export type AccordionTriggerProps = Omit & { +export type AccordionTriggerProps = Omit< + BaseAccordion.Trigger.Props, + "children" | "className" | "style" +> & { /** - * Node rendered before the label (inline-start), matching the Figma header icon. Sized to the - * trigger's `--node-size`. Decorative, kept out of the name. + * Element rendered before the label (inline-start), e.g. ``. */ icon?: React.ReactNode; + /** Visible trigger label. */ + label: React.ReactNode; }; /** @@ -22,11 +26,11 @@ export type AccordionTriggerProps = Omit}> - {icon ? {icon} : null} - {children} + {icon} + {label} diff --git a/packages/propel/src/components/accordion/accordion.stories.tsx b/packages/propel/src/components/accordion/accordion.stories.tsx index cf002fb8..fa686893 100644 --- a/packages/propel/src/components/accordion/accordion.stories.tsx +++ b/packages/propel/src/components/accordion/accordion.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { CircleHelp } from "lucide-react"; import { expect } from "storybook/test"; +import { Icon } from "../icon"; import { Accordion, AccordionHeader, @@ -75,7 +76,7 @@ export const Default: Story = { {ITEMS.map((item) => ( - {item.label} + {item.body} @@ -91,7 +92,10 @@ export const WithIcon: Story = { {ITEMS.map((item) => ( - }>{item.label} + } + label={item.label} + /> {item.body} @@ -131,7 +135,7 @@ export const States: Story = { ).map(([label, id]) => ( - {label} — What is Plane? + Plane is an open-source project management tool. @@ -149,7 +153,7 @@ export const MultipleItems: Story = { {ITEMS.map((item) => ( - {item.label} + {item.body} @@ -168,7 +172,7 @@ export const Interaction: Story = { - Section A + Panel A content @@ -239,7 +243,7 @@ export const KeyboardToggle: Story = { - Section A + Panel A content diff --git a/packages/propel/src/components/alert-dialog/alert-dialog-close.tsx b/packages/propel/src/components/alert-dialog/alert-dialog-close.tsx index 712b9e0e..ced28284 100644 --- a/packages/propel/src/components/alert-dialog/alert-dialog-close.tsx +++ b/packages/propel/src/components/alert-dialog/alert-dialog-close.tsx @@ -7,9 +7,13 @@ export type AlertDialogCloseProps = Omit}> - * Cancel - * ; + * ; + * + label="Delete project" + /> @@ -89,18 +88,16 @@ export const Default: Story = { tone="neutral" magnitude="xl" render={} - > - Cancel - + label="Cancel" + /> + label="Delete" + /> @@ -152,13 +149,12 @@ export const OpenFromMenu: Story = { tone="neutral" magnitude="xl" render={} - > - Project options - + label="Project options" + /> - Rename - Duplicate - setConfirmOpen(true)}>Delete project… + + + setConfirmOpen(true)} label="Delete project…" /> @@ -182,18 +178,16 @@ export const OpenFromMenu: Story = { tone="neutral" magnitude="xl" render={} - > - Cancel - + label="Cancel" + /> + label="Delete" + /> @@ -259,9 +253,8 @@ export const CloseConfirmation: Story = { tone="neutral" magnitude="xl" render={} - > - Write a comment - + label="Write a comment" + /> @@ -289,9 +282,8 @@ export const CloseConfirmation: Story = { tone="neutral" magnitude="xl" render={} - > - Close - + label="Close" + /> @@ -313,9 +305,8 @@ export const CloseConfirmation: Story = { tone="neutral" magnitude="xl" render={} - > - Keep writing - + label="Keep writing" + /> + label="Discard" + /> @@ -411,9 +401,8 @@ export const MultipleTriggers: Story = { tone="neutral" magnitude="xl" render={} - > - Delete {project} - + label={`Delete ${project}`} + /> ))} @@ -440,18 +429,16 @@ export const MultipleTriggers: Story = { tone="neutral" magnitude="xl" render={} - > - Cancel - + label="Cancel" + /> + label="Delete" + /> )} diff --git a/packages/propel/src/components/anchor-button/anchor-button.stories.tsx b/packages/propel/src/components/anchor-button/anchor-button.stories.tsx index 0008282d..7dfa459d 100644 --- a/packages/propel/src/components/anchor-button/anchor-button.stories.tsx +++ b/packages/propel/src/components/anchor-button/anchor-button.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { ArrowUpRight, Plus } from "lucide-react"; import { expect, fn } from "storybook/test"; +import { Icon } from "../icon"; import { AnchorButton, type AnchorButtonMagnitude, type AnchorButtonProminence } from "./index"; const PROMINENCES: AnchorButtonProminence[] = ["primary", "secondary"]; @@ -12,7 +13,7 @@ const MAGNITUDES: AnchorButtonMagnitude[] = ["sm", "md", "lg", "xl"]; const meta = { title: "Components/AnchorButton", component: AnchorButton, - args: { children: "Show more", prominence: "primary", magnitude: "md" }, + args: { label: "Show more", prominence: "primary", magnitude: "md" }, } satisfies Meta; export default meta; @@ -22,13 +23,16 @@ export const Default: Story = {}; /** `prominence`: `primary` is the blue link; `secondary` the muted gray inline link. */ export const Prominences: Story = { - argTypes: { prominence: { control: false }, children: { control: false } }, + argTypes: { prominence: { control: false }, label: { control: false } }, render: (args) => (
{PROMINENCES.map((prominence) => ( - - {prominence} action - + ))}
), @@ -36,13 +40,11 @@ export const Prominences: Story = { /** Text sizes (Figma S/Base/L/XL map to sm/md/lg/xl). */ export const Magnitudes: Story = { - argTypes: { magnitude: { control: false }, children: { control: false } }, + argTypes: { magnitude: { control: false }, label: { control: false } }, render: (args) => (
{MAGNITUDES.map((magnitude) => ( - - {magnitude} - + ))}
), @@ -67,9 +69,12 @@ export const ActsNotNavigates: Story = { export const AsLink: Story = { args: { prominence: "primary", magnitude: "md" }, render: (args) => ( - }> - View all work items - + } + label="View all work items" + /> ), }; @@ -94,12 +99,18 @@ export const WithIcons: Story = { parameters: { controls: { disable: true } }, render: () => (
- }> - Add condition - - }> - View docs - + } + label="Add condition" + /> + } + label="View docs" + />
), }; @@ -107,5 +118,5 @@ export const WithIcons: Story = { /** The `loading` spinner replaces the leading icon and dims the label. */ export const Loading: Story = { args: { prominence: "primary", magnitude: "md", loading: true }, - render: (args) => Saving…, + render: (args) => , }; diff --git a/packages/propel/src/components/anchor-button/anchor-button.tsx b/packages/propel/src/components/anchor-button/anchor-button.tsx index 28f5bfb2..c3576888 100644 --- a/packages/propel/src/components/anchor-button/anchor-button.tsx +++ b/packages/propel/src/components/anchor-button/anchor-button.tsx @@ -7,28 +7,23 @@ import { AnchorButtonLabel, type AnchorButtonProps as AnchorButtonElementProps, } from "../../elements/anchor-button"; -import { Icon } from "../../internal/icon"; import { Spinner } from "../../internal/spinner"; export type { AnchorButtonMagnitude, AnchorButtonProminence } from "../../elements/anchor-button"; -export type AnchorButtonProps = AnchorButtonElementProps & { +export type AnchorButtonProps = Omit & { /** * Set `false` when `render` swaps the underlying tag away from ` + label="Open command palette" + /> } + icon={} placeholder="Search commands" aria-label="Search commands" /> diff --git a/packages/propel/src/components/badge/badge.stories.tsx b/packages/propel/src/components/badge/badge.stories.tsx index 026015fa..aaf470aa 100644 --- a/packages/propel/src/components/badge/badge.stories.tsx +++ b/packages/propel/src/components/badge/badge.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { Check, Sparkles } from "lucide-react"; import { iconControl } from "../../storybook/icon-control"; +import { Icon } from "../icon"; import { Badge, BadgeLabel, type BadgeMagnitude, type BadgeTone } from "./index"; const TONES: BadgeTone[] = [ @@ -31,7 +32,7 @@ const meta = { subcomponents: { BadgeLabel }, argTypes: { startIcon: iconControl, endIcon: iconControl }, args: { - children: "Badge", + label: "Badge", tone: "neutral", magnitude: "md", }, @@ -50,13 +51,11 @@ export const Default: Story = {}; /** Every color/intent the badge supports, side by side. */ export const Tones: Story = { - argTypes: { tone: { control: false }, children: { control: false } }, + argTypes: { tone: { control: false }, label: { control: false } }, render: (args) => (
{TONES.map((tone) => ( - - {tone} - + ))}
), @@ -64,13 +63,11 @@ export const Tones: Story = { /** The three sizes (Figma S / Base / Large). */ export const Magnitudes: Story = { - argTypes: { magnitude: { control: false }, children: { control: false } }, + argTypes: { magnitude: { control: false }, label: { control: false } }, render: (args) => (
{MAGNITUDES.map((magnitude) => ( - - {magnitude} - + ))}
), @@ -82,9 +79,14 @@ export const WithLeadingIcon: Story = { render: (args) => (
{MAGNITUDES.map((magnitude) => ( - }> - Done - + } + label="Done" + /> ))}
), @@ -96,9 +98,14 @@ export const WithTrailingIcon: Story = { render: (args) => (
{MAGNITUDES.map((magnitude) => ( - }> - Pro - + } + label="Pro" + /> ))}
), @@ -129,9 +136,13 @@ export const PlanBadges: Story = { {(["paid", "free"] as const).map((plan) => (
{MAGNITUDES.map((magnitude) => ( - - {plan === "paid" ? "Paid" : "Free"} - + ))}
))} diff --git a/packages/propel/src/components/badge/badge.tsx b/packages/propel/src/components/badge/badge.tsx index 319b052e..c82a77c2 100644 --- a/packages/propel/src/components/badge/badge.tsx +++ b/packages/propel/src/components/badge/badge.tsx @@ -5,20 +5,13 @@ import { type BadgeProps as BadgeElementProps, BadgeLabel, } from "../../elements/badge"; -import { Icon } from "../../internal/icon"; -export type BadgeProps = BadgeElementProps & { +export type BadgeProps = Omit & { /** The badge label — text, a count, or any inline content. */ - children?: React.ReactNode; - /** - * Node rendered before the label (inline-start). A bare icon or any node; sized to the badge's - * `--node-size` and tinted to the tone. Decorative, kept out of the name. - */ + label?: React.ReactNode; + /** Element rendered before the label (inline-start), e.g. ``. */ startIcon?: React.ReactNode; - /** - * Node rendered after the label (inline-end). A bare icon or any node; sized to the badge's - * `--node-size` and tinted to the tone. Decorative, kept out of the name. - */ + /** Element rendered after the label (inline-end), e.g. ``. */ endIcon?: React.ReactNode; }; @@ -26,12 +19,12 @@ export type BadgeProps = BadgeElementProps & { * The ready-made badge: composes the atomic `Badge` pill with the `BadgeLabel` and optional leading * (`startIcon`) and trailing (`endIcon`) icon slots. */ -export function Badge({ children, startIcon, endIcon, ...props }: BadgeProps) { +export function Badge({ label, startIcon, endIcon, ...props }: BadgeProps) { return ( - {startIcon ? {startIcon} : null} - {children} - {endIcon ? {endIcon} : null} + {startIcon} + {label} + {endIcon} ); } diff --git a/packages/propel/src/components/banner/banner.stories.tsx b/packages/propel/src/components/banner/banner.stories.tsx index d95324ba..9345b01c 100644 --- a/packages/propel/src/components/banner/banner.stories.tsx +++ b/packages/propel/src/components/banner/banner.stories.tsx @@ -4,6 +4,7 @@ import { expect, fn } from "storybook/test"; import { iconControl } from "../../storybook/icon-control"; import { Button } from "../button/index"; +import { Icon } from "../icon"; import { IconButton } from "../icon-button/index"; import { Banner, type BannerTone } from "./index"; @@ -67,24 +68,35 @@ export const WithActions: Story = { tone: "neutral", actions: ( <> - - - + + - - + + - - + - - + + /> ); }, }; @@ -155,12 +144,8 @@ export const Stretch: Story = { parameters: { controls: { disable: true } }, render: (args) => (
- - +
), }; @@ -318,9 +303,7 @@ export const CustomTag: Story = { sizing: "hug", }, render: (args) => ( - + + /> ); }, play: async ({ canvas, userEvent }) => { @@ -372,9 +354,7 @@ export const AsLink: Story = { sizing: "hug", }, render: (args) => ( - +
+ + label="Cancel" + /> + label="Create" + />
diff --git a/packages/propel/src/components/context-menu/context-menu-checkbox-item.tsx b/packages/propel/src/components/context-menu/context-menu-checkbox-item.tsx index 2b00364a..737287a5 100644 --- a/packages/propel/src/components/context-menu/context-menu-checkbox-item.tsx +++ b/packages/propel/src/components/context-menu/context-menu-checkbox-item.tsx @@ -9,18 +9,19 @@ import { ContextMenuItemShortcut, type ContextMenuItemTone, } from "../../elements/context-menu"; -import { Icon } from "../../internal/icon"; export type ContextMenuCheckboxItemProps = Omit< BaseContextMenu.CheckboxItem.Props, - "className" | "style" + "children" | "className" | "label" | "style" > & { /** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */ tone: ContextMenuItemTone; - /** Leading icon after the tick. */ + /** Leading element after the tick, e.g. ``. */ icon?: React.ReactNode; - /** Trailing keyboard-shortcut hint after the label. */ - trailing?: React.ReactNode; + /** Primary row label. */ + label: React.ReactNode; + /** Element rendered at the inline end of the row, often a keyboard shortcut. */ + endContent?: React.ReactNode; }; /** @@ -31,8 +32,8 @@ export type ContextMenuCheckboxItemProps = Omit< export function ContextMenuCheckboxItem({ tone, icon, - trailing, - children, + endContent, + label, ...props }: ContextMenuCheckboxItemProps) { return ( @@ -46,9 +47,9 @@ export function ContextMenuCheckboxItem({ > - {icon != null ? {icon} : null} - {children} - {trailing != null ? {trailing} : null} + {icon} + {label} + {endContent != null ? {endContent} : null} ); } diff --git a/packages/propel/src/components/context-menu/context-menu-item.tsx b/packages/propel/src/components/context-menu/context-menu-item.tsx index 9cc3fae9..96ba46e0 100644 --- a/packages/propel/src/components/context-menu/context-menu-item.tsx +++ b/packages/propel/src/components/context-menu/context-menu-item.tsx @@ -9,40 +9,41 @@ import { type ContextMenuItemTone, ContextMenuItemShortcut, } from "../../elements/context-menu"; -import { Icon } from "../../internal/icon"; export type ContextMenuItemProps = Omit< BaseContextMenu.Item.Props, - "className" | "style" | "render" + "children" | "className" | "label" | "style" | "render" > & { /** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */ tone: ContextMenuItemTone; - /** Leading icon before the label. */ + /** Leading element before the label, e.g. ``. */ icon?: React.ReactNode; - /** Trailing keyboard-shortcut hint after the label. */ - trailing?: React.ReactNode; + /** Primary row label. */ + label: React.ReactNode; + /** Element rendered at the inline end of the row, often a keyboard shortcut. */ + endContent?: React.ReactNode; /** Single-select selected state. */ selected?: boolean; }; /** * The ready-made menu row: grafts Base UI's `Item` behavior onto the styled `ContextMenuItem` and - * composes its region parts — a leading icon, the label, an optional trailing shortcut hint, and a - * trailing check for single-select selected state. Pass `tone="danger"` for destructive actions. + * composes its region parts — a leading icon, the label, optional end content, and a trailing check + * for single-select selected state. Pass `tone="danger"` for destructive actions. */ export function ContextMenuItem({ tone, icon, - trailing, + endContent, selected, - children, + label, ...props }: ContextMenuItemProps) { return ( }> - {icon != null ? {icon} : null} - {children} - {trailing != null ? {trailing} : null} + {icon} + {label} + {endContent != null ? {endContent} : null} {selected ? ( diff --git a/packages/propel/src/components/context-menu/context-menu-link-item.tsx b/packages/propel/src/components/context-menu/context-menu-link-item.tsx index 5cc6c56a..cd6aa2b5 100644 --- a/packages/propel/src/components/context-menu/context-menu-link-item.tsx +++ b/packages/propel/src/components/context-menu/context-menu-link-item.tsx @@ -7,37 +7,38 @@ import { type ContextMenuItemTone, ContextMenuItemShortcut, } from "../../elements/context-menu"; -import { Icon } from "../../internal/icon"; export type ContextMenuLinkItemProps = Omit< BaseContextMenu.LinkItem.Props, - "className" | "style" | "render" + "children" | "className" | "label" | "style" | "render" > & { /** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */ tone: ContextMenuItemTone; - /** Leading icon before the label. */ + /** Leading element before the label, e.g. ``. */ icon?: React.ReactNode; - /** Trailing hint after the label. */ - trailing?: React.ReactNode; + /** Primary row label. */ + label: React.ReactNode; + /** Element rendered at the inline end of the row. */ + endContent?: React.ReactNode; }; /** * The ready-made navigational menu row: grafts Base UI's `LinkItem` behavior onto the styled - * `ContextMenuLinkItem` and composes its region parts — a leading icon, the label, and an optional - * trailing hint. + * `ContextMenuLinkItem` and composes its region parts — a leading icon, the label, and optional end + * content. */ export function ContextMenuLinkItem({ tone, icon, - trailing, - children, + endContent, + label, ...props }: ContextMenuLinkItemProps) { return ( }> - {icon != null ? {icon} : null} - {children} - {trailing != null ? {trailing} : null} + {icon} + {label} + {endContent != null ? {endContent} : null} ); } diff --git a/packages/propel/src/components/context-menu/context-menu-radio-item.tsx b/packages/propel/src/components/context-menu/context-menu-radio-item.tsx index 91eda3bb..e6692504 100644 --- a/packages/propel/src/components/context-menu/context-menu-radio-item.tsx +++ b/packages/propel/src/components/context-menu/context-menu-radio-item.tsx @@ -9,18 +9,19 @@ import { ContextMenuRadioItem as ContextMenuRadioItemElement, ContextMenuRadioItemIndicator, } from "../../elements/context-menu"; -import { Icon } from "../../internal/icon"; export type ContextMenuRadioItemProps = Omit< BaseContextMenu.RadioItem.Props, - "className" | "style" + "children" | "className" | "label" | "style" > & { /** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */ tone: ContextMenuItemTone; - /** Leading icon after the radio dot. */ + /** Leading element after the radio dot, e.g. ``. */ icon?: React.ReactNode; - /** Trailing keyboard-shortcut hint after the label. */ - trailing?: React.ReactNode; + /** Primary row label. */ + label: React.ReactNode; + /** Element rendered at the inline end of the row, often a keyboard shortcut. */ + endContent?: React.ReactNode; }; /** @@ -31,8 +32,8 @@ export type ContextMenuRadioItemProps = Omit< export function ContextMenuRadioItem({ tone, icon, - trailing, - children, + endContent, + label, ...props }: ContextMenuRadioItemProps) { return ( @@ -40,9 +41,9 @@ export function ContextMenuRadioItem({ }> - {icon != null ? {icon} : null} - {children} - {trailing != null ? {trailing} : null} + {icon} + {label} + {endContent != null ? {endContent} : null} ); } diff --git a/packages/propel/src/components/context-menu/context-menu-submenu-trigger.tsx b/packages/propel/src/components/context-menu/context-menu-submenu-trigger.tsx index 4b6a1bac..1941b212 100644 --- a/packages/propel/src/components/context-menu/context-menu-submenu-trigger.tsx +++ b/packages/propel/src/components/context-menu/context-menu-submenu-trigger.tsx @@ -9,30 +9,31 @@ import { type ContextMenuSubmenuTriggerTone, } from "../../elements/context-menu"; import { DisclosureIndicator } from "../../internal/disclosure-indicator"; -import { Icon } from "../../internal/icon"; export type ContextMenuSubmenuTriggerProps = Omit< BaseContextMenu.SubmenuTrigger.Props, - "className" | "style" | "render" + "children" | "className" | "label" | "style" | "render" > & { /** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */ tone: ContextMenuSubmenuTriggerTone; - /** Leading icon before the label. */ + /** Leading element before the label, e.g. ``. */ icon?: React.ReactNode; - /** Trailing hint before the submenu caret. */ - trailing?: React.ReactNode; + /** Primary row label. */ + label: React.ReactNode; + /** Element rendered before the submenu caret at the inline end of the row. */ + endContent?: React.ReactNode; }; /** * The ready-made submenu trigger: grafts Base UI's `SubmenuTrigger` behavior onto the styled * `ContextMenuSubmenuTrigger` and composes its region parts — a leading icon, the label, an - * optional trailing hint, and the caret that points toward the submenu. + * optional end content, and the caret that points toward the submenu. */ export function ContextMenuSubmenuTrigger({ tone, icon, - trailing, - children, + endContent, + label, ...props }: ContextMenuSubmenuTriggerProps) { return ( @@ -40,9 +41,9 @@ export function ContextMenuSubmenuTrigger({ {...props} render={} > - {icon != null ? {icon} : null} - {children} - {trailing != null ? {trailing} : null} + {icon} + {label} + {endContent != null ? {endContent} : null} diff --git a/packages/propel/src/components/context-menu/context-menu.stories.tsx b/packages/propel/src/components/context-menu/context-menu.stories.tsx index 31d4ebe8..281fd18a 100644 --- a/packages/propel/src/components/context-menu/context-menu.stories.tsx +++ b/packages/propel/src/components/context-menu/context-menu.stories.tsx @@ -12,6 +12,7 @@ import { import * as React from "react"; import { expect, fireEvent, userEvent, waitFor, within } from "storybook/test"; +import { Icon } from "../icon"; import { ContextMenu, ContextMenuContent, @@ -27,8 +28,8 @@ import { } from "./index"; // Components-tier story: the ready-made `ContextMenu` root and `ContextMenuContent` surface graft -// Base UI's portal/positioner/popup, and the rich `ContextMenuItem` lays out an icon + label + a -// trailing shortcut. `ContextMenuTrigger` is the behavior surface that opens the menu, and +// Base UI's portal/positioner/popup, and the rich `ContextMenuItem` lays out an icon + label + +// `endContent`. `ContextMenuTrigger` is the behavior surface that opens the menu, and // `ContextMenuSeparator` is the ready-made divider. The elements-tier story composes the raw // `ContextMenuItem` rows by hand. const meta = { @@ -59,19 +60,21 @@ export const Default: Story = { Right-click here - } trailing="⌘X"> - Cut - - } trailing="⌘C"> - Copy - - } trailing="⌘V"> - Paste - + } + endContent="⌘X" + label="Cut" + /> + } endContent="⌘C" label="Copy" /> + } + endContent="⌘V" + label="Paste" + /> - }> - Delete - + } label="Delete" /> ), @@ -101,16 +104,10 @@ export const Tones: Story = { Right-click here - }> - Rename - - }> - Duplicate - + } label="Rename" /> + } label="Duplicate" /> - }> - Delete - + } label="Delete" /> ), @@ -156,26 +153,22 @@ export const Submenu: Story = { Right-click here - }> - Rename - - }> - Duplicate - + } label="Rename" /> + } label="Duplicate" /> - }> - Move to - + } + label="Move to" + /> - Mobile app - Web app - Design system + + + - }> - Delete - + } label="Delete" /> ), @@ -219,20 +212,25 @@ export const LinkItems: Story = { } + icon={} href="#work-item" target="_blank" rel="noopener noreferrer" - > - Open in new tab - - } href="#activity"> - Go to activity - + label="Open in new tab" + /> + } + href="#activity" + label="Go to activity" + /> - } trailing="⌘C"> - Copy link - + } + endContent="⌘C" + label="Copy link" + /> ), @@ -288,9 +286,8 @@ export const SingleSelect: Story = { selected={sortBy === option.key} closeOnClick={false} onClick={() => setSortBy(option.key)} - > - {option.label} - + label={option.label} + /> ))} @@ -343,17 +340,22 @@ export const SelectionRows: Story = { checked={wrap} onCheckedChange={setWrap} closeOnClick={false} - > - Wrap titles - + label="Wrap titles" + /> - - Manual order - - - Last updated - + + diff --git a/packages/propel/src/components/dialog/dialog-close.tsx b/packages/propel/src/components/dialog/dialog-close.tsx index 8b916ea7..25da6a9b 100644 --- a/packages/propel/src/components/dialog/dialog-close.tsx +++ b/packages/propel/src/components/dialog/dialog-close.tsx @@ -7,11 +7,14 @@ export type DialogCloseProps = Omit}> - * - * - * - * ; + * } + * render={} + * />; * ``` * * Maps 1:1 to `Dialog.Close`. diff --git a/packages/propel/src/components/dialog/dialog-trigger.tsx b/packages/propel/src/components/dialog/dialog-trigger.tsx index 356d0b62..eb4d1397 100644 --- a/packages/propel/src/components/dialog/dialog-trigger.tsx +++ b/packages/propel/src/components/dialog/dialog-trigger.tsx @@ -7,9 +7,13 @@ export type DialogTriggerProps = Omit}> - * Open dialog - * ; + * + label="Delete project" + /> @@ -81,9 +81,8 @@ export const Default: Story = { magnitude="lg" aria-label="Close" render={} - > - - + icon={} + /> @@ -98,18 +97,16 @@ export const Default: Story = { tone="neutral" magnitude="xl" render={} - > - Cancel - + label="Cancel" + /> + label="Delete" + /> @@ -154,9 +151,8 @@ export const EscapeCloses: Story = { tone="neutral" magnitude="xl" render={} - > - Open settings - + label="Open settings" + /> @@ -194,9 +190,8 @@ export const NonDismissable: Story = { tone="neutral" magnitude="xl" render={} - > - Open locked dialog - + label="Open locked dialog" + /> @@ -215,9 +210,8 @@ export const NonDismissable: Story = { tone="neutral" magnitude="xl" render={} - > - Discard - + label="Discard" + /> @@ -274,9 +268,8 @@ export const ScrollableBody: Story = { tone="neutral" magnitude="xl" render={} - > - What's new - + label="What's new" + /> @@ -288,9 +281,8 @@ export const ScrollableBody: Story = { magnitude="lg" aria-label="Close" render={} - > - - + icon={} + /> {/* A scrollable region must be keyboard-reachable (axe scrollable-region-focusable). */} @@ -313,9 +305,8 @@ export const ScrollableBody: Story = { tone="neutral" magnitude="xl" render={} - > - Got it - + label="Got it" + /> @@ -368,16 +359,17 @@ export const OpenFromMenu: Story = { tone="neutral" magnitude="xl" render={} - > - Project options - + label="Project options" + /> - }>Edit - }>Copy link + } label="Edit" /> + } label="Copy link" /> - } onClick={() => setOpen(true)}> - Delete project… - + } + onClick={() => setOpen(true)} + label="Delete project…" + /> @@ -392,9 +384,8 @@ export const OpenFromMenu: Story = { magnitude="lg" aria-label="Close" render={} - > - - + icon={} + /> @@ -409,18 +400,16 @@ export const OpenFromMenu: Story = { tone="neutral" magnitude="xl" render={} - > - Cancel - + label="Cancel" + /> + label="Delete" + /> @@ -472,9 +461,8 @@ export const NestedDialogs: Story = { tone="neutral" magnitude="xl" render={} - > - Invite teammates - + label="Invite teammates" + /> @@ -486,9 +474,8 @@ export const NestedDialogs: Story = { magnitude="lg" aria-label="Close" render={} - > - - + icon={} + /> @@ -504,9 +491,8 @@ export const NestedDialogs: Story = { tone="neutral" magnitude="xl" render={} - > - Create custom role - + label="Create custom role" + /> @@ -525,9 +511,8 @@ export const NestedDialogs: Story = { tone="neutral" magnitude="xl" render={} - > - Back - + label="Back" + /> @@ -537,9 +522,8 @@ export const NestedDialogs: Story = { tone="neutral" magnitude="xl" render={} - > - Send invites - + label="Send invites" + /> @@ -610,9 +594,8 @@ export const CloseConfirmation: Story = { tone="neutral" magnitude="xl" render={} - > - Add comment - + label="Add comment" + /> @@ -624,9 +607,8 @@ export const CloseConfirmation: Story = { magnitude="lg" aria-label="Close" render={} - > - - + icon={} + /> - Post comment - + label="Post comment" + /> @@ -674,9 +655,8 @@ export const CloseConfirmation: Story = { tone="neutral" magnitude="xl" render={} - > - Go back - + label="Go back" + /> + label="Discard" + /> @@ -766,18 +745,16 @@ export const DetachedTriggers: Story = { tone="neutral" magnitude="xl" render={} - > - Keyboard shortcuts - + label="Keyboard shortcuts" + /> + label="Help" + /> @@ -790,9 +767,8 @@ export const DetachedTriggers: Story = { magnitude="lg" aria-label="Close" render={} - > - - + icon={} + /> @@ -807,9 +783,8 @@ export const DetachedTriggers: Story = { tone="neutral" magnitude="xl" render={} - > - Done - + label="Done" + /> diff --git a/packages/propel/src/components/drawer/drawer-close.tsx b/packages/propel/src/components/drawer/drawer-close.tsx index f463417e..f27b2e7d 100644 --- a/packages/propel/src/components/drawer/drawer-close.tsx +++ b/packages/propel/src/components/drawer/drawer-close.tsx @@ -10,9 +10,13 @@ export type DrawerCloseProps = Omit - * - * + * } + * /> * } * />; * ``` diff --git a/packages/propel/src/components/drawer/drawer-trigger.tsx b/packages/propel/src/components/drawer/drawer-trigger.tsx index 66ee3748..e6c5e3a8 100644 --- a/packages/propel/src/components/drawer/drawer-trigger.tsx +++ b/packages/propel/src/components/drawer/drawer-trigger.tsx @@ -10,9 +10,13 @@ export type DrawerTriggerProps = Omit - * Open details - * + * + + + + + + + + + + + label="Keep editing" + /> + label="Discard" + /> diff --git a/packages/propel/src/components/form/form.stories.tsx b/packages/propel/src/components/form/form.stories.tsx index e930d49a..e3b66cc4 100644 --- a/packages/propel/src/components/form/form.stories.tsx +++ b/packages/propel/src/components/form/form.stories.tsx @@ -168,9 +168,14 @@ export const Default: Story = { /> - + + + label="Create account" + /> ); @@ -338,9 +347,8 @@ export const ServerFunction: Story = { tone="neutral" magnitude="md" loading={pending} - > - Reserve username - + label="Reserve username" + /> ); @@ -406,9 +414,14 @@ export const SchemaValidation: Story = { /> - + + label="Options" + /> @@ -153,9 +152,8 @@ export const DisplayProperties: Story = { tone="neutral" magnitude="xl" render={} - > - Display - + label="Display" + /> Display Properties
@@ -165,9 +163,8 @@ export const DisplayProperties: Story = { magnitude="md" pressed={Boolean(pills[p])} onPressedChange={(next) => setPills((s) => ({ ...s, [p]: next }))} - > - {p} - + label={p} + /> ))}
@@ -233,9 +230,8 @@ export const DisplayAccordion: Story = { tone="neutral" magnitude="xl" render={} - > - Display options - + label="Display options" + /> {SECTIONS.map((title) => { const key = title.split(" ")[0].toLowerCase(); @@ -322,9 +318,8 @@ export const OpenOnHover: Story = { tone="neutral" magnitude="xl" render={} - > - Filters - + label="Filters" + /> Applied filters
@@ -379,9 +374,8 @@ export const DetachedTrigger: Story = { tone="neutral" magnitude="xl" render={} - > - Display - + label="Display" + />
{/* The popover: declared elsewhere, associated by `handle`. */} @@ -446,9 +440,8 @@ export const MultipleTriggers: Story = { tone="neutral" magnitude="xl" render={} - > - {member.name} - + label={member.name} + /> ))} @@ -522,18 +515,16 @@ export const Controlled: Story = { tone="neutral" magnitude="xl" render={} - > - Sort - + label="Sort" + /> + label="Filter" + /> {triggerId === "view-filter-trigger" ? ( <> @@ -562,9 +553,8 @@ export const Controlled: Story = { setTriggerId("view-filter-trigger"); setOpen(true); }} - > - Reopen filters - + label="Reopen filters" + /> ); }, @@ -645,9 +635,8 @@ export const RTL: Story = { tone="neutral" magnitude="xl" render={} - > - خيارات - + label="خيارات" + /> الترتيب حسب diff --git a/packages/propel/src/components/preview-card/preview-card.stories.tsx b/packages/propel/src/components/preview-card/preview-card.stories.tsx index a19a6f89..059f66d3 100644 --- a/packages/propel/src/components/preview-card/preview-card.stories.tsx +++ b/packages/propel/src/components/preview-card/preview-card.stories.tsx @@ -132,7 +132,7 @@ export const WithImageInteraction: Story = { }, }; -// Per-trigger payload content: the card resolves its body from the string payload the +// Per-trigger payload body: the card resolves its body from the string payload the // active trigger passed, so one card serves several launch points. const topics: Record = { cycles: { @@ -284,9 +284,8 @@ export const Controlled: Story = { setTriggerId("modules-trigger"); setOpen(true); }} - > - Preview modules - + label="Preview modules" + /> ); }, diff --git a/packages/propel/src/components/radio/radio.stories.tsx b/packages/propel/src/components/radio/radio.stories.tsx index ccd82b80..69ca6f5f 100644 --- a/packages/propel/src/components/radio/radio.stories.tsx +++ b/packages/propel/src/components/radio/radio.stories.tsx @@ -209,9 +209,14 @@ export const FormIntegration: Story = { - +