Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ type PageHeaderAlign = "center" | "start";
type PageHeaderSize = "display" | "lg" | "md" | "sm";

const buttonVariants = ["outline", "fill", "secondary", "primary", "text"] as const;
const iconButtonVariants = ["quiet", "fill", "primary"] as const;
const iconButtonVariants = ["quiet", "outline", "fill", "primary"] as const;
const iconButtonSizes = ["xs", "standard", "sm", "md", "lg"] as const;
const buttonInspectorStates = ["default", "hover", "active"] as const;
const fieldOrientations = ["vertical", "horizontal"] as const;
const pageHeaderAlignments = ["start", "center"] as const;
Expand Down Expand Up @@ -309,6 +310,8 @@ export function ComponentDetailPage({
const { t } = useI18n();
const stateLabel = (state: string) => optionLabelKeys[state] ? t(optionLabelKeys[state]) : state;
const [variant, setVariant] = useState<(typeof buttonVariants)[number]>("fill");
const [iconButtonSize, setIconButtonSize] = useState<(typeof iconButtonSizes)[number]>("xs");
const [iconButtonShape, setIconButtonShape] = useState<"square" | "circle">("square");
const [iconButtonVariant, setIconButtonVariant] = useState<(typeof iconButtonVariants)[number]>("quiet");
const [iconName, setIconName] = useState<IconName>("search");
const [numberBadgeValue, setNumberBadgeValue] = useState("18");
Expand Down Expand Up @@ -519,7 +522,7 @@ export function ComponentDetailPage({

if (component.name === "IconButton") {
const stateProps = `${inspectorDisabled ? " disabled" : ""}${inspectorLoading ? " loading" : ""}`;
return `import { IconButton } from "@openbitfun/ui";\nimport { List } from "lucide-react";\n\n<IconButton\n aria-label="${t("components.preview.listView")}"\n icon={<List />}\n variant="${iconButtonVariant}"${stateProps}\n/>`;
return `import { IconButton } from "@openbitfun/ui";\nimport { List } from "lucide-react";\n\n<IconButton\n aria-label="${t("components.preview.listView")}"\n icon={<List />}\n variant="${iconButtonVariant}"\n size="${iconButtonSize}"\n shape="${iconButtonShape}"${stateProps}\n/>`;
}
if (component.name === "Field") {
const labelAction = fieldShowLabelAction
Expand Down Expand Up @@ -633,6 +636,8 @@ export function ComponentDetailPage({
fieldShowLabelAction,
flowChatPreview,
iconButtonVariant,
iconButtonSize,
iconButtonShape,
iconName,
iconSize,
iconTone,
Expand Down Expand Up @@ -854,7 +859,8 @@ export function ComponentDetailPage({
disabled={state === "disabled" || applyInspectorControls && inspectorDisabled}
icon={<List aria-hidden="true" />}
loading={state === "loading" || applyInspectorControls && inspectorLoading}
size={size}
size={iconButtonSize}
shape={iconButtonShape}
variant={previewVariant}
/>
);
Expand Down Expand Up @@ -2393,6 +2399,12 @@ export function ComponentDetailPage({
value={iconButtonVariant}
/>
)}
{component.name === "IconButton" && (
<>
<InspectorSelect label={t("detail.size")} options={iconButtonSizes} value={iconButtonSize} onChange={(value) => setIconButtonSize(value as (typeof iconButtonSizes)[number])} translateOptions={false} />
<InspectorSelect label="shape" options={["square", "circle"]} value={iconButtonShape} onChange={(value) => setIconButtonShape(value as "square" | "circle")} translateOptions={false} />
</>
)}
{component.name === "Field" && (
<InspectorSelect
label={t("detail.orientation")}
Expand Down Expand Up @@ -2486,7 +2498,7 @@ export function ComponentDetailPage({
value={scrollAreaOrientation}
/>
)}
{(component.name === "Button" || component.name === "IconButton") && (
{component.name === "Button" && (
<InspectorSelect
label={t("detail.size")}
onChange={(value) => setSize(value as PreviewSize)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ export function IconCompositionPreview() {
<section className="component-icon-composition" aria-label={t("detail.iconComposition")}>
<h3>{t("detail.iconComposition")}</h3>
<p>{t("detail.iconCompositionHint")}</p>
{(["panel", "tertiary"] as const).map(surface => (
<div className="component-icon-composition__surface" data-surface={surface} key={surface}>
{(["outline", "primary"] as const).flatMap(variant => [false, true].map(disabled => (
<div className="component-icon-composition__row" key={`${variant}-${disabled}`}>
<code>{variant} / {disabled ? "disabled" : "default"}</code>
<Button variant={variant} disabled={disabled} leadingIcon={<Icon name="session" />} trailingIcon={<Icon name="chevron-down" />}>{label}</Button>
<Button variant={variant} disabled={disabled} leadingIcon={<SessionIcon aria-hidden="true" />} trailingIcon={<Icon name="chevron-right" />}>{label}</Button>
</div>
)))}
{(["default", "hover", "active", "disabled"] as const).map(state => (
<div className="component-icon-composition__row" key={state}>
<code>IconButton / {state}</code>
<IconButton aria-label={`22px / ${state}`} icon={<Icon name="session" />} size="xs" disabled={state === "disabled"} data-openbitfun-preview-state={state} />
<IconButton aria-label={`30px / ${state}`} icon={<Icon name="session" />} size="standard" shape="circle" variant="outline" disabled={state === "disabled"} data-openbitfun-preview-state={state} />
</div>
))}
</div>
))}
{(["xs", "sm", "md", "lg"] as const).map(size => (
<div className="component-icon-composition__row" key={size}>
<code>Button / {size}</code>
Expand Down
11 changes: 11 additions & 0 deletions design-system/apps/design-lab/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3911,6 +3911,17 @@ input.lab-force-focus {
font-size: var(--openbitfun-type-body-xs-font-size);
}

.component-icon-composition__surface {
display: grid;
gap: var(--openbitfun-space-4);
padding: var(--openbitfun-space-4);
background: var(--openbitfun-color-surface-panel);
}

.component-icon-composition__surface[data-surface="tertiary"] {
background: var(--openbitfun-color-surface-tertiary);
}

.component-icon-catalog__item {
display: grid;
min-block-size: 88px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,13 @@ test("IconButton preview exposes its icon-only presentation contract", async ()
assert.ok(declaration);
assert.deepEqual(
[...declaration[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]),
["quiet", "fill", "primary"],
["quiet", "outline", "fill", "primary"],
);
assert.match(source, /data-component="icon-button"/);
assert.match(source, /aria-label=\{t\("components\.preview\.listView"\)\}/);
assert.match(source, /icon=\{<List aria-hidden="true" \/>\}/);
assert.match(source, /size=\{iconButtonSize\}/);
assert.match(source, /shape=\{iconButtonShape\}/);
});

test("Icon preview exposes the complete named catalog and semantic controls", async () => {
Expand Down
7 changes: 6 additions & 1 deletion design-system/packages/design-tokens/src/system.tokens.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"$description": "Theme-independent system scales.",
"opacity": {
"iconArtwork": { "$type": "number", "$value": 0.8 }
},
"space": {
"$type": "dimension",
"0": { "$value": "0px" },
Expand Down Expand Up @@ -620,7 +623,9 @@
"iconButton": {
"$type": "dimension",
"xsSize": { "$value": "22px" },
"xsIconSize": { "$value": "14px" }
"xsIconSize": { "$value": "14px" },
"standardSize": { "$value": "30px" },
"standardIconSize": { "$value": "16px" }
},
"button": {
"$type": "dimension",
Expand Down
14 changes: 14 additions & 0 deletions design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@ focus ring. Use it for toolbar, dialog, and row utilities. `fill` and `primary`
keep an opaque backing surface for persistent emphasis. Disabled quiet actions
remain transparent and do not show hover or pressed feedback.

Use `size="xs"` for 22px square controls with 14px glyphs and a 4px radius.
`size="standard" shape="circle" variant="outline"` provides the 30px outlined
circle with a 16px glyph. Quiet and outline controls use the shared neutral
hover surface for both hover and pressed states; outline keeps its border when
disabled. Existing sm/md/lg sizes and the default sm size remain available.

The 62 reviewed single-path, single-tone masks have opaque paths.
`Icon` and `SessionIcon` retain their original 80% artwork opacity standalone;
Button, IconButton, ActionItem and TabGroup slots own this opacity in controls
through the public `--openbitfun-opacity-icon-artwork` contract. Button trailing
slots use half the content opacity and restore full disabled content opacity.
The progress-25 and legacy turn assets retain their internal transparency.
Product callers should not add opacity or dimensions inside these owned slots.

The catalog uses exported vectors, including their view boxes and per-path
opacity. Theme colors remain caller-owned through `currentColor`. Asset
fingerprints are reviewed with intentional resource updates so replacing a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const actionItemMeta = {
],
states: ["default", "hover", "active", "focus-visible", "disabled", "danger"],
tokens: [
"opacity.iconArtwork",
"color.action.neutral.content",
"color.action.neutral.contentDisabled",
"color.action.neutral.surface",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
}

.leading {
--openbitfun-opacity-icon-artwork: 1;
display: inline-flex;
flex: 0 0 auto;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const buttonMeta = {
],
states: ["default", "hover", "active", "disabled"],
tokens: [
"opacity.iconArtwork",
"component.button.content",
"component.button.outlineBorder",
"component.button.outlineBorderInteractive",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@
}

.icon {
--openbitfun-opacity-icon-artwork: 1;
display: inline-flex;
flex: 0 0 auto;
align-items: center;
Expand All @@ -258,6 +259,11 @@
.trailingIcon {
inline-size: var(--_button-trailing-icon-size);
block-size: var(--_button-trailing-icon-size);
opacity: 0.5;
}

.button:disabled .trailingIcon {
opacity: 1;
}

.progress {
Expand Down
1 change: 1 addition & 0 deletions design-system/packages/ui/src/components/Icon/Icon.meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const iconMeta = {
],
states: ["default"],
tokens: [
"opacity.iconArtwork",
"color.content.primary",
"color.content.secondary",
"color.content.muted",
Expand Down
5 changes: 5 additions & 0 deletions design-system/packages/ui/src/components/Icon/Icon.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
background-color: transparent;
}

/* Reviewed monochrome paths keep authored opacity outside control slots. */
.icon[data-openbitfun-artwork="monochrome"] {
opacity: var(--openbitfun-opacity-icon-artwork);
}

.icon[data-openbitfun-source="line"] > svg {
display: block;
inline-size: 100%;
Expand Down
1 change: 1 addition & 0 deletions design-system/packages/ui/src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export const Icon = forwardRef<HTMLSpanElement, IconProps>(function Icon({
data-openbitfun-component="icon"
data-openbitfun-name={name}
data-openbitfun-source={name ? "catalog" : "line"}
data-openbitfun-artwork={name && name !== "progress-25" && name !== "turn" ? "monochrome" : undefined}
data-openbitfun-tone={tone}
data-size={size}
ref={ref}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading