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
6 changes: 6 additions & 0 deletions design-system/apps/design-lab/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ export const enUSMessages = {
"detail.livePlayground": "Live playground",
"detail.allStates": "Preview states",
"detail.preview": "Preview",
"detail.iconComposition": "Icons in components",
"detail.iconCompositionHint": "Catalog icons (left) and SVG icons (right) share slot sizes. Standalone icons below retain their own size.",
"detail.previewUnavailable": "Preview not implemented",
"components.preview.inputError": "Please check this value.",
"detail.code": "Code",
Expand Down Expand Up @@ -1105,6 +1107,8 @@ export const zhCNMessages = {
"detail.livePlayground": "实时工作台",
"detail.allStates": "状态预览",
"detail.preview": "预览",
"detail.iconComposition": "组件中的图标尺寸",
"detail.iconCompositionHint": "左侧为图标库图标,右侧为 SVG 图标,均使用组件插槽尺寸;下方独立图标保留自身尺寸。",
"detail.previewUnavailable": "此组件尚未实现预览",
"components.preview.inputError": "请检查输入内容。",
"detail.code": "代码",
Expand Down Expand Up @@ -1745,6 +1749,8 @@ export const zhTWMessages = {
"detail.livePlayground": "即時工作台",
"detail.allStates": "狀態預覽",
"detail.preview": "預覽",
"detail.iconComposition": "元件中的圖示尺寸",
"detail.iconCompositionHint": "左側為圖示庫圖示,右側為 SVG 圖示,皆使用元件插槽尺寸;下方獨立圖示保留自身尺寸。",
"detail.previewUnavailable": "此元件尚未實作預覽",
"components.preview.inputError": "請檢查輸入內容。",
"detail.code": "程式碼",
Expand Down
20 changes: 12 additions & 8 deletions design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {
} from "@bitfun/ui";
import type { ComponentMeta } from "@bitfun/ui/registry";
import previewImage from "../assets/design-system-hero.webp";
import { IconCompositionPreview } from "../preview/IconCompositionPreview";
import { NestedMenuPattern } from "./ReferencePatterns";
import { useI18n, type MessageKey } from "../i18n";
import {
Expand Down Expand Up @@ -1749,14 +1750,17 @@ export function ComponentDetailPage({
))}
</div>
) : component.name === "Icon" ? (
<div className="component-icon-catalog">
{iconNames.map((name) => (
<div className="component-icon-catalog__item" key={name}>
<Icon name={name} size="lg" />
<code>{name}</code>
</div>
))}
</div>
<>
<IconCompositionPreview />
<div className="component-icon-catalog">
{iconNames.map((name) => (
<div className="component-icon-catalog__item" key={name}>
<Icon name={name} size="lg" />
<code>{name}</code>
</div>
))}
</div>
</>
) : component.name === "IconButton" ? (
<div
className="component-preview-matrix"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ChevronRight, Settings } from "lucide-react";
import { Button, Icon, IconButton, Input, SessionIcon, TabGroup } from "@bitfun/ui";
import { useI18n } from "../i18n";

export function IconCompositionPreview() {
const { t } = useI18n();
const label = t("components.preview.settings");
return (
<section className="component-icon-composition" aria-label={t("detail.iconComposition")}>
<h3>{t("detail.iconComposition")}</h3>
<p>{t("detail.iconCompositionHint")}</p>
{(["xs", "sm", "md", "lg"] as const).map(size => (
<div className="component-icon-composition__row" key={size}>
<code>Button / {size}</code>
<Button size={size} leadingIcon={<Icon name="settings" />} trailingIcon={<Icon name="chevron-right" />}>{label}</Button>
<Button size={size} leadingIcon={<Settings />} trailingIcon={<ChevronRight />}>{label}</Button>
<IconButton size={size} aria-label={`Icon / ${size}`} icon={<Icon name="settings" />} />
<IconButton size={size} aria-label={`SVG / ${size}`} icon={<Settings />} />
</div>
))}
<div className="component-icon-composition__row">
<code>TabGroup</code>
<TabGroup
aria-label={t("components.preview.tabGroupLabel")}
defaultValue="session"
items={[
{ value: "session", label: t("components.preview.session"), icon: <SessionIcon /> },
{ value: "settings", label, icon: <Icon name="settings" /> },
{ value: "assistant", label: t("components.preview.assistant"), icon: <Icon name="user" /> },
]}
/>
</div>
<div className="component-icon-composition__row">
<code>Input</code>
<Input aria-label="Icon" leading={<Icon name="settings" />} trailing={<Icon name="chevron-right" />} defaultValue={label} />
<Input aria-label="SVG" leading={<Settings />} trailing={<ChevronRight />} defaultValue={label} />
</div>
</section>
);
}
32 changes: 32 additions & 0 deletions design-system/apps/design-lab/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3948,6 +3948,38 @@ input.lab-force-focus {
padding: var(--bf-space-5);
}

.component-icon-composition {
display: grid;
gap: var(--bf-space-4);
min-inline-size: 0;
padding: var(--bf-space-5);
border-block-end: var(--bf-border-width-default) solid var(--bf-color-border-subtle);
}

.component-icon-composition h3,
.component-icon-composition p {
margin: 0;
}

.component-icon-composition p {
color: var(--bf-color-content-muted);
font-size: var(--bf-font-size-small);
}

.component-icon-composition__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--bf-space-4);
min-inline-size: 0;
}

.component-icon-composition__row > code {
min-inline-size: 112px;
color: var(--bf-color-content-secondary);
font-size: var(--bf-font-size-caption);
}

.component-icon-catalog__item {
display: grid;
min-block-size: 88px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,16 @@ test("Switch is explicit and unknown components never silently become switches",
assert.match(preview, /此组件尚未实现预览/);
assert.doesNotMatch(preview, /data-bf-component="switch"/);
});

test("Icon details include real mixed-icon compositions at every button size", () => {
const { preview } = render("Icon");
assert.match(preview, /component-icon-composition/);
for (const size of ["xs", "sm", "md", "lg"]) {
assert.match(preview, new RegExp(`Button / ${size}`));
assert.match(preview, new RegExp(`aria-label="SVG / ${size}"`));
assert.match(preview, new RegExp(`aria-label="Icon / ${size}"`));
}
assert.match(preview, /data-bf-component="tab-group"/);
assert.match(preview, /data-bf-component="input"/);
assert.match(preview, /component-icon-catalog/);
});
6 changes: 6 additions & 0 deletions design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ uncontrolled open state, trigger/region accessibility wiring, focus exclusion
while collapsed, reduced-motion behavior, and independent header actions.
Product copy and the revealed content remain consumer-owned.

Sized icon slots in buttons, tabs, menu items and fields own their glyph geometry.
Pass catalog `Icon` nodes through `leadingIcon`, `trailingIcon`, `icon` or the
matching component slot, just as for SVG icons. These slots constrain catalog
icons to the component's size; a standalone `Icon` retains its explicit size
(24px by default). Do not shrink the catalog globally to correct a slot mismatch.

## Advanced selection and menus

Use native `Select` for simple options. `Combobox` adds search, grouped options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
color: var(--bf-color-content-secondary);
}

.leading > :where(svg, img) {
.leading > :where(svg, img),
.leading > [data-bf-component="icon"] {
display: block;
inline-size: var(--bf-control-action-card-icon-size);
block-size: var(--bf-control-action-card-icon-size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
block-size: var(--bf-font-size-small);
}

.leading > :where(svg, img) {
.leading > :where(svg, img),
.leading > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
justify-content: center;
}

.leading > :where(svg, img) {
.leading > :where(svg, img),
.leading > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.root[data-bf-shape="square"] { border-radius: var(--bf-radius-base); }
.image { inline-size: 100%; block-size: 100%; object-fit: cover; }
.content { display: inline-flex; align-items: center; justify-content: center; inline-size: 100%; block-size: 100%; }
.content > :where(svg, img) { inline-size: 55%; block-size: 55%; }
.content > :where(svg, img), .content > [data-bf-component="icon"] { inline-size: 55%; block-size: 55%; }
.group { display: inline-flex; align-items: center; }
.group > .root + .root { margin-inline-start: calc(var(--bf-space-2) * -1); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@
color: currentColor;
}

.icon > :where(svg, img) {
.icon > :where(svg, img),
.icon > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
.option { display: flex; align-items: center; gap: var(--bf-space-2); min-block-size: var(--bf-overlay-menu-item-height); padding: var(--bf-space-2) var(--bf-overlay-menu-item-padding-inline); border-radius: var(--bf-overlay-menu-item-radius); cursor: pointer; overflow-wrap: anywhere; }
.option[data-active="true"], .option[data-selected="true"] { background: var(--bf-color-action-neutral-surface); }
.option[aria-disabled="true"] { color: var(--bf-color-content-disabled); cursor: not-allowed; }
.option > svg { flex-shrink: 0; inline-size: var(--bf-overlay-menu-item-icon-size); block-size: var(--bf-overlay-menu-item-icon-size); }
.option > svg, .option > [data-bf-component="icon"] { flex-shrink: 0; inline-size: var(--bf-overlay-menu-item-icon-size); block-size: var(--bf-overlay-menu-item-icon-size); }
.copy { display: flex; flex-direction: column; flex: 1; min-inline-size: 0; }
.copy small { color: var(--bf-color-content-muted); }
.group, .empty { padding: var(--bf-space-2); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
background: var(--bf-color-status-info-surface);
}

.icon > svg {
.icon > svg,
.icon > [data-bf-component="icon"] {
inline-size: var(--bf-layout-confirm-dialog-icon-glyph-size);
block-size: var(--bf-layout-confirm-dialog-icon-glyph-size);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
block-size: calc(var(--bf-control-height-lg) + var(--bf-space-4));
}

.media > :where(svg, img) {
.media > :where(svg, img),
.media > [data-bf-component="icon"] {
display: block;
max-inline-size: 100%;
max-block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@
block-size: var(--_icon-button-icon-size);
}

.icon > :where(svg, img) {
.icon > :where(svg, img),
.icon > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@
}

.leading > :where(svg, img),
.trailing > :where(svg, img) {
.trailing > :where(svg, img),
.leading > [data-bf-component="icon"],
.trailing > [data-bf-component="icon"] {
display: block;
inline-size: var(--_field-icon-size);
block-size: var(--_field-icon-size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
min-inline-size: 0;
}

.icon > :where(svg, img) {
.icon > :where(svg, img),
.icon > [data-bf-component="icon"] {
display: block;
inline-size: 1em;
block-size: 1em;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const navigationPanelMeta = {
tokens: [
"color.surface.subtle",
"color.border.subtle",
"color.content.primary",
"color.content.muted",
"color.action.neutral.content",
"color.action.neutral.contentDisabled",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
block-size: 100%;
min-block-size: 0;
flex-direction: column;
color: var(--bf-color-action-neutral-content);
color: var(--bf-color-content-primary);
background: var(--bf-color-surface-subtle);
}

Expand Down Expand Up @@ -81,7 +81,8 @@
overflow: hidden;
flex: 1 1 auto;
min-inline-size: 0;
color: var(--bf-color-content-muted);
/* Group captions carry half the ink of destinations, not the action label tone. */
color: color-mix(in srgb, var(--bf-color-content-primary) 50%, transparent);
font-family: var(--bf-font-family-control);
font-size: var(--bf-layout-navigation-panel-heading-font-size);
font-weight: var(--bf-font-weight-medium);
Expand Down Expand Up @@ -109,6 +110,10 @@
border-radius: var(--bf-layout-navigation-panel-item-radius);
}

.item[data-bf-tone="neutral"]:not([data-disabled="true"]) {
color: var(--bf-color-content-primary);
}

.item > [data-bf-part="trigger"] {
gap: var(--bf-layout-navigation-panel-item-gap);
}
Expand All @@ -122,7 +127,7 @@
background: var(--bf-color-action-neutral-surface-pressed);
}

.item:has(> [data-bf-part="trigger"][aria-current]) [data-bf-part="label"] {
.item > [data-bf-part="trigger"][aria-current] > [data-bf-part="label"] {
font-weight: var(--bf-font-weight-semibold);
}

Expand All @@ -145,7 +150,22 @@
border-block-start: var(--bf-border-width-default) solid var(--bf-color-border-subtle);
}

:global([data-contrast="high"]) .headingLabel {
color: var(--bf-color-content-muted);
}

@media (prefers-contrast: more) {
.headingLabel {
color: var(--bf-color-content-muted);
}
}

@media (forced-colors: active) {
.headingLabel,
.item[data-bf-tone="neutral"]:not([data-disabled="true"]) {
color: CanvasText;
}

.item:has(> [data-bf-part="trigger"][aria-current]) {
color: HighlightText;
background: Highlight;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@
color: currentColor;
}

.icon > :where(svg, img) {
.icon > :where(svg, img),
.icon > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@
margin-inline-end: var(--bf-control-select-trailing-inset);
}

.leading > :where(svg, img, [data-bf-component="icon"]) {
.leading > :where(svg, img),
.leading > [data-bf-component="icon"],
.indicator > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
block-size: var(--bf-control-status-pill-icon-size);
}

.leading > :where(svg, img, [data-bf-component="icon"]) {
.leading > :where(svg, img),
.leading > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
color: currentColor;
}

.icon > :where(svg, img) {
.icon > :where(svg, img),
.icon > [data-bf-component="icon"] {
display: block;
inline-size: 100%;
block-size: 100%;
Expand Down
Loading
Loading