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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const descriptionKeys: Readonly<Record<string, MessageKey>> = {
TerminalControlToolCard: "component.TerminalControlToolCard.description",
TodoToolCard: "component.TodoToolCard.description",
Toolbar: "component.Toolbar.description",
Tooltip: "component.Tooltip.description",
ViewImageToolCard: "component.ViewImageToolCard.description",
WebFetchToolCard: "component.WebFetchToolCard.description",
WebSearchToolCard: "component.WebSearchToolCard.description",
Expand Down
15 changes: 15 additions & 0 deletions design-system/apps/design-lab/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const enUSMessages = {
"component.TerminalControlToolCard.description": "An ambient trace for interrupting or terminating a terminal session.",
"component.TodoToolCard.description": "An ambient task-progress card with compact and expandable list presentations.",
"component.Toolbar.description": "A responsive bar with independent leading, centered, trailing, and horizontally scrollable composition regions.",
"component.Tooltip.description": "A portaled, delay-managed hover label that flips placement at viewport edges and supports cursor-following and interactive content.",
"component.ViewImageToolCard.description": "An ambient image-result card with an expandable preview and accessible lightbox.",
"component.WebFetchToolCard.description": "An ambient web-fetch card with source metadata, result actions, and content preview.",
"component.WebSearchToolCard.description": "An ambient web-search card with link, snippet, URL, and summary result presentations.",
Expand Down Expand Up @@ -244,6 +245,8 @@ export const enUSMessages = {
"components.preview.scrollAreaLabel": "Scrollable activity",
"components.preview.scrollAreaItem": "Activity item {index}",
"components.preview.menuLabel": "Session menu",
"components.preview.tooltipTrigger": "Rename session",
"components.preview.tooltipContent": "Rename the current session",
"components.preview.menuSectionTitle": "Sessions",
"components.preview.menuItemOne": "Welcome session",
"components.preview.menuItemTwo": "Design review",
Expand Down Expand Up @@ -402,6 +405,8 @@ export const enUSMessages = {
"detail.option.left": "Left",
"detail.option.right": "Right",
"detail.option.confirmation": "Confirmation",
"detail.option.top": "Top",
"detail.option.bottom": "Bottom",
"detail.option.off": "Off",
"detail.option.on": "On",
"detail.option.selected": "Selected",
Expand Down Expand Up @@ -694,6 +699,7 @@ export const zhCNMessages = {
"component.TerminalControlToolCard.description": "低关注终端控制轨迹,用于呈现中断或终止终端会话的结果。",
"component.TodoToolCard.description": "低关注任务进度卡片,支持紧凑摘要与可展开列表两种呈现。",
"component.Toolbar.description": "具备独立前置、居中、尾部与横向滚动区域的响应式工具栏。",
"component.Tooltip.description": "具备 Portal、延迟管理、视口边缘翻转,并支持跟随光标与可交互内容的悬停提示。",
"component.ViewImageToolCard.description": "低关注图片结果卡片,包含可展开预览和无障碍灯箱。",
"component.WebFetchToolCard.description": "低关注网页读取卡片,包含来源元数据、结果操作与内容预览。",
"component.WebSearchToolCard.description": "低关注网页搜索卡片,统一呈现链接、摘要、网址与结果列表。",
Expand Down Expand Up @@ -839,6 +845,8 @@ export const zhCNMessages = {
"components.preview.scrollAreaLabel": "可滚动动态",
"components.preview.scrollAreaItem": "动态项目 {index}",
"components.preview.menuLabel": "会话菜单",
"components.preview.tooltipTrigger": "重命名会话",
"components.preview.tooltipContent": "重命名当前会话",
"components.preview.menuSectionTitle": "会话",
"components.preview.menuItemOne": "欢迎会话",
"components.preview.menuItemTwo": "设计审查",
Expand Down Expand Up @@ -997,6 +1005,8 @@ export const zhCNMessages = {
"detail.option.left": "左侧",
"detail.option.right": "右侧",
"detail.option.confirmation": "待确认",
"detail.option.top": "顶部",
"detail.option.bottom": "底部",
"detail.option.off": "关闭",
"detail.option.on": "开启",
"detail.option.selected": "选中",
Expand Down Expand Up @@ -1283,6 +1293,7 @@ export const zhTWMessages = {
"component.TerminalControlToolCard.description": "低關注終端控制軌跡,用於呈現中斷或終止終端工作階段的結果。",
"component.TodoToolCard.description": "低關注任務進度卡片,支援緊湊摘要與可展開列表兩種呈現。",
"component.Toolbar.description": "具備獨立前置、置中、尾端與橫向捲動區域的響應式工具列。",
"component.Tooltip.description": "具備 Portal、延遲管理、視口邊緣翻轉,並支援跟隨游標與可互動內容的懸停提示。",
"component.ViewImageToolCard.description": "低關注圖片結果卡片,包含可展開預覽和無障礙燈箱。",
"component.WebFetchToolCard.description": "低關注網頁讀取卡片,包含來源中繼資料、結果操作與內容預覽。",
"component.WebSearchToolCard.description": "低關注網頁搜尋卡片,統一呈現連結、摘要、網址與結果列表。",
Expand Down Expand Up @@ -1423,6 +1434,8 @@ export const zhTWMessages = {
"components.preview.scrollAreaLabel": "可捲動動態",
"components.preview.scrollAreaItem": "動態項目 {index}",
"components.preview.menuLabel": "工作階段選單",
"components.preview.tooltipTrigger": "重新命名工作階段",
"components.preview.tooltipContent": "重新命名目前的工作階段",
"components.preview.menuSectionTitle": "工作階段",
"components.preview.menuItemOne": "歡迎工作階段",
"components.preview.menuItemTwo": "設計審查",
Expand Down Expand Up @@ -1575,6 +1588,8 @@ export const zhTWMessages = {
"detail.option.left": "左側",
"detail.option.right": "右側",
"detail.option.confirmation": "待確認",
"detail.option.top": "頂部",
"detail.option.bottom": "底部",
"detail.option.off": "關閉",
"detail.option.on": "開啟",
"detail.option.selected": "已選取",
Expand Down
26 changes: 25 additions & 1 deletion design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
ToolbarBadge,
ToolbarGroup,
ToolbarSeparator,
Tooltip,
type ColorScheme,
type ConfirmDialogType,
type ContrastMode,
Expand Down Expand Up @@ -282,6 +283,8 @@ export function ComponentDetailPage({
? "selected"
: component.name === "ScrollArea"
? "auto"
: component.name === "Tooltip"
? "top"
: "default",
);
const [inspectorDisabled, setInspectorDisabled] = useState(false);
Expand Down Expand Up @@ -345,6 +348,8 @@ export function ComponentDetailPage({
return ["selected", "unselected", "hover", "disabled"] as const;
case "Toolbar":
return ["default", "with-center", "overflow"] as const;
case "Tooltip":
return ["top", "bottom", "left", "right"] as const;
default:
return ["off", "on", "focus-visible", "disabled"] as const;
}
Expand Down Expand Up @@ -436,6 +441,9 @@ export function ComponentDetailPage({
if (component.name === "FieldGroup") {
return `import { Field, FieldGroup, FieldRow, FormSection, Input } from "@bitfun/ui";\nimport { Settings } from "lucide-react";\n\n<FormSection\n description="${t("components.preview.fieldDescription")}"\n headingAs="h3"\n leading={<Settings />}\n title="${t("components.preview.modalSectionTitle")}"\n>\n <FieldGroup appearance="subtle" dividers>\n <FieldRow>\n <Field controlWidth="fill" label="${t("components.preview.modalProviderName")}" labelWidth="md" orientation="horizontal" required>\n <Input defaultValue="OpenBitFun" />\n </Field>\n </FieldRow>\n <FieldRow>\n <Field controlWidth="fill" label="${t("components.preview.modalApiUrl")}" labelWidth="md" orientation="horizontal">\n <Input defaultValue="https://api.openbitfun.com" />\n </Field>\n </FieldRow>\n </FieldGroup>\n</FormSection>`;
}
if (component.name === "Tooltip") {
return `import { Tooltip } from "@bitfun/ui";\n\n<Tooltip\n content="${t("components.preview.tooltipContent")}"\n placement="${previewState}"\n>\n <Button>${t("components.preview.tooltipTrigger")}</Button>\n</Tooltip>`;
}
if (component.name === "Menu") {
return `import { Menu, MenuItem, MenuSection, MenuSeparator } from "@bitfun/ui";\nimport { MessageCircle } from "lucide-react";\n\n<Menu\n aria-label="${t("components.preview.menuLabel")}"\n scrollbarVisibility="${menuShowScrollbar ? "auto" : "hidden"}"\n>\n <MenuSection title="${t("components.preview.menuSectionTitle")}">\n <MenuItem leading={<MessageCircle />}>${t("components.preview.menuItemOne")}</MenuItem>\n <MenuItem leading={<MessageCircle />}>${t("components.preview.menuItemTwo")}</MenuItem>\n </MenuSection>\n <MenuSeparator />\n <MenuSection aria-label="${t("components.preview.menuMoreSection")}">\n <MenuItem disabled>${t("components.preview.menuDisabledItem")}</MenuItem>\n </MenuSection>\n</Menu>`;
}
Expand Down Expand Up @@ -1040,6 +1048,20 @@ export function ComponentDetailPage({
);
}

if (component.name === "Tooltip") {
return (
<Tooltip
content={t("components.preview.tooltipContent")}
delay={0}
placement={state as "top" | "bottom" | "left" | "right"}
>
<Button size="sm" variant="fill">
{t("components.preview.tooltipTrigger")}
</Button>
</Tooltip>
);
}

if (component.name === "Menu") {
const itemCount = state === "scrolling" ? 12 : 3;
return (
Expand Down Expand Up @@ -1619,7 +1641,7 @@ export function ComponentDetailPage({
</Fragment>
))}
</div>
) : component.name === "ActionCard" || component.name === "ActionItem" || component.name === "Field" || component.name === "FieldGroup" || component.name === "Input" || component.name === "KeyHint" || component.name === "Menu" || component.name === "NavigationPanel" || component.name === "PageHeader" || component.name === "ScrollArea" || component.name === "SearchField" || component.name === "SegmentedControl" || component.name === "Select" || component.name === "StatusPill" ? (
) : component.name === "ActionCard" || component.name === "ActionItem" || component.name === "Field" || component.name === "FieldGroup" || component.name === "Input" || component.name === "KeyHint" || component.name === "Menu" || component.name === "NavigationPanel" || component.name === "PageHeader" || component.name === "ScrollArea" || component.name === "SearchField" || component.name === "SegmentedControl" || component.name === "Select" || component.name === "StatusPill" || component.name === "Tooltip" ? (
<div
className="component-preview-matrix"
data-component={component.name === "ActionCard"
Expand Down Expand Up @@ -1648,6 +1670,8 @@ export function ComponentDetailPage({
? "navigation-panel"
: component.name === "ScrollArea"
? "scroll-area"
: component.name === "Tooltip"
? "tooltip"
: "search-field"}
data-state-count={states.length}
>
Expand Down
10 changes: 10 additions & 0 deletions design-system/apps/design-lab/src/pages/ComponentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ToolbarBadge,
ToolbarGroup,
ToolbarSeparator,
Tooltip,
type ColorScheme,
type ContrastMode,
type DensityMode,
Expand Down Expand Up @@ -109,6 +110,7 @@ const componentIcons = {
Switch: ToggleLeft,
TabGroup: PanelTop,
Toolbar: PanelTop,
Tooltip: MessageCircle,
} as const;

function ComponentCardPreview({ component }: { component: ComponentMeta }) {
Expand Down Expand Up @@ -452,6 +454,14 @@ function ComponentCardPreview({ component }: { component: ComponentMeta }) {
)}
/>
);
case "Tooltip":
return (
<Tooltip content={t("components.preview.tooltipContent")} delay={0}>
<Button size="sm" tabIndex={-1} variant="fill">
{t("components.preview.tooltipTrigger")}
</Button>
</Tooltip>
);
default:
return null;
}
Expand Down
14 changes: 14 additions & 0 deletions design-system/packages/design-tokens/src/system.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,20 @@
"itemIconSize": { "$value": "14px" },
"scrollbarGap": { "$value": "2px" }
},
"tooltip": {
"$type": "dimension",
"maxInlineSize": { "$value": "280px" },
"maxBlockSize": { "$value": "320px" },
"paddingBlock": { "$value": "6px" },
"paddingInline": { "$value": "10px" },
"surfaceRadius": { "$value": "{radius.sm}" },
"arrowSize": { "$value": "8px" },
"gap": {
"$description": "Distance between the trigger (or cursor) and the tooltip surface.",
"$value": "{space.2}"
},
"fontSize": { "$value": "{font.size.xs}" }
},
"modal": {
"$type": "dimension",
"viewportGutter": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ export const actionItemMeta = {
{ name: "shortcut", type: "ReactNode" },
{ defaultValue: "[]", name: "actions", type: "readonly ActionItemAction[]" },
{ defaultValue: "false", name: "disabled", type: "boolean" },
{ defaultValue: "neutral", name: "tone", type: "neutral | danger" },
],
states: ["default", "hover", "active", "focus-visible", "disabled"],
states: ["default", "hover", "active", "focus-visible", "disabled", "danger"],
tokens: [
"color.action.neutral.content",
"color.action.neutral.contentDisabled",
"color.action.neutral.surface",
"color.action.neutral.surfacePressed",
"color.content.muted",
"color.focus.ring",
"color.status.danger.content",
"color.status.danger.surface",
"control.height.sm",
"font.family.control",
"font.size.small",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@
cursor: not-allowed;
}

.root[data-bf-tone="danger"]:not([data-disabled="true"]) {
color: var(--bf-color-status-danger-content);
}

.root[data-bf-tone="danger"]:not([data-disabled="true"]):hover,
.root[data-bf-tone="danger"]:not([data-disabled="true"]):has(.trigger[data-bf-preview-state="hover"]) {
background: var(--bf-color-status-danger-surface);
}

.root[data-bf-tone="danger"]:not([data-disabled="true"]):has(.trigger:active),
.root[data-bf-tone="danger"]:not([data-disabled="true"]):has(.trigger[data-bf-preview-state="active"]) {
background: var(--bf-color-status-danger-surface);
}

.trigger {
display: inline-flex;
flex: 1 1 auto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface ActionItemAction {
tone?: IconButtonProps["tone"];
}

export type ActionItemTone = "neutral" | "danger";

export interface ActionItemProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "className"> {
actions?: readonly ActionItemAction[];
Expand All @@ -26,6 +28,7 @@ export interface ActionItemProps
metadata?: ReactNode;
reserveLeadingSpace?: boolean;
shortcut?: ReactNode;
tone?: ActionItemTone;
triggerClassName?: string;
}

Expand All @@ -38,6 +41,7 @@ export const ActionItem = forwardRef<HTMLButtonElement, ActionItemProps>(functio
metadata,
reserveLeadingSpace = false,
shortcut,
tone = "neutral",
triggerClassName,
type = "button",
...props
Expand All @@ -48,6 +52,7 @@ export const ActionItem = forwardRef<HTMLButtonElement, ActionItemProps>(functio
<span
className={classNames(styles.root, className)}
data-bf-component="action-item"
data-bf-tone={tone}
data-disabled={disabled ? "true" : "false"}
>
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export {
ActionItem,
type ActionItemAction,
type ActionItemProps,
type ActionItemTone,
} from "./ActionItem";
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export const navigationPanelMeta = {
{ name: "children", type: "ReactNode" },
{ name: "header", type: "ReactNode" },
{ name: "footer", type: "ReactNode" },
{ name: "bodyRef", type: "Ref<HTMLDivElement>" },
{ name: "bodyClassName", type: "string" },
{ name: "contentClassName", type: "string" },
{ defaultValue: "auto", name: "scrollbarVisibility", type: "auto | always | hidden" },
],
states: ["default", "selected-item", "disabled-item", "scrolling"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type HTMLAttributes,
type MouseEventHandler,
type ReactNode,
type Ref,
} from "react";
import { ActionItem, type ActionItemProps } from "../ActionItem";
import { IconButton, type IconButtonProps } from "../IconButton";
Expand All @@ -14,6 +15,8 @@ import styles from "./NavigationPanel.module.css";
export interface NavigationPanelProps
extends Omit<HTMLAttributes<HTMLElement>, "children"> {
bodyClassName?: string;
/** Ref to the scroll viewport so product sticky headers can track the same root. */
bodyRef?: Ref<HTMLDivElement>;
children: ReactNode;
contentClassName?: string;
footer?: ReactNode;
Expand Down Expand Up @@ -47,6 +50,7 @@ export type NavigationPanelSeparatorProps = HTMLAttributes<HTMLDivElement>;
export const NavigationPanel = forwardRef<HTMLElement, NavigationPanelProps>(
function NavigationPanel({
bodyClassName,
bodyRef,
children,
className,
contentClassName,
Expand All @@ -68,6 +72,7 @@ export const NavigationPanel = forwardRef<HTMLElement, NavigationPanelProps>(
<ScrollArea
className={classNames(styles.body, bodyClassName)}
orientation="vertical"
ref={bodyRef}
scrollbarVisibility={scrollbarVisibility}
>
<div
Expand Down
38 changes: 38 additions & 0 deletions design-system/packages/ui/src/components/Tooltip/Tooltip.meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { ComponentMeta } from "../../registry.types";

export const tooltipMeta = {
category: "feedback",
description: "A portaled, delay-managed hover label that flips placement at viewport edges and supports cursor-following and interactive content.",
maturity: "stable",
name: "Tooltip",
props: [
{ name: "children", type: "ReactElement" },
{ name: "content", type: "ReactNode" },
{ defaultValue: "top", name: "placement", type: "top | bottom | left | right" },
{ defaultValue: "hover", name: "trigger", type: "hover | click | focus" },
{ defaultValue: "450", name: "delay", type: "number" },
{ defaultValue: "false", name: "disabled", type: "boolean" },
{ defaultValue: "false", name: "followCursor", type: "boolean" },
{ defaultValue: "false", name: "interactive", type: "boolean" },
{ name: "portalContainer", type: "TooltipPortalTarget" },
],
states: ["default", "visible", "interactive", "instant"],
tokens: [
"color.surface.raised",
"color.content.primary",
"color.border.subtle",
"color.border.default",
"color.border.strong",
"font.family.sans",
"font.weight.regular",
"shadow.sm",
"overlay.tooltip.maxInlineSize",
"overlay.tooltip.maxBlockSize",
"overlay.tooltip.paddingBlock",
"overlay.tooltip.paddingInline",
"overlay.tooltip.surfaceRadius",
"overlay.tooltip.arrowSize",
"overlay.tooltip.gap",
"overlay.tooltip.fontSize",
],
} as const satisfies ComponentMeta;
Loading
Loading