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
9 changes: 9 additions & 0 deletions design-system/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,12 @@ pnpm --dir src/web-ui exec vitest run src/shared/ui/Select.test.tsx
```

These package and DOM checks do not establish native rendering or live remote behavior.

For ColorPicker HEX drafts, flower selection, curved lightness dragging and keyboard input:

```bash
pnpm --dir design-system run build:ui && node --test design-system/packages/ui/tests/color-picker.test.mjs design-system/packages/ui/tests/registry.test.mjs
pnpm --dir src/web-ui run test:run src/shared/ui/ColorPicker.test.tsx src/app/components/WorkspaceIcon/WorkspaceIconDialog.test.tsx
```

These package and DOM checks do not establish visual fidelity or live remote behavior.
26 changes: 26 additions & 0 deletions design-system/apps/design-lab/src/i18n/colorPickerMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const colorPickerEn = {
'component.ColorPicker.description': 'An editable HEX pill and expandable flower palette with a curved lightness slider.',
'colorPicker.color': 'Color HEX',
'colorPicker.palette': 'Choose a color',
'colorPicker.edit': 'Edit color',
'colorPicker.apply': 'Apply color',
'colorPicker.lightness': 'Lightness',
} as const;

export const colorPickerZhCN = {
'component.ColorPicker.description': '可编辑的 HEX 胶囊输入与可展开的花瓣色盘,搭配弧形明度滑杆。',
'colorPicker.color': 'HEX 颜色值',
'colorPicker.palette': '选择颜色',
'colorPicker.edit': '编辑颜色',
'colorPicker.apply': '应用颜色',
'colorPicker.lightness': '明度',
} satisfies Record<keyof typeof colorPickerEn, string>;

export const colorPickerZhTW = {
'component.ColorPicker.description': '可編輯的 HEX 膠囊輸入與可展開的花瓣色盤,搭配弧形明度滑桿。',
'colorPicker.color': 'HEX 色彩值',
'colorPicker.palette': '選擇顏色',
'colorPicker.edit': '編輯顏色',
'colorPicker.apply': '套用顏色',
'colorPicker.lightness': '明度',
} satisfies Record<keyof typeof colorPickerEn, string>;
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const categoryKeys: Readonly<Record<string, MessageKey>> = {
};

const descriptionKeys: Readonly<Record<string, MessageKey>> = {
ColorPicker: "component.ColorPicker.description",
MobileAvatar: "component.MobileAvatar.description",
MobileCheckbox: "component.MobileCheckbox.description",
MobileRadioGroup: "component.MobileRadioGroup.description",
Expand Down
4 changes: 4 additions & 0 deletions design-system/apps/design-lab/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { subagentMotionEn, subagentMotionZhCN, subagentMotionZhTW } from "./suba
import { thinkingIndicatorEn, thinkingIndicatorZhCN, thinkingIndicatorZhTW } from "./thinkingIndicatorMessages";
import { splitViewEn, splitViewZhCN, splitViewZhTW } from "./splitViewMessages";
import { setupWizardEn, setupWizardZhCN, setupWizardZhTW } from "./setupWizardMessages";
import { colorPickerEn, colorPickerZhCN, colorPickerZhTW } from "./colorPickerMessages";
import type { DesignLabLocale } from "./core.mjs";

export const enUSMessages = {
Expand All @@ -18,6 +19,7 @@ export const enUSMessages = {
...thinkingIndicatorEn,
...splitViewEn,
...setupWizardEn,
...colorPickerEn,
...flowChatEn,
"component.OpenBitFunSolidMark.description": "The original silver solid mark on a transparent canvas, preserving its material and shading.",
"component.OpenBitFunAppIcon.description": "The application icon used by the installed product: silver mark, black rounded-square tile, and assets for different display sizes.",
Expand Down Expand Up @@ -841,6 +843,7 @@ export const zhCNMessages = {
...thinkingIndicatorZhCN,
...splitViewZhCN,
...setupWizardZhCN,
...colorPickerZhCN,
...flowChatZhCN,
"component.OpenBitFunSolidMark.description": "透明底的银色实体标志,保留原稿的材质、光泽与立体明暗。",
"component.OpenBitFunAppIcon.description": "实际应用使用的图标:银色实体标志、黑色圆角底板,以及适配不同显示尺寸的资源。",
Expand Down Expand Up @@ -1662,6 +1665,7 @@ export const zhTWMessages = {
...thinkingIndicatorZhTW,
...splitViewZhTW,
...setupWizardZhTW,
...colorPickerZhTW,
...flowChatZhTW,
"patterns.description": "查看元件在常見介面中的組合與互動。",
"patterns.title": "組合範例",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CapsulePreview } from "../preview/CapsulePreview";
import { ColorPickerPreview } from "../preview/ColorPickerPreview";
import { Fragment, useEffect, useMemo, useState, type CSSProperties, type ReactNode } from "react";
import { ComponentOverview } from "../preview/ComponentOverview";
import { getComponentPresentation, getInitialPreviewState, hasComponentOverview } from "../preview/componentPresentation";
Expand Down Expand Up @@ -539,6 +540,7 @@ export function ComponentDetailPage({
if (component.name === "Checkbox" || component.name === "Radio") return `import { ${component.name} } from "@openbitfun/ui";\n\n<${component.name}${component.name === "Checkbox" ? ` appearance="${checkboxAppearance}"` : ""} label="${t("components.preview.notifications")}" defaultChecked />`;
if (component.name === "NumberBadge") return `import { NumberBadge } from "@openbitfun/ui";\n\n<NumberBadge value={${JSON.stringify(numberBadgeValue)}} />;`;
if (component.name === "NumberInput") return 'import { useState } from "react";\nimport { NumberInput } from "@openbitfun/ui";\n\nfunction Example() {\n const [value, setValue] = useState(8);\n return <NumberInput value={value} onValueChange={setValue} />;\n}';
if (component.name === "ColorPicker") return 'import { ColorPicker } from "@openbitfun/ui";\n\n<ColorPicker onValueChange={setColor} />';
if (component.name === "Empty") return `import { Empty } from "@openbitfun/ui";\n\n<Empty title="${t("components.preview.cardTitle")}" description="${t("components.preview.cardDescription")}" />`;
if (flowChatPreview) {
return flowChatPreview.codeSample(t);
Expand Down Expand Up @@ -1285,6 +1287,7 @@ export function ComponentDetailPage({
);
}
if (component.name === "NumberInput") return <NumberInputPreview key={state} state={state} />;
if (component.name === "ColorPicker") return <ColorPickerPreview key={state} state={state} />;
if (component.name === "NumberBadge") return <NumberBadge value={numberBadgeValue} />;
if (component.name === "Empty") {
return (
Expand Down
2 changes: 2 additions & 0 deletions design-system/apps/design-lab/src/pages/ComponentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from "react";
import { ComponentDetailPage } from "./ComponentDetailPage";
import { BrandPreview } from "../preview/BrandPreview";
import { CapsulePreview } from "../preview/CapsulePreview";
import { ColorPickerPreview } from "../preview/ColorPickerPreview";
import { AppWindow, Heading, Keyboard, List, Rows3, MousePointerClick, PanelTop, ToggleLeft } from "lucide-react";
import { VoiceCallPreview, VoiceParticlePreview } from "../components/VoiceCallPreview";
import {
Expand Down Expand Up @@ -189,6 +190,7 @@ const componentIcons = {

export function ComponentCardPreview({ component }: { component: ComponentMeta }) {
const { t } = useI18n();
if (component.name === "ColorPicker") return <ColorPickerPreview />;
if (component.category === "brand") return <BrandPreview name={component.name} active={false} />;
if (component.name === "VoiceCallPanel") return <VoiceCallPreview compact />;
if (component.name === "VoiceParticleLogo") return <VoiceParticlePreview />;
Expand Down
10 changes: 10 additions & 0 deletions design-system/apps/design-lab/src/preview/ColorPickerPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useState } from 'react';
import { ColorPicker } from '@openbitfun/ui';
import { useI18n } from '../i18n';

export function ColorPickerPreview({ state = 'collapsed' }: { state?: string }) {
const { t } = useI18n();
const [value, setValue] = useState<string | undefined>(state === 'invalid' ? '#12' : undefined);
return <ColorPicker value={value} onValueChange={setValue} defaultOpen={state === 'expanded'} disabled={state === 'disabled'}
labels={{ color: t('colorPicker.color'), palette: t('colorPicker.palette'), edit: t('colorPicker.edit'), apply: t('colorPicker.apply'), lightness: t('colorPicker.lightness') }} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const groupForTool = {
GetFileDiff: 'files', view_image: 'files',
ListModels: 'execution', ControlHub: 'web',
RunCode: 'execution', ExecCommand: 'execution', WriteStdin: 'execution', ExecControl: 'execution', Cron: 'execution', GetToolSpec: 'execution', Skill: 'execution',
WebSearch: 'web', WebFetch: 'web', PageDeploy: 'web', PagePublish: 'web',
WebSearch: 'web', WebFetch: 'web', PageDeploy: 'web', PagePublish: 'web', PageManage: 'web',
AgentSpawn: 'collaboration', AgentSendInput: 'collaboration', AgentWait: 'collaboration', TodoWrite: 'collaboration',
ContextCompression: 'collaboration', ReviewSessionSummary: 'collaboration', SessionControl: 'collaboration', SessionMessage: 'collaboration',
} as const satisfies Record<ToolNameForOwner<'standard'>, ToolGroup>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const presentations: Record<string, ComponentPresentation> = {
NavigationPanel: "composition", PageHeader: "composition", ScrollArea: "composition", SplitView: "composition",
SegmentedControl: "composition", TabGroup: "composition",
SetupWizard: "composition",
ColorPicker: "composition",
Icon: "icons", RollingText: "motion", ShimmerText: "motion", VoiceCallPanel: "motion",
ThinkingIndicator: "motion",
MobileBadge: "compact", MobileButton: "compact", MobileIconButton: "compact",
Expand All @@ -38,6 +39,7 @@ const presentations: Record<string, ComponentPresentation> = {
};

const initialStates: Record<string, string> = {
ColorPicker: "expanded",
Card: "raised", Disclosure: "open", Composer: "with-context", Switch: "off",
StatusPill: "success", Toolbar: "with-center", TabGroup: "selected", SegmentedControl: "selected",
Listbox: "selected-option", NavigationPanel: "selected-item", ScrollArea: "auto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ test("other components formerly using the fallback render their own state specim
assert.match(render("Avatar").preview, /<img/);
});

test("ColorPicker opens its registered flower palette in the Design Lab", () => {
const { preview, html } = render("ColorPicker");
assert.match(preview, /data-openbitfun-component="color-picker"/);
assert.match(preview, /aria-expanded="true"/);
assert.equal((preview.match(/data-openbitfun-part="petal"/g) ?? []).length, 19);
assert.match(preview, /role="slider"[^>]*aria-label="明度"/);
assert.match(html, /import \{ ColorPicker \}/);
assert.doesNotMatch(preview, /type="color"/);
});

test("LauncherButton exposes its interaction states with the catalog mic", () => {
const { html, preview } = render("LauncherButton");

Expand Down
11 changes: 11 additions & 0 deletions design-system/packages/theme-openbitfun/src/dark.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@
"secondaryBorder": { "$type": "color", "$value": "color-mix(in srgb, {color.accent.secondary} 25%, transparent)" }
},
"identity": {
"project": {
"$description": "User-selected project identity colors; independent of status, selection and assistant identity.",
"blue": { "$type": "color", "$value": "{ref.color.blue.400}" },
"cyan": { "$type": "color", "$value": "{ref.color.cyan.400}" },
"green": { "$type": "color", "$value": "{ref.color.green.400}" },
"yellow": { "$type": "color", "$value": "{ref.color.amber.400}" },
"orange": { "$type": "color", "$value": "{ref.color.amber.550}" },
"red": { "$type": "color", "$value": "{ref.color.red.400}" },
"purple": { "$type": "color", "$value": "{ref.color.purple.500}" },
"pink": { "$type": "color", "$value": "{ref.color.pink.400}" }
},
"assistant": {
"$description": "Named assistant identity used where it must remain distinct from the primary and secondary product accents.",
"content": { "$type": "color", "$value": "{ref.color.pink.500}" },
Expand Down
11 changes: 11 additions & 0 deletions design-system/packages/theme-openbitfun/src/light.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@
"secondaryBorder": { "$type": "color", "$value": "color-mix(in srgb, {color.accent.secondary} 25%, transparent)" }
},
"identity": {
"project": {
"$description": "User-selected project identity colors; independent of status, selection and assistant identity.",
"blue": { "$type": "color", "$value": "{ref.color.blue.600}" },
"cyan": { "$type": "color", "$value": "{ref.color.cyan.600}" },
"green": { "$type": "color", "$value": "{ref.color.green.600}" },
"yellow": { "$type": "color", "$value": "{ref.color.amber.525}" },
"orange": { "$type": "color", "$value": "{ref.color.amber.625}" },
"red": { "$type": "color", "$value": "{ref.color.red.600}" },
"purple": { "$type": "color", "$value": "{ref.color.purple.650}" },
"pink": { "$type": "color", "$value": "{ref.color.pink.600}" }
},
"assistant": {
"$description": "Named assistant identity used where it must remain distinct from the primary and secondary product accents.",
"content": { "$type": "color", "$value": "{ref.color.pink.600}" },
Expand Down
26 changes: 25 additions & 1 deletion design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ export function Example() {

The package owns component anatomy, behavior, accessibility, and stable variants. It does not own theme selection persistence, product state, routes, locale resources, or platform APIs.

## Color selection

`ColorPicker` combines a round swatch, an editable HEX pill, an expandable flower
palette and a curved lightness slider. Use `value` / `onValueChange` for a controlled
draft, or `defaultValue` for an independent control. Opening the palette and
focusing unchanged text do not emit a value change. `onValueChange` includes
incomplete text; owners must require a six-digit HEX value before persistence.
Enter, blur or the apply action expands three-digit shorthand. Invalid text keeps
the last valid preview. `open` / `onOpenChange` can connect an external custom-color
entry point; `defaultOpen` is available for inline authoring.

The palette stays in normal document flow. Its petals are keyboard buttons; the
lightness slider supports pointer capture, touch, arrow keys, Page Up/Down and
Home/End. Escape first leaves text editing, then collapses the palette. Pass
localized `labels` from the owning surface. Bloom motion respects reduced motion.

`components/ColorPicker/color.ts` owns sRGB/HSL conversion and the sampled flower
gamut. Palette colors, the current-color ring and the lightness ramp represent
editable color content; they deliberately do not follow theme or status colors.
Chrome, borders, focus and shadows use existing semantic/system tokens. No theme
color literal or new token is needed. The component has no storage or host API.

## Pane layouts

`SplitView` retains its primary and secondary content hosts across placement,
Expand Down Expand Up @@ -607,7 +629,9 @@ matches the trigger. `variant="pill"` uses badge typography, spacing and rounded
ends while retaining the selected control size. An option's `selectedLabel`
shortens only the closed trigger; the expanded header, listbox and native form
option retain its full `label`. Combine these with content widths for compact
inline platform or scope selectors. `Combobox` adds searchable single
inline platform or scope selectors. Set `showGroupLabels={false}` to hide group
headings while retaining the standard section spacing, and `showGroupDividers`
to separate option groups with a themed border. `Combobox` adds searchable single
selection, grouped options, explicit custom-value creation and async loading
states. `MultiSelect` owns multiple selection, removable tags and select-all.
Controlled values are authoritative; option discovery remains host-owned.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ComponentMeta } from '../../registry.types';

export const colorPickerMeta = {
name: 'ColorPicker', category: 'form', maturity: 'stable',
description: 'An editable HEX pill and expandable flower palette with a curved lightness slider.',
props: [
{ name: 'value', type: 'string' }, { name: 'defaultValue', type: 'string' }, { name: 'onValueChange', type: '(value: string) => void' },
{ name: 'open', type: 'boolean' }, { name: 'defaultOpen', type: 'boolean', defaultValue: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' }, { name: 'disabled', type: 'boolean', defaultValue: 'false' },
{ name: 'labels', type: 'Partial<ColorPickerLabels>' },
],
states: ['collapsed', 'expanded', 'invalid', 'disabled'],
tokens: ['color.surface.panel', 'color.content.primary', 'color.field.background', 'color.focus.ring', 'control.height.lg', 'radius.pill', 'shadow.sm', 'shadow.base', 'motion.duration.normal', 'type.code.md.fontFamily'],
} as const satisfies ComponentMeta;
Loading
Loading