diff --git a/packages/propel/src/components/pill/index.tsx b/packages/propel/src/components/pill/index.tsx
index ccb97d34..4ed981cf 100644
--- a/packages/propel/src/components/pill/index.tsx
+++ b/packages/propel/src/components/pill/index.tsx
@@ -1,5 +1,5 @@
export * from "./icon-pill";
export * from "./pill";
export * from "./pill-switch";
-// Re-export the atomic elements parts so a fully custom pill is importable from this convenience.
+// Re-export PillLabel (+ magnitude types) for composition alongside the component wrappers.
export { PillLabel, type PillLabelProps, type PillMagnitude } from "../../elements/pill";
diff --git a/packages/propel/src/components/pill/pill.stories.tsx b/packages/propel/src/components/pill/pill.stories.tsx
index ae2d780a..25e1f0d4 100644
--- a/packages/propel/src/components/pill/pill.stories.tsx
+++ b/packages/propel/src/components/pill/pill.stories.tsx
@@ -10,6 +10,7 @@ const MAGNITUDES = ["sm", "md", "lg"] as const;
// Module-scope spies so the `play` function can assert against the same references the
// `render` wires to the buttons.
const clickSpies = { onClick: fn(), onLoadingClick: fn() };
+const iconPillLoadingSpy = fn();
const meta = {
title: "Components/Pill",
@@ -50,22 +51,91 @@ export const Magnitudes: Story = {
};
/**
- * `PillButton` states. Default / hover / active darken the chip's fill + border (hover and active
- * forced via the pseudo-states addon); `disabled` and `loading` drop to a transparent fill with a
- * dimmed label, and `loading` swaps the inline-start node for a spinner.
+ * `PillButton` states for both emphases. Hover / active forced via the pseudo-states addon;
+ * `disabled` and `loading` drop to a transparent fill with a dimmed label; `loading` shows a
+ * spinner after the label (Figma).
*/
export const States: Story = {
parameters: {
controls: { disable: true },
- pseudo: { hover: ["#pill-hover"], active: ["#pill-active"] },
+ pseudo: {
+ hover: ["#pill-outline-hover", "#pill-soft-hover"],
+ active: ["#pill-outline-active", "#pill-soft-active"],
+ },
},
+ render: () => (
+
+
+
}
+ label="outline"
+ />
+
}
+ label="outline hover"
+ />
+
}
+ label="outline active"
+ />
+
}
+ disabled
+ label="outline disabled"
+ />
+
+
+
+
} label="soft" />
+
}
+ label="soft hover"
+ />
+
}
+ label="soft active"
+ />
+
}
+ disabled
+ label="soft disabled"
+ />
+
+
+
+ ),
+};
+
+/** Both fill treatments: `outline` (≈ secondary) and `soft` (≈ tertiary). */
+export const Emphases: Story = {
+ parameters: { controls: { disable: true } },
render: () => (
-
} label="Default" />
-
} label="Hover" />
-
} label="Active" />
-
} disabled label="Disabled" />
-
+
}
+ label="outline"
+ />
+
} label="soft" />
),
};
@@ -75,11 +145,21 @@ export const States: Story = {
* choices (e.g. display properties in a settings menu).
*/
export const Switch: Story = {
- parameters: { controls: { disable: true } },
+ parameters: {
+ controls: { disable: true },
+ pseudo: { hover: ["#pill-switch-hover"] },
+ },
render: () => (
} label="Off" />
} defaultPressed label="On" />
+
}
+ label="Hover"
+ />
+
} disabled label="Disabled" />
),
};
@@ -107,8 +187,42 @@ export const Icons: Story = {
};
/**
- * Clicking a `PillButton` fires its handler, and a `loading` pill blocks the click while staying
- * focusable (`aria-busy`). Tagged out of the sidebar/docs/manifest but still run under `test`.
+ * Labels past the width cap (PillButton 150px, PillSwitch 120px) truncate with an ellipsis.
+ * `PillLabel` sets a native `title` from the string label so hover recovers the full text.
+ */
+export const TruncatedLabel: Story = {
+ parameters: { controls: { disable: true } },
+ render: () => (
+
+
}
+ label="Engineering Infrastructure Team"
+ />
+
}
+ label="Engineering Infrastructure Team"
+ />
+
+ ),
+ play: async ({ canvas }) => {
+ const buttons = canvas.getAllByRole("button", { name: "Engineering Infrastructure Team" });
+ await expect(buttons).toHaveLength(2);
+ for (const button of buttons) {
+ // Icon is also a ``; the label is the one carrying `title`.
+ const label = button.querySelector("span[title]");
+ await expect(label).toHaveAttribute("title", "Engineering Infrastructure Team");
+ await expect(label!.scrollWidth).toBeGreaterThan(label!.clientWidth);
+ }
+ },
+};
+
+/**
+ * Clicking a `PillButton` fires its handler. A `loading` pill is `aria-busy` + `aria-disabled` and
+ * blocks clicks, but stays focusable (NOT natively `disabled`) so assistive tech can announce busy.
+ * Spinner sits after the label (Figma). Tagged out of sidebar/docs/manifest; still run under
+ * `test`.
*/
export const ButtonClicks: Story = {
tags: ["!dev", "!autodocs", "!manifest"],
@@ -129,8 +243,23 @@ export const ButtonClicks: Story = {
const busy = canvas.getByRole("button", { name: "Busy" });
await expect(busy).toHaveAttribute("aria-busy", "true");
+ await expect(busy).toHaveAttribute("aria-disabled", "true");
+ // Soft-disabled: remains in the tab order and focusable.
+ await expect(busy).not.toBeDisabled();
+ busy.focus();
+ await expect(busy).toHaveFocus();
+
await userEvent.click(busy);
await expect(onLoadingClick).not.toHaveBeenCalled();
+
+ // Loading spinner sits after the label (Figma).
+ const label = busy.querySelector("span[title='Busy']");
+ const spinner = busy.querySelector("svg");
+ await expect(label).not.toBeNull();
+ await expect(spinner).not.toBeNull();
+ await expect(
+ label!.compareDocumentPosition(spinner!) & Node.DOCUMENT_POSITION_FOLLOWING,
+ ).toBeTruthy();
},
};
@@ -150,3 +279,36 @@ export const SwitchToggles: Story = {
await expect(toggle).toHaveAttribute("aria-pressed", "false");
},
};
+
+/**
+ * A `loading` `IconPill` is `aria-busy` + `aria-disabled` and blocks activation, but stays
+ * focusable (NOT natively `disabled`). Tagged out of sidebar/docs/manifest; still run under
+ * `test`.
+ */
+export const IconLoadingBlocks: Story = {
+ tags: ["!dev", "!autodocs", "!manifest"],
+ render: () => (
+ }
+ />
+ ),
+ play: async ({ canvas }) => {
+ iconPillLoadingSpy.mockClear();
+
+ const button = canvas.getByRole("button", { name: "Add item" });
+ await expect(button).toHaveAttribute("aria-busy", "true");
+ await expect(button).toHaveAttribute("aria-disabled", "true");
+ await expect(button).not.toBeDisabled();
+
+ button.focus();
+ await expect(button).toHaveFocus();
+ await userEvent.keyboard("{Enter}");
+ await userEvent.keyboard("[Space]");
+ await userEvent.click(button);
+ await expect(iconPillLoadingSpy).not.toHaveBeenCalled();
+ },
+};
diff --git a/packages/propel/src/components/pill/pill.tsx b/packages/propel/src/components/pill/pill.tsx
index f077921c..4eb284eb 100644
--- a/packages/propel/src/components/pill/pill.tsx
+++ b/packages/propel/src/components/pill/pill.tsx
@@ -3,31 +3,37 @@ import { LoaderCircle } from "lucide-react";
import type * as React from "react";
import {
+ type PillButtonEmphasis,
PillButton as PillButtonElement,
type PillButtonProps as PillButtonElementProps,
PillLabel,
} from "../../elements/pill";
import { Spinner } from "../../internal/spinner";
-export type PillButtonProps = Omit & {
+export type { PillButtonEmphasis };
+
+export type PillButtonProps = Omit & {
+ /** Fill treatment. Defaults to `outline` (≈ Button secondary). `soft` ≈ Button tertiary. */
+ emphasis?: PillButtonEmphasis;
/** Element rendered before the label (inline-start), e.g. ` `. */
startIcon?: React.ReactNode;
/** Element rendered after the label (inline-end), e.g. ` `. */
endIcon?: React.ReactNode;
/** Visible pill label. */
label: string;
- /** Busy state: swaps the inline-start node for a spinner and blocks clicks. */
+ /** Busy state: hides start/end icons, shows a spinner after the label, and blocks clicks. */
loading?: boolean;
};
/**
* The ready-made pill button: grafts Base UI's `Button` behavior onto the styled `PillButton`
- * container (behavior outer, the styled button as the render target), with an optional leading node
- * (or a spinner while `loading`), the `PillLabel`, and an optional trailing node. `loading`
- * disables the button while keeping it focusable (`aria-busy`).
+ * container (behavior outer, the styled button as the render target), with an optional leading
+ * node, the `PillLabel`, an optional trailing node, and a trailing spinner while `loading`.
+ * `loading` disables the button while keeping it focusable (`aria-busy`).
*/
export function PillButton({
magnitude,
+ emphasis = "outline",
startIcon,
endIcon,
loading = false,
@@ -38,20 +44,20 @@ export function PillButton({
return (
}
+ render={ }
disabled={disabled || loading}
focusableWhenDisabled={loading ? true : undefined}
aria-busy={loading ? true : undefined}
>
+ {!loading ? startIcon : null}
+ {label}
{loading ? (
) : (
- startIcon
+ endIcon
)}
- {label}
- {!loading ? endIcon : null}
);
}
diff --git a/packages/propel/src/elements/pill/icon-pill.tsx b/packages/propel/src/elements/pill/icon-pill.tsx
index 86e29936..6e0be481 100644
--- a/packages/propel/src/elements/pill/icon-pill.tsx
+++ b/packages/propel/src/elements/pill/icon-pill.tsx
@@ -7,9 +7,9 @@ export type IconPillProps = Omit, "className"
IconPillVariantProps;
/**
- * The icon-only square pill container: a styled `` with no baked content. Compose a
- * `PillIcon` (or `PillSpinner`) inside it. Base-UI-agnostic — graft the Base UI `Button` behavior
- * in `components` via `} />` (which also supplies the required
+ * The icon-only square pill container: a styled `` with no baked content. Compose an `Icon`
+ * (or `Spinner` while busy) inside it. Base-UI-agnostic — graft the Base UI `Button` behavior in
+ * `components` via `} />` (which also supplies the required
* `aria-label`).
*/
export function IconPill({ magnitude, render, ...props }: IconPillProps) {
diff --git a/packages/propel/src/elements/pill/pill-label.tsx b/packages/propel/src/elements/pill/pill-label.tsx
index 913ea7f2..5807e256 100644
--- a/packages/propel/src/elements/pill/pill-label.tsx
+++ b/packages/propel/src/elements/pill/pill-label.tsx
@@ -5,8 +5,16 @@ import { pillLabelVariants } from "./variants";
export type PillLabelProps = Omit, "className" | "style">;
-/** The pill's single-line label. Shrinks and truncates instead of wrapping. */
-export function PillLabel({ render, ...props }: PillLabelProps) {
- const defaultProps: useRender.ElementProps<"span"> = { className: pillLabelVariants() };
+/**
+ * The pill's single-line label. Shrinks and truncates instead of wrapping. When `children` is a
+ * string, bakes a native `title` so hover recovers the full text past the 120px cap; pass `title`
+ * explicitly to override (or `title=""` to suppress).
+ */
+export function PillLabel({ render, children, title, ...props }: PillLabelProps) {
+ const defaultProps: useRender.ElementProps<"span"> = {
+ className: pillLabelVariants(),
+ children,
+ title: title !== undefined ? title : typeof children === "string" ? children : undefined,
+ };
return useRender({ defaultTagName: "span", render, props: mergeProps(defaultProps, props) });
}
diff --git a/packages/propel/src/elements/pill/pill-switch.tsx b/packages/propel/src/elements/pill/pill-switch.tsx
index 2708f9c8..95c3b3f8 100644
--- a/packages/propel/src/elements/pill/pill-switch.tsx
+++ b/packages/propel/src/elements/pill/pill-switch.tsx
@@ -9,10 +9,10 @@ export type PillSwitchProps = Omit, "classNam
/**
* The toggle pill container. Renders a `` by default carrying the selected look as its
* pressed state (the cva styles `data-pressed`); compose a `PillLabel` and optional
- * leading/trailing `PillIcon` inside it. It does NOT embed any toggle behavior, keeping
- * `elements/pill` independent of the Toggle primitive: a switch composition grafts the behavior on
- * in `components` by rendering the styled button as the `Toggle`'s render target — `} />`.
+ * leading/trailing `Icon` inside it. It does NOT embed any toggle behavior, keeping `elements/pill`
+ * independent of the Toggle primitive: a switch composition grafts the behavior on in `components`
+ * by rendering the styled button as the `Toggle`'s render target — `}
+ * />`.
*/
export function PillSwitch({ magnitude, render, ...props }: PillSwitchProps) {
return useRender({
diff --git a/packages/propel/src/elements/pill/pill.stories.tsx b/packages/propel/src/elements/pill/pill.stories.tsx
index af8b8f0b..3477f52f 100644
--- a/packages/propel/src/elements/pill/pill.stories.tsx
+++ b/packages/propel/src/elements/pill/pill.stories.tsx
@@ -10,18 +10,18 @@ const MAGNITUDES: PillMagnitude[] = ["sm", "md", "lg"];
// elements-tier story (rule 2b): a pure UI-configuration showcase. The styled pill containers
// (PillButton / PillSwitch / IconPill) and inline parts (PillLabel, internal Icon/Spinner slots)
-// render DIRECTLY — no Base UI grafts — with every visual axis (`magnitude`) shown and every
+// render DIRECTLY — no Base UI grafts — with every visual axis (`magnitude` / `emphasis`) shown and every
// visual state pinned statically via the attributes the cvas key off: `disabled` is the native
-// attribute, busy pins the `aria-busy` (+ `aria-disabled`) the ready-made pills set while
-// `loading`, and the switch's selected look pins the `data-pressed=""`/`aria-pressed` Base UI's
-// `Toggle` would set. Hover / active / focus-visible are CSS pseudo-classes, forced by the
-// pseudo-states addon. Grafting, keyboard, and aria behavior are demonstrated AND tested in the
-// ready-made pills (Components/Pill), which compose these parts.
+// attribute; busy pins `aria-busy` (what drives the loading look) and may also set `aria-disabled`
+// to mirror ready-made a11y, though styles do not key off it; and the switch's selected look pins
+// the `data-pressed=""`/`aria-pressed` Base UI's `Toggle` would set. Hover / active / focus-visible
+// are CSS pseudo-classes, forced by the pseudo-states addon. Grafting, keyboard, and aria behavior
+// are demonstrated AND tested in the ready-made pills (Components/Pill), which compose these parts.
const meta = {
title: "Elements/Pill",
component: PillButton,
subcomponents: { PillSwitch, IconPill, PillLabel },
- args: { magnitude: "md", children: "Add label" },
+ args: { magnitude: "md", emphasis: "outline", children: "Add label" },
render: ({ children, ...props }) => (
@@ -53,7 +53,7 @@ export const Magnitudes: Story = {
render: () => (
{MAGNITUDES.map((magnitude) => (
-
+
@@ -64,64 +64,123 @@ export const Magnitudes: Story = {
),
};
-/**
- * Every visual state of `PillButton`, pinned statically. Hover / active / focus-visible are CSS
- * pseudo-classes forced by the pseudo-states addon; `disabled` is the native attribute the
- * `disabled:` palette keys off; busy pins the `aria-busy` (+ `aria-disabled`) the ready-made pill
- * sets while `loading` — it swaps the leading node for the internal `Spinner` slot and keeps the
- * button focusable instead of natively disabling it.
- */
-export const States: Story = {
- parameters: {
- controls: { disable: true },
- pseudo: {
- hover: ["#pill-button-hover"],
- active: ["#pill-button-active"],
- focusVisible: ["#pill-button-focus"],
- },
- },
+/** Both fill treatments: `outline` (≈ Button secondary) and `soft` (≈ Button tertiary). */
+export const Emphases: Story = {
+ parameters: { controls: { disable: true } },
render: () => (
-
+
- Default
-
-
-
-
-
- Hover
+ outline
-
+
- Active
-
-
-
-
-
- Focus
-
-
-
-
-
- Disabled
-
-
-
-
-
- Busy
+ soft
),
};
+/**
+ * Every visual state of `PillButton` for both emphases, pinned statically. Hover / active /
+ * focus-visible via the pseudo-states addon; `disabled` native; busy pins `aria-busy` (loading
+ * styles) plus `aria-disabled` for ready-made a11y parity, with spinner after the label (Figma).
+ */
+export const States: Story = {
+ parameters: {
+ controls: { disable: true },
+ pseudo: {
+ hover: ["#pill-button-outline-hover", "#pill-button-soft-hover"],
+ active: ["#pill-button-outline-active", "#pill-button-soft-active"],
+ focusVisible: ["#pill-button-outline-focus", "#pill-button-soft-focus"],
+ },
+ },
+ render: () => (
+
+
+
+
+
+
+ outline
+
+
+
+
+
+ outline hover
+
+
+
+
+
+ outline active
+
+
+
+
+
+ outline focus
+
+
+
+
+
+ outline disabled
+
+
+ outline busy
+
+
+
+
+
+
+
+
+
+
+ soft
+
+
+
+
+
+ soft hover
+
+
+
+
+
+ soft active
+
+
+
+
+
+ soft focus
+
+
+
+
+
+ soft disabled
+
+
+ soft busy
+
+
+
+
+
+
+ ),
+};
+
/**
* The `PillSwitch` container: the selected look is its pressed state, pinned here via the
* `data-pressed=""` / `aria-pressed` Base UI's `Toggle` sets on its rendered button. The toggle
@@ -165,8 +224,9 @@ export const Switch: Story = {
/**
* Icon-only `IconPill` containers. Require an `aria-label`. Second row pins the states: hover /
- * active / focus-visible via the pseudo-states addon, native `disabled`, and busy (`aria-busy` +
- * `aria-disabled` with the internal `Spinner` slot replacing the icon).
+ * active / focus-visible via the pseudo-states addon, native `disabled`, and busy (`aria-busy`
+ * drives loading styles; `aria-disabled` mirrors ready-made a11y; internal `Spinner` replaces the
+ * icon).
*/
export const Icons: Story = {
parameters: {
@@ -219,6 +279,38 @@ export const Icons: Story = {
),
};
+/**
+ * Labels past the width cap (PillButton 150px, PillSwitch 120px) truncate with an ellipsis.
+ * `PillLabel` bakes a native `title` from the string children so hover recovers the full text.
+ */
+export const TruncatedLabel: Story = {
+ parameters: { controls: { disable: true } },
+ render: () => (
+
+
+
+
+
+ Engineering Infrastructure Team – View
+
+
+
+
+
+ Engineering Infrastructure Team – Toggle
+
+
+ ),
+};
+
/**
* CSS canary (rule 2b): asserts the pinned `data-pressed` selector actually compiled — the pressed
* switch's fill (`data-pressed:bg-layer-2-selected`) computes differently from the resting fill
diff --git a/packages/propel/src/elements/pill/pill.tsx b/packages/propel/src/elements/pill/pill.tsx
index 6d3d5e02..2ac07155 100644
--- a/packages/propel/src/elements/pill/pill.tsx
+++ b/packages/propel/src/elements/pill/pill.tsx
@@ -3,20 +3,20 @@ import { useRender } from "@base-ui/react/use-render";
import { pillButtonVariants, type PillButtonVariantProps } from "./variants";
-export type { PillMagnitude } from "./variants";
+export type { PillButtonEmphasis, PillMagnitude } from "./variants";
export type PillButtonProps = Omit, "className" | "style"> &
PillButtonVariantProps;
/**
* The pill-shaped button container: a styled `` with no baked content. Compose a
- * `PillLabel` and optional leading/trailing `PillIcon` (or `PillSpinner`) inside it.
+ * `PillLabel` and optional leading/trailing `Icon` (or `Spinner` while busy) inside it.
* Base-UI-agnostic — graft the Base UI `Button` behavior in `components` via `} />`.
*/
-export function PillButton({ magnitude, render, ...props }: PillButtonProps) {
+export function PillButton({ magnitude, emphasis, render, ...props }: PillButtonProps) {
const defaultProps: useRender.ElementProps<"button"> = {
- className: pillButtonVariants({ magnitude }),
+ className: pillButtonVariants({ magnitude, emphasis }),
type: "button",
};
return useRender({ defaultTagName: "button", render, props: mergeProps(defaultProps, props) });
diff --git a/packages/propel/src/elements/pill/variants.ts b/packages/propel/src/elements/pill/variants.ts
index a3b1f68f..123fa98e 100644
--- a/packages/propel/src/elements/pill/variants.ts
+++ b/packages/propel/src/elements/pill/variants.ts
@@ -1,4 +1,4 @@
-import { cva, cx } from "class-variance-authority";
+import { cva, cx, type VariantProps } from "class-variance-authority";
import { type StrictVariantProps } from "../../internal/variant-props";
@@ -12,17 +12,18 @@ import { type StrictVariantProps } from "../../internal/variant-props";
// - Leading/trailing node position (inline-start / inline-end), sized per size step
//
// "Depends (adjustable)" → props: label (children), leading/trailing node, magnitude,
-// selected/unselected (PillSwitch pressed state), disabled, and which interactive part
-// (PillButton vs PillSwitch vs IconPill). magnitude has no sensible default, so it is a
-// required prop on every container part — no cva `defaultVariants`.
+// emphasis (PillButton outline|soft), selected/unselected (PillSwitch pressed state), disabled,
+// and which interactive part (PillButton vs PillSwitch vs IconPill). magnitude/emphasis have no
+// sensible default on elements, so they are required — no cva `defaultVariants`.
// Shared structural base baked into every pill container cva below.
const pillBase =
"inline-flex shrink-0 items-center justify-center rounded-md border-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-accent-strong";
-// Shared label-pill base (PillButton + PillSwitch): a 14px node scale, capped width, and
-// per-magnitude height/padding/font.
-const labelPillBase = cx(pillBase, "max-w-[120px] gap-1 py-1 [--node-size:0.875rem]");
+// Shared label-pill base (PillButton + PillSwitch): a 14px node scale and per-magnitude
+// height/padding/font. The truncation cap differs per container (PillButton 150px, PillSwitch
+// 120px per Figma), so it lives on each container cva, not here.
+const labelPillBase = cx(pillBase, "gap-1 py-1 [--node-size:0.875rem]");
const labelPillMagnitude = {
sm: "h-5 px-1.5 text-12",
@@ -33,26 +34,42 @@ const labelPillMagnitude = {
// ─── Containers (one styled element each) ────────────────────────────────────
export const pillButtonVariants = cva(
- [
- labelPillBase,
- "cursor-pointer border-subtle-1 bg-layer-2 text-secondary",
- "hover:border-strong hover:bg-layer-2-hover",
- "active:border-strong active:bg-layer-2-active active:text-primary",
- "disabled:cursor-not-allowed disabled:border-subtle-1 disabled:bg-layer-transparent disabled:text-disabled",
- "aria-busy:cursor-default aria-busy:border-subtle-1 aria-busy:bg-layer-transparent aria-busy:text-disabled",
- ],
+ [labelPillBase, "max-w-[150px] cursor-pointer text-secondary"],
{
- variants: { magnitude: labelPillMagnitude },
+ variants: {
+ magnitude: labelPillMagnitude,
+ // Figma PillButton mirrors Button prominence chrome (control-chrome):
+ // `outline` ≈ secondary (bordered + layer-2), `soft` ≈ tertiary (borderless + layer-3).
+ // Disabled/loading stay transparent per the pill Figma (not button's layer-disabled fill).
+ emphasis: {
+ outline: [
+ "border-subtle-1 bg-layer-2",
+ "hover:border-strong hover:bg-layer-2-hover",
+ "active:border-strong active:bg-layer-2-active active:text-primary",
+ "disabled:cursor-not-allowed disabled:border-subtle-1 disabled:bg-layer-transparent disabled:text-disabled",
+ "aria-busy:cursor-default aria-busy:border-subtle-1 aria-busy:bg-layer-transparent aria-busy:text-disabled",
+ ],
+ soft: [
+ "border-transparent bg-layer-3",
+ "hover:bg-layer-3-hover",
+ "active:bg-layer-3-active active:text-primary",
+ "disabled:cursor-not-allowed disabled:bg-layer-transparent disabled:text-disabled",
+ "aria-busy:cursor-default aria-busy:bg-layer-transparent aria-busy:text-disabled",
+ ],
+ },
+ },
},
);
// No `defaultVariants` today, so every axis is required.
export type PillButtonVariantProps = StrictVariantProps;
+type PillButtonVariantConfig = VariantProps;
+export type PillButtonEmphasis = NonNullable;
export const pillSwitchVariants = cva(
[
labelPillBase,
- "cursor-pointer border-subtle-1 bg-layer-2 text-secondary",
+ "max-w-[120px] cursor-pointer border-subtle-1 bg-layer-2 text-secondary",
"hover:border-strong hover:bg-layer-2-hover",
"data-pressed:border-strong data-pressed:bg-layer-2-selected data-pressed:text-primary",
"disabled:cursor-not-allowed disabled:border-subtle-1 disabled:bg-layer-transparent disabled:text-disabled",
@@ -94,6 +111,4 @@ export type IconPillVariantProps = StrictVariantProps;
// keeps it on one line per the Figma spec.
export const pillLabelVariants = cva("min-w-0 truncate");
-export type PillMagnitude = NonNullable<
- NonNullable[0]>["magnitude"]
->;
+export type PillMagnitude = NonNullable;