Skip to content

Commit a3b186b

Browse files
committed
Replace styled labels with explicit props
1 parent 6e48751 commit a3b186b

21 files changed

Lines changed: 197 additions & 59 deletions

packages/propel/src/components/checkbox/checkbox.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CheckboxIndeterminateIndicator,
88
CheckboxIndicator,
99
CheckboxLabel,
10+
type CheckboxLabelSizing,
1011
} from "../../elements/checkbox";
1112

1213
export type CheckboxProps = Omit<BaseCheckbox.Root.Props, "className" | "style" | "render"> & {
@@ -16,6 +17,8 @@ export type CheckboxProps = Omit<BaseCheckbox.Root.Props, "className" | "style"
1617
* or `aria-labelledby`.
1718
*/
1819
label?: React.ReactNode;
20+
/** Width of the clickable label row when `label` is present. */
21+
sizing?: CheckboxLabelSizing;
1922
/**
2023
* Element shown between the box and the label (inline-start), e.g. `<Icon icon={User}
2124
* tint="secondary" magnitude="sm" />`. Only rendered when `label` is present.
@@ -28,7 +31,7 @@ export type CheckboxProps = Omit<BaseCheckbox.Root.Props, "className" | "style"
2831
* check and indeterminate indicators, and optionally wraps the row in a clickable `CheckboxLabel`
2932
* with an icon slot.
3033
*/
31-
export function Checkbox({ label, icon, id, ...props }: CheckboxProps) {
34+
export function Checkbox({ label, sizing, icon, id, ...props }: CheckboxProps) {
3235
// Generate a stable id so an explicit `label` can be associated with the box.
3336
const generatedId = React.useId();
3437
const checkboxId = id ?? generatedId;
@@ -49,7 +52,7 @@ export function Checkbox({ label, icon, id, ...props }: CheckboxProps) {
4952
if (label == null) return box;
5053

5154
return (
52-
<CheckboxLabel htmlFor={checkboxId}>
55+
<CheckboxLabel htmlFor={checkboxId} sizing={sizing}>
5356
{box}
5457
{icon}
5558
{label}

packages/propel/src/components/dialog/dialog.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ export const OpenFromMenu: Story = {
366366
<MenuItem icon={<Icon icon={Link2} tint="secondary" />} label="Copy link" />
367367
<MenuSeparator />
368368
<MenuItem
369-
icon={<Icon icon={Trash2} tint="danger" />}
369+
tone="danger"
370+
icon={<Icon icon={Trash2} />}
370371
onClick={() => setOpen(true)}
371372
label="Delete project…"
372373
/>

packages/propel/src/components/menu/menu-checkbox-item.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ import {
1010
MenuItemMeta,
1111
MenuItemTitle,
1212
MenuItemTitleRow,
13+
type MenuItemTone,
1314
} from "../../elements/menu";
1415

1516
export type MenuCheckboxItemProps = Omit<
1617
BaseMenu.CheckboxItem.Props,
1718
"children" | "className" | "label" | "style"
1819
> & {
20+
/** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */
21+
tone?: MenuItemTone;
1922
/** Leading element shown after the checkbox, e.g. `<Icon icon={Settings} tint="secondary" />`. */
2023
icon?: React.ReactNode;
2124
/** Primary row label. */
@@ -32,9 +35,15 @@ export type MenuCheckboxItemProps = Omit<
3235
* state, so `checked` / `defaultChecked` / `onCheckedChange` forward straight to it and the
3336
* indicator reads it from context.
3437
*/
35-
export function MenuCheckboxItem({ icon, label, endContent, ...props }: MenuCheckboxItemProps) {
38+
export function MenuCheckboxItem({
39+
tone,
40+
icon,
41+
label,
42+
endContent,
43+
...props
44+
}: MenuCheckboxItemProps) {
3645
return (
37-
<BaseMenu.CheckboxItem {...props} render={<MenuCheckboxItemElement />}>
46+
<BaseMenu.CheckboxItem {...props} render={<MenuCheckboxItemElement tone={tone} />}>
3847
<MenuItemControl>
3948
<BaseMenu.CheckboxItemIndicator keepMounted render={<MenuCheckboxItemIndicator />}>
4049
<Check aria-hidden />

packages/propel/src/components/menu/menu-item.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type * as React from "react";
44

55
import {
66
MenuItem as MenuItemElement,
7+
type MenuItemTone,
78
MenuItemContent,
89
MenuItemDescription,
910
MenuItemSecondaryText,
@@ -17,6 +18,8 @@ export type MenuItemProps = Omit<
1718
BaseMenu.Item.Props,
1819
"children" | "className" | "label" | "style"
1920
> & {
21+
/** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */
22+
tone?: MenuItemTone;
2023
/** Leading element before the label, e.g. `<Icon icon={Settings} tint="secondary" />`. */
2124
icon?: React.ReactNode;
2225
/** Primary row label. */
@@ -37,6 +40,7 @@ export type MenuItemProps = Omit<
3740
* the single-select check indicator.
3841
*/
3942
export function MenuItem({
43+
tone,
4044
icon,
4145
description,
4246
secondaryText,
@@ -48,7 +52,7 @@ export function MenuItem({
4852
// The taller, top-aligned row layout is implied by having a description — derived, not a prop.
4953
const layout = description != null ? "with-description" : "default";
5054
return (
51-
<BaseMenu.Item {...props} render={<MenuItemElement layout={layout} />}>
55+
<BaseMenu.Item {...props} render={<MenuItemElement layout={layout} tone={tone} />}>
5256
{icon}
5357
<MenuItemContent>
5458
<MenuItemTitleRow>

packages/propel/src/components/menu/menu-link-item.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import {
77
MenuItemTitleRow,
88
MenuItemTrailing,
99
MenuLinkItem as MenuLinkItemElement,
10+
type MenuItemTone,
1011
} from "../../elements/menu";
1112

1213
export type MenuLinkItemProps = Omit<
1314
BaseMenu.LinkItem.Props,
1415
"children" | "className" | "label" | "style"
1516
> & {
17+
/** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */
18+
tone?: MenuItemTone;
1619
/** Leading element before the label, e.g. `<Icon icon={ExternalLink} tint="secondary" />`. */
1720
icon?: React.ReactNode;
1821
/** Primary row label. */
@@ -25,9 +28,9 @@ export type MenuLinkItemProps = Omit<
2528
* The ready-made navigational `<a>` menu row: grafts Base UI's `Menu.LinkItem` behavior onto the
2629
* styled `MenuLinkItem` and lays out an optional leading icon, label, and end content.
2730
*/
28-
export function MenuLinkItem({ icon, label, endContent, ...props }: MenuLinkItemProps) {
31+
export function MenuLinkItem({ tone, icon, label, endContent, ...props }: MenuLinkItemProps) {
2932
return (
30-
<BaseMenu.LinkItem {...props} render={<MenuLinkItemElement />}>
33+
<BaseMenu.LinkItem {...props} render={<MenuLinkItemElement tone={tone} />}>
3134
{icon}
3235
<MenuItemContent>
3336
<MenuItemTitleRow>

packages/propel/src/components/menu/menu-radio-item.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
MenuItemMeta,
99
MenuItemTitle,
1010
MenuItemTitleRow,
11+
type MenuItemTone,
1112
MenuRadioItem as MenuRadioItemElement,
1213
MenuRadioItemIndicator,
1314
} from "../../elements/menu";
@@ -16,6 +17,8 @@ export type MenuRadioItemProps = Omit<
1617
BaseMenu.RadioItem.Props,
1718
"children" | "className" | "label" | "style"
1819
> & {
20+
/** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */
21+
tone?: MenuItemTone;
1922
/** Leading element shown after the radio dot, e.g. `<Icon icon={Settings} tint="secondary" />`. */
2023
icon?: React.ReactNode;
2124
/** Primary row label. */
@@ -31,9 +34,9 @@ export type MenuRadioItemProps = Omit<
3134
* `MenuRadioGroup` carrying `value`/`onValueChange`; the dot reads the selected state from
3235
* context.
3336
*/
34-
export function MenuRadioItem({ icon, label, endContent, ...props }: MenuRadioItemProps) {
37+
export function MenuRadioItem({ tone, icon, label, endContent, ...props }: MenuRadioItemProps) {
3538
return (
36-
<BaseMenu.RadioItem {...props} render={<MenuRadioItemElement />}>
39+
<BaseMenu.RadioItem {...props} render={<MenuRadioItemElement tone={tone} />}>
3740
<MenuItemControl>
3841
<BaseMenu.RadioItemIndicator keepMounted render={<MenuRadioItemIndicator />}>
3942
<Circle aria-hidden />

packages/propel/src/components/menu/menu-submenu-trigger.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
MenuItemTitle,
88
MenuItemTitleRow,
99
MenuItemTrailing,
10+
type MenuItemTone,
1011
MenuSubmenuTrigger as MenuSubmenuTriggerElement,
1112
} from "../../elements/menu";
1213
import { DisclosureIndicator } from "../../internal/disclosure-indicator";
@@ -15,6 +16,8 @@ export type MenuSubmenuTriggerProps = Omit<
1516
BaseMenu.SubmenuTrigger.Props,
1617
"children" | "className" | "label" | "style"
1718
> & {
19+
/** Neutral rows use the standard text hierarchy; `danger` rows use the error palette. */
20+
tone?: MenuItemTone;
1821
/** Leading element before the label, e.g. `<Icon icon={Folder} tint="secondary" />`. */
1922
icon?: React.ReactNode;
2023
/** Primary row label. */
@@ -28,9 +31,15 @@ export type MenuSubmenuTriggerProps = Omit<
2831
* styled `MenuSubmenuTrigger` and lays out an optional leading icon, label, end content, and the
2932
* submenu chevron indicator.
3033
*/
31-
export function MenuSubmenuTrigger({ icon, endContent, label, ...props }: MenuSubmenuTriggerProps) {
34+
export function MenuSubmenuTrigger({
35+
tone,
36+
icon,
37+
endContent,
38+
label,
39+
...props
40+
}: MenuSubmenuTriggerProps) {
3241
return (
33-
<BaseMenu.SubmenuTrigger {...props} render={<MenuSubmenuTriggerElement />}>
42+
<BaseMenu.SubmenuTrigger {...props} render={<MenuSubmenuTriggerElement tone={tone} />}>
3443
{icon}
3544
<MenuItemContent>
3645
<MenuItemTitleRow>

packages/propel/src/components/menu/menu.stories.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,7 @@ export const ActionMenu: Story = {
344344
label="Archive"
345345
/>
346346
<MenuSeparator />
347-
<MenuItem
348-
icon={<Icon icon={Trash2} tint="danger" />}
349-
label={<span className="text-danger-primary">Delete</span>}
350-
/>
347+
<MenuItem tone="danger" icon={<Icon icon={Trash2} />} label="Delete" />
351348
</MenuContent>
352349
</Menu>
353350
),
@@ -1149,10 +1146,7 @@ export const DetachedTrigger: Story = {
11491146
<MenuItem icon={<Icon icon={Pencil} tint="secondary" />} label="Edit" />
11501147
<MenuItem icon={<Icon icon={Copy} tint="secondary" />} label="Make a copy" />
11511148
<MenuSeparator />
1152-
<MenuItem
1153-
icon={<Icon icon={Trash2} tint="danger" />}
1154-
label={<span className="text-danger-primary">Delete</span>}
1155-
/>
1149+
<MenuItem tone="danger" icon={<Icon icon={Trash2} />} label="Delete" />
11561150
</MenuContent>
11571151
</Menu>
11581152
</>

packages/propel/src/components/popover/popover.stories.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,16 @@ function ToggleFooter({ defaultToggles = {} }: { defaultToggles?: Record<string,
5858
return (
5959
<div className="flex flex-col">
6060
<Checkbox
61+
sizing="fill"
6162
checked={Boolean(toggles.sub)}
6263
onCheckedChange={(next) => setToggles((t) => ({ ...t, sub: Boolean(next) }))}
63-
label={<span className="flex-1">Show sub-work items</span>}
64+
label="Show sub-work items"
6465
/>
6566
<Checkbox
67+
sizing="fill"
6668
checked={Boolean(toggles.empty)}
6769
onCheckedChange={(next) => setToggles((t) => ({ ...t, empty: Boolean(next) }))}
68-
label={<span className="flex-1">Show empty groups</span>}
70+
label="Show empty groups"
6971
/>
7072
</div>
7173
);

packages/propel/src/components/table/table-head.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ const ariaSort: Record<TableHeadSort, React.AriaAttributes["aria-sort"]> = {
2828
export type TableHeadProps = Omit<TableHeadElementProps, "aria-sort" | "children" | "mode"> & {
2929
/** Header label. */
3030
label: React.ReactNode;
31-
/** Visual treatment. `sortable` renders the title as a sort-cycling button. */
31+
/** Visually hide the label while keeping it available to assistive tech. */
32+
visuallyHidden?: boolean;
3233
/** Whether this header is interactive sortable (renders a sort trigger + reflects `aria-sort`). */
3334
sortable?: boolean;
3435
/** Current sort state for a sortable header. @default "none" */
@@ -40,6 +41,7 @@ export type TableHeadProps = Omit<TableHeadElementProps, "aria-sort" | "children
4041
/** A ready-made header cell: a plain title, or (when sortable) a sort-cycling button with a chevron. */
4142
export function TableHead({
4243
label,
44+
visuallyHidden = false,
4345
sortable = false,
4446
sort = "none",
4547
onSort,
@@ -49,6 +51,7 @@ export function TableHead({
4951
const isSortable = sortable;
5052
const hasSortControl = isSortable && onSort != null;
5153
const SortGlyph = sortGlyph[sort];
54+
const titleVisibility = visuallyHidden ? "hidden" : "visible";
5255

5356
return (
5457
<TableHeadElement
@@ -58,13 +61,13 @@ export function TableHead({
5861
>
5962
{hasSortControl ? (
6063
<TableHeadSortTrigger onClick={onSort}>
61-
<TableHeadTitle>{label}</TableHeadTitle>
64+
<TableHeadTitle visibility={titleVisibility}>{label}</TableHeadTitle>
6265
<TableHeadSortIndicator>
6366
<SortGlyph />
6467
</TableHeadSortIndicator>
6568
</TableHeadSortTrigger>
6669
) : (
67-
<TableHeadTitle>{label}</TableHeadTitle>
70+
<TableHeadTitle visibility={titleVisibility}>{label}</TableHeadTitle>
6871
)}
6972
</TableHeadElement>
7073
);

0 commit comments

Comments
 (0)