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
31 changes: 18 additions & 13 deletions cmd/web/frontend/docs/ui-design-system.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,11 +402,12 @@ UI primitive は `src/components/ui/` 配下に置きます。domain に依存

### Button (`ui/button`)

- 既定の primitive: `Button` は `--primary-black` 背景、白文字、
高さ `40px`、radius `6px`、横 padding `0 16px` の primary アクションを
描画します。
- 同伴の `.splitButton` セレクターは、末尾の `40px` 正方形 chevron スロットで
同じ見た目を再利用します。
- `Button` は `primary`、`positive`、`secondary`、`tertiary` variant を提供します。
Primary は黒塗り、positive は ready 状態の緑塗り、secondary は neutral な
アウトライン、tertiary は visible border のない muted text を使います。
- default size は高さ `40px`、横 padding `0 16px` です。`compact` size は
Issue Card のクイックアクションに合わせた高さ `32px`、横 padding `0 12px` です。
- Storybook で各 variant の個別表示と一覧比較を確認できます。
- グローバルな `button` リセットは、それ以外のすべての native button に
中立の surface、`1px` border、radius `6px`、padding `8px 10px`、
`--muted` の disabled 状態を与えます。
Expand Down Expand Up @@ -577,10 +578,11 @@ gap: 14px;
- radius `8px` のカード、`0 12px 32px rgb(0 0 0 / 8%)` の濃いめの shadow。
- タイトル行: `16px / 500` リンク + `32px` の menu トリガー。
- メトリック行: icon + value ペア。各メトリックを `1px` 左 border で区切ります。
- クイックアクションボタンはスコープ付き変数 (`--quick-action-bg`、
`--quick-action-border`、`--quick-action-text`) を使い、各バリアント
(`quickAction-ready`、`quickAction-done`) は色だけを上書きします。
- クイックアクションは共有の compact `Button` を使います。Ready は `positive`、
Done は `primary`、Resolve は `secondary` です。
- blocked のカードでは、`Ready` への直接遷移を `Resolve` に置き換えます。
neutral なアウトラインを使ったテキストのみのボタンにより、ダイアログを開く
この操作を、緑色で直接遷移する `Ready` と区別します。
この操作は comment の全ページから最新の blocker comment を取得し、
Continue with Comment ダイアログを開きます。blocker は読み取り専用の文脈として
表示し、フリーテキストまたは組み込みの `Ok` / `Retry` ショートカットで
Expand Down Expand Up @@ -632,6 +634,8 @@ gap: 14px;
の上に積みます。
- `basic-info-panel` は issue ID、project、priority、status、assignee、
作成日、更新日を 1 カラム meta grid で表示します。
- `artifacts-section` は pull request を、種別 icon、label、repository と PR 番号、
を横並びにした compact な text link として表示します。
- `meta-item` は各エントリを top rule、`12px` muted `dt`、通常 weight の
`dd`、ID のための `overflow-wrap: anywhere` で描画します。
- `runs-section` は run 行を `8px` border のグループで囲みます。
Expand All @@ -643,12 +647,13 @@ gap: 14px;
`コメントを入力…` はエディターを開き、設定済みショートカットは指示本文を
即時送信します。将来、短い表示名に長い agent 指示を割り当てられるよう、
ショートカットの表示名と本文は別々の値として扱います。
- Reject 操作は split button を使用します。主ボタンはエディターを開き、
chevron 側はショートカットメニューを開きます。両方で disabled と送信中の
状態を共有します
- Reject 操作は primary の Done より主張を弱めた neutral な secondary split button
使用します。主ボタンはエディターを開き、chevron 側はショートカットメニューを
開きます。両方で disabled と送信中の状態を共有します
- Issue 詳細 sidebar の status shortcut には `Quick Action` の見出しを付けます。
backlog では `Ready`、ready では `Draft`、review では `Done` と赤色の Reject
split button を表示します。
backlog では positive の `Ready`、ready では secondary の `Draft`、review では
secondary の Reject split button、primary の `Done` の順で表示します。blocked では
secondary の `Resolve` を表示します。
- `status-actions` は同じカード内の wrap-flex アクション行です。
- `issue-description` は共有 markdown レンダラーを使い、line-height
`1.65`、アンカー色 `--primary-black`、インライン画像に border を付けます。
Expand Down
41 changes: 24 additions & 17 deletions cmd/web/frontend/docs/ui-design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,14 @@ must remain reusable.

### Button (`ui/button`)

- Default primitive: `Button` renders a primary action button with
`--primary-black` background, white text, `40px` height, `6px` radius, and a
`0 16px` horizontal padding.
- A companion `.splitButton` selector reuses the same look in a `40px` square
trailing chevron slot.
- `Button` exposes `primary`, `positive`, `secondary`, and `tertiary` variants.
Primary uses a black fill, positive uses the ready-state green fill,
secondary uses a neutral outline, and tertiary uses muted text without a
visible border.
- The default size is `40px` high with `0 16px` horizontal padding. The
`compact` size matches Issue Card quick actions at `32px` high with `0 12px`
horizontal padding.
- Storybook exposes each variant independently and together for visual comparison.
- The global `button` reset gives every other native button a neutral
surface with `1px` border, `6px` radius, `8px 10px` padding, and a disabled
state in `--muted`.
Expand Down Expand Up @@ -579,14 +582,14 @@ This is the recurring shape for `runs-section`, `comment-list`,
- `8px` radius card with a heavier `0 12px 32px rgb(0 0 0 / 8%)` shadow.
- Title row: `16px / 500` link plus a `32px` menu trigger.
- Metric row: icon + value pairs separated by `1px` left border per metric.
- Quick action button uses scoped variables (`--quick-action-bg`,
`--quick-action-border`, `--quick-action-text`) so each variant
(`quickAction-ready`, `quickAction-done`) only overrides colors.
- Quick actions use the shared compact `Button`: Ready uses `positive`, Done
uses `primary`, and Resolve uses `secondary`.
- A blocked card replaces the direct `Ready` transition with `Resolve`. The
action loads the latest blocker comment across all comment pages and opens a
continue-with-comment dialog. The blocker is read-only context; free text or
the built-in `Ok` / `Retry` shortcuts create the change request before the
issue moves to `ready`.
text-only neutral outlined button distinguishes this dialog-opening action
from the direct green `Ready` transition. The action loads the latest blocker
comment across all comment pages and opens a continue-with-comment dialog.
The blocker is read-only context; free text or the built-in `Ok` / `Retry`
shortcuts create the change request before the issue moves to `ready`.

### Issue Board

Expand Down Expand Up @@ -633,6 +636,8 @@ palette:
content under `900px`.
- `basic-info-panel` uses a one-column meta grid for issue ID, project,
priority, status, assignee, created date, and updated date.
- `artifacts-section` presents a pull request as a compact text link with a type
icon and an inline label, repository, and PR number reference.
- `meta-item` renders each entry with a top rule, `12px` muted `dt`, normal
weight `dd`, and `overflow-wrap: anywhere` for IDs.
- `runs-section` wraps run rows in a single `8px` bordered group.
Expand All @@ -644,12 +649,14 @@ palette:
opens the editor, while configured shortcuts submit their instruction body
immediately. Shortcut labels and bodies are separate values so future
configuration can keep compact labels for longer agent instructions.
- Reject actions use a split button: the primary segment opens the editor and
the chevron segment opens the shortcut menu. Both segments share disabled
and submitting states.
- Reject actions use a neutral secondary split button so they remain weaker
than the primary Done action. The main segment opens the editor and the
chevron segment opens the shortcut menu. Both segments share disabled and
submitting states.
- The issue-detail sidebar labels its status shortcuts as `Quick Action`:
backlog shows `Ready`, ready shows `Draft`, and review shows `Done` together
with the red Reject split button.
backlog shows positive `Ready`, ready shows secondary `Draft`, and review
shows the secondary Reject split button before primary `Done`. Blocked issues
show secondary `Resolve`.
- `status-actions` is a wrap-flex action row inside the same card.
- `issue-description` uses the shared markdown renderer with `1.65` line
height, anchor color `--primary-black`, and bordered inline images.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
}

.version {
align-self: flex-end;
align-self: flex-start;
color: var(--medium-gray);
font-size: 11px;
line-height: 1;
Expand Down
74 changes: 60 additions & 14 deletions cmd/web/frontend/src/components/ui/button/index.module.css
Original file line number Diff line number Diff line change
@@ -1,22 +1,68 @@
.primaryButton,
.splitButton {
background: var(--primary-black);
border-color: var(--primary-black);
color: var(--white);
height: 40px;
}

.primaryButton {
.button {
align-items: center;
border: 1px solid transparent;
border-radius: var(--radius-sm);
cursor: pointer;
display: inline-flex;
flex-shrink: 0;
font-weight: 500;
justify-content: center;
line-height: 1;
}

.default {
block-size: 40px;
font-size: 14px;
gap: var(--space-2);
padding: var(--space-0) var(--space-4);
}

.splitButton {
border-left-color: var(--dark-gray);
border-radius: var(--space-0) var(--radius-sm) var(--radius-sm) var(--space-0);
margin-left: calc(var(--space-2) * -1);
width: 40px;
.compact {
block-size: 32px;
font-size: 13px;
gap: 6px;
padding: var(--space-0) var(--space-3);
}

.primary {
background: var(--primary-black);
border-color: var(--primary-black);
color: var(--white);
}

.positive {
background: var(--status-ready-text);
border-color: var(--status-ready-text);
color: var(--white);
}

.secondary {
background: transparent;
border-color: var(--border);
color: var(--primary-black);
}

.tertiary {
background: transparent;
border-color: transparent;
color: var(--muted);
}

.button:hover:not(:disabled),
.button:focus-visible:not(:disabled) {
opacity: 0.9;
}

.secondary:hover:not(:disabled),
.secondary:focus-visible:not(:disabled),
.tertiary:hover:not(:disabled),
.tertiary:focus-visible:not(:disabled) {
background: var(--surface-hover);
color: var(--primary-black);
opacity: 1;
}

.button:disabled {
cursor: not-allowed;
opacity: 0.6;
}
52 changes: 52 additions & 0 deletions cmd/web/frontend/src/components/ui/button/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CSSProperties } from "react";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { noop } from "@/stories/fixtures";
import { Button } from "./index";
Expand All @@ -8,6 +9,15 @@ const meta = {
args: {
children: "Create task",
onClick: noop,
size: "default",
variant: "primary",
},
argTypes: {
size: { control: "inline-radio", options: ["default", "compact"] },
variant: {
control: "inline-radio",
options: ["primary", "positive", "secondary", "tertiary"],
},
},
} satisfies Meta<typeof Button>;

Expand All @@ -16,3 +26,45 @@ export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {};

export const Positive: Story = {
args: { children: "Ready", variant: "positive" },
};

export const Secondary: Story = {
args: { children: "Resolve", variant: "secondary" },
};

export const Tertiary: Story = {
args: { children: "Reject", variant: "tertiary" },
};

export const Compact: Story = {
args: { children: "Done", size: "compact" },
};

export const Variants: Story = {
render: (args) => (
<div style={variantGridStyle}>
<Button {...args} variant="primary">
Primary
</Button>
<Button {...args} variant="positive">
Positive
</Button>
<Button {...args} variant="secondary">
Secondary
</Button>
<Button {...args} variant="tertiary">
Tertiary
</Button>
</div>
),
};

const variantGridStyle: CSSProperties = {
alignItems: "center",
display: "flex",
flexWrap: "wrap",
gap: "12px",
};
27 changes: 22 additions & 5 deletions cmd/web/frontend/src/components/ui/button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import type { ButtonHTMLAttributes, ReactNode } from "react";
import styles from "./index.module.css";

type Props = {
children?: React.ReactNode;
onClick: () => void;
export type ButtonVariant = "primary" | "positive" | "secondary" | "tertiary";
export type ButtonSize = "default" | "compact";

type Props = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> & {
children?: ReactNode;
size?: ButtonSize;
type?: "button" | "submit" | "reset";
variant?: ButtonVariant;
};

export function Button({ children, onClick }: Props) {
export function Button({
children,
className,
size = "default",
type = "button",
variant = "primary",
...buttonProps
}: Props) {
return (
<button className={styles.primaryButton} type="button" onClick={onClick}>
<button
{...buttonProps}
className={[styles.button, styles[variant], styles[size], className].filter(Boolean).join(" ")}
type={type}
>
{children}
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,40 +140,6 @@
min-width: 0;
}

.quickActionButton {
align-items: center;
block-size: 32px;
background: var(--quick-action-bg);
border: 1px solid var(--quick-action-border);
border-radius: var(--radius-sm);
color: var(--quick-action-text);
display: inline-flex;
flex-shrink: 0;
font-size: 13px;
font-weight: 500;
gap: 6px;
justify-content: center;
line-height: 1;
padding: 0 12px;
}

.quickActionButton:hover,
.quickActionButton:focus-visible {
opacity: 0.9;
}

.quickAction-ready {
--quick-action-bg: var(--status-ready-text);
--quick-action-border: transparent;
--quick-action-text: var(--white);
}

.quickAction-done {
--quick-action-bg: transparent;
--quick-action-border: var(--border);
--quick-action-text: var(--primary-black);
}

.quickActionIcon {
color: currentcolor;
flex-shrink: 0;
Expand Down
Loading
Loading