Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
8267fdd
fix(ui): migrate number and split button wrappers
dev-rb Oct 26, 2026
daaa3e6
docs(ui): enable hosted proxy for button migration preview
dev-rb Oct 26, 2026
0a54ae5
fix(ui): correct migrated menu and action controls
dev-rb Oct 26, 2026
17b58d8
fix(ui): keep sidebar navigation controls visually neutral
dev-rb Oct 26, 2026
17b3472
style(ui): use accent for primary workflow actions
dev-rb Oct 26, 2026
4747298
style(ui): accent remaining creation and onboarding actions
dev-rb Oct 26, 2026
b0e8c17
style(ui): outline new actions and square tag triggers
dev-rb Oct 26, 2026
941264f
style(properties): use secondary task property controls
dev-rb Oct 26, 2026
570ad5f
style(tasks): outline kanban load more buttons
dev-rb Oct 26, 2026
feb57b1
fix(email): use ghost composer utilities and size reply icons
dev-rb Oct 26, 2026
6c74ed3
style(drive): use ghost folder composer header actions
dev-rb Oct 26, 2026
e9b2c6e
style(properties): use extra-small task and document pills
dev-rb Oct 26, 2026
d1954a4
fix(ui): assign button variants by action role
dev-rb Oct 26, 2026
331c845
style(ui): remove automatic button group elevation
dev-rb Oct 26, 2026
83bd6b1
style(projects): align composer controls with tasks
dev-rb Oct 26, 2026
8552498
style(projects): match task property presentation
dev-rb Oct 26, 2026
83bdc97
style(ui): raise the shared new button
dev-rb Oct 26, 2026
306f1f2
feat(ui)!: replace button API and migrate app consumers
dev-rb Oct 26, 2026
3b69f5d
chore(ui): merge main into button redesign
dev-rb Oct 26, 2026
95b59ab
refactor(ui): move button materials into scoped CSS
dev-rb Oct 26, 2026
6b28fb4
fix(theme): mark preview scopes for CSS button materials
dev-rb Oct 26, 2026
4913382
fix(channels): preserve the last header tab outline
dev-rb Oct 26, 2026
1e32935
style(email): use small signal and noise tabs
dev-rb Oct 26, 2026
928ab5e
style(ui): use medium channel and email tabs
dev-rb Oct 26, 2026
ffc4db1
refactor(ui): share control sizing between buttons and tabs
dev-rb Oct 26, 2026
78f6999
style(ui): increase medium tab horizontal padding
dev-rb Oct 26, 2026
0da1b8c
style(ui): reduce medium and large tab label sizes
dev-rb Oct 26, 2026
95beb78
style(ui): keep tabs flat across treatments
dev-rb Oct 26, 2026
52ade94
style(ui): align button typography with tab sizes
dev-rb Oct 26, 2026
31bc0ca
fix(ui): soften dark borders and restore create action contrast
dev-rb Oct 26, 2026
e8e7d44
refactor(ui): share semantic neutral border tokens
dev-rb Oct 26, 2026
686f17b
style(ui): remove dark secondary button borders
dev-rb Oct 26, 2026
4112f25
fix(ui): compact side-panel action buttons
dev-rb Oct 26, 2026
0ea58c1
chore: merge main into button redesign
dev-rb Oct 27, 2026
8a172b4
chore: merge main into button redesign
dev-rb Oct 27, 2026
84e3e5e
fix(ui): unify tooltip and copy button composition
dev-rb Oct 27, 2026
c0c588b
fix(ui): complete button caller and fixture migration
dev-rb Oct 27, 2026
a5a1ad0
style(ui): normalize migrated control props and formatting
dev-rb Oct 27, 2026
f98ad8e
chore(ci): refresh pull request checks
dev-rb Oct 27, 2026
9b2729c
fix(ui): hide detached tooltips and preserve pointer transparency
dev-rb Oct 27, 2026
301453a
chore: merge main into button redesign
dev-rb Oct 27, 2026
4ceb054
fix(properties): default pills to extra-small sizing
dev-rb Oct 27, 2026
28c8bf4
fix(sidebar): retain translated search tooltip
dev-rb Oct 27, 2026
6fe6e01
fix(ui): migrate time range clear button label
dev-rb Oct 27, 2026
707058f
fix(ui): compact tag pills and list pagination
dev-rb Oct 27, 2026
5c62d9f
fix(channels): compact attachment pagination button
dev-rb Oct 27, 2026
0cf58de
style(view-shell): use secondary collection search button
dev-rb Oct 27, 2026
8ed8d6e
refactor(ui): keep only the raised button finish
dev-rb Oct 27, 2026
fe96b3b
docs(ui): document the raised-only button finish
dev-rb Oct 27, 2026
14f74c5
refactor(ui)!: use accent styling for primary buttons
dev-rb Oct 27, 2026
ffe7e32
feat(ui): apply adaptive button finishes automatically
dev-rb Oct 27, 2026
2af701d
fix(ui): reuse shared elevation for button finishes
dev-rb Oct 27, 2026
1923167
fix(ui): keep outline buttons flat
dev-rb Oct 27, 2026
027810a
style(ui): shade light-theme button edges
dev-rb Oct 27, 2026
eed41a8
style(ui): remove light raised-button outlines
dev-rb Oct 27, 2026
8818c53
style(ui): restore white button highlights
dev-rb Oct 27, 2026
85c7d62
style(ui): simplify light-theme raised button edges
dev-rb Oct 27, 2026
d76c7f7
style(ui): align list-header control sizes
dev-rb Oct 27, 2026
60888c0
style(ui): remove normal button focus outlines
dev-rb Oct 27, 2026
63042d5
style(properties): tighten icon and label spacing
dev-rb Oct 27, 2026
7060be7
style(properties): restore icon and label spacing
dev-rb Oct 27, 2026
5481e40
chore: merge main into button redesign
dev-rb Oct 28, 2026
873b95f
style(properties): use ghost unpin actions
dev-rb Oct 28, 2026
09ff001
fix(ui): adapt incoming controls to the button API
dev-rb Oct 28, 2026
2608c0c
fix(properties): reduce unpin icon size
dev-rb Oct 28, 2026
a51d070
style(ui): soften dark secondary button backgrounds
dev-rb Oct 28, 2026
e373ddd
style(ui): soften dark secondary button state steps
dev-rb Oct 28, 2026
84d3a87
fix(ui): align view sidebar header control sizes
dev-rb Oct 28, 2026
de03ec5
style(ui): add a subtle accent tint to secondary buttons
dev-rb Oct 28, 2026
98c2916
fix(ui): retain light secondary borders when pressed
dev-rb Oct 28, 2026
bf2d7a3
fix(home): match filter button to sidebar header sizing
dev-rb Oct 28, 2026
832c702
fix(ui): restore the send icon's intended stroke weight
dev-rb Oct 28, 2026
b778583
fix(ui): use large composer action buttons
dev-rb Oct 28, 2026
31915db
feat(ui): restore solid monochrome buttons as neutral
dev-rb Oct 28, 2026
9dcd238
fix(ui): enlarge composer glyphs within medium buttons
dev-rb Oct 28, 2026
bfcfd21
style(ui): use neutral styling for composer send actions
dev-rb Oct 28, 2026
2632fc2
style(settings): use explicit neutral action buttons
dev-rb Oct 28, 2026
4a626f1
style(ui): adopt secondary buttons for neutral actions
dev-rb Oct 28, 2026
6bc4ef7
chore: merge latest main into button redesign
dev-rb Oct 28, 2026
a21962c
fix(ui): reconcile migrated controls with latest main
dev-rb Oct 28, 2026
cfacce7
style(ui): apply biome formatting to migrated controls
dev-rb Oct 28, 2026
c2cb6b3
chore: merge latest main into button redesign
dev-rb Oct 28, 2026
d000c0d
refactor(theme): consolidate runtime mode tokens
dev-rb Oct 28, 2026
bafb042
docs(ui): consolidate button guidance in agent rules
dev-rb Oct 28, 2026
09ead73
chore: merge latest main into button redesign
dev-rb Oct 28, 2026
6610932
fix(ui): separate disclosure expansion from active button styling
dev-rb Oct 28, 2026
ece75d1
style(pr): format detail view imports
dev-rb Oct 28, 2026
cfabc3c
fix(theme): update functional fixture theme import
dev-rb Oct 28, 2026
aff8792
chore: merge latest main into button redesign
dev-rb Oct 28, 2026
8bf2db4
test(ui): match details triggers by accessible name
dev-rb Oct 28, 2026
dedaa36
style(test): format the details button locator
dev-rb Oct 28, 2026
090ad8d
chore: merge latest main into button redesign
dev-rb Oct 28, 2026
348551b
chore: merge latest main into button redesign
dev-rb Oct 28, 2026
74e7d1c
chore: merge latest main and adapt routine template controls
dev-rb Oct 28, 2026
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
  •  
  •  
  •  
34 changes: 22 additions & 12 deletions .agents/rules/web/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,35 @@ construction. Browse them at `/app/component/ui` (Foundations → Controls).
`TabSelector` inside a `HeaderActionGroup` or `ButtonGroup`, and never render
a tab as a `Button`. A split button (`ButtonGroup` with a menu trigger) is
its own island; never nest it inside another, including through
`SidePanel.HeaderActions`. Enforced by ast-grep `tsx-tabs-not-in-action-group`
`SidePanel.HeaderActions`. Use `ButtonGroup.Button` for explicit segments and
`variant="secondary" finish="raised"` for action frames; ordinary Buttons stay
independent. Enforced by ast-grep `tsx-tabs-not-in-action-group`
and `tsx-no-button-as-tab`, CI error.
- **FE-62** Lay out a desktop header as one row: title or breadcrumbs, then
view tabs on the left; islands on the right in the order filters, view
options, New, Search (detail views: entity actions, sharing, navigation,
detail toggle). Every control in the row is 32px: `Tabs` `md`, `icon-md` for
icon-only buttons (16px glyph), `md` for labelled ones (14px glyph), 8px
between islands. Filter, sort, group, and download live in the header, never
detail toggle). Detail-header groups use `sm` labelled segments and `icon-sm`
icon-only segments (28px controls); Drive and Tasks list-header groups use
`md` / `icon-md`, matching Tabs, New, and Search. Keep 8px between islands. Filter, sort, group, and download live in the header, never
in a second toolbar row; only active filter chips sit below it.
- **FE-63** Style controls only through their props (`variant`, `size`,
`fullWidth`, `iconOnly`); `class` on a control is for placement (margin,
- **FE-63** Style controls only through their props (`variant`, `size`, `finish`,
`fullWidth`); `class` on a control is for placement (margin,
width, order, shrink). Never resize or recolor a control from a wrapper
(`[&_button]:size-8`), and don't use `square` for new icon buttons. Enforced
(`[&_button]:size-8`); use an explicit `icon-*` size for icon actions. Put glyph
sizing on the rendered icon, not on Button or ancestor SVG selectors. Enforced
for wrappers by ast-grep `tsx-no-descendant-button-styles`, warning.
- **FE-64** Keep one selected state: desktop view tabs share one rounded track with an inset selected pill; a selected tab and a pressed button share
the pressed fill; the inset selected pill has a subtle edge and shadow, while unselected options sit within the shared outer frame. No
accent text, underlines, or bespoke tints for selection on desktop. The one
exception is the agent composer's Auto/Chat/Code switch: `Tabs variant="track"`,
a raised pill sliding along the composer tray's track.
- Use `variant="primary"` for the main create, save, submit, invite, approve, or continue action. Primary uses the theme accent; the separate accent variant stays removed. Use `neutral` for solid monochrome emphasis: black with white text in light themes, white with black text in dark themes. Home/AI chat, channel, and email Send actions use the shared neutral `SendButton`. Keep the shared New launcher (`ViewCreateButton`) on `variant="secondary" finish="raised"`, and retain secondary, outline, ghost, and danger variants for their own roles.
- Current application Button usages trial `secondary` instead of `outline`, including shared wrappers and groups. Keep the outline API and canonical Button gallery examples; do not change outline variants on badges, inputs, or other components.
- Settings actions that previously used inverse-filled `cta` styling use explicit `variant="neutral"`, including creation, runtime setup, billing, invitation, and signature-save actions. Keep existing accent, ghost, and destructive actions in their own roles; do not recolor Button descendants from `SettingsPage`.
- Primary, neutral, secondary, danger, and success Buttons and ButtonGroup frames automatically use the raised desktop finish and shared glass on touch. Use `finish="flat"` to opt out or `finish="raised"` to frame a ghost/link action. Outline variants, bare controls, and grouped segments always stay flat.
- Follow the [Button and ButtonGroup contract](ui-primitives.md#button-and-buttongroup)
for accessible names, tooltips, sizing, explicit segments, and activation behavior.
- **FE-64** Components own their state treatments: desktop Tabs share a flat rounded track with an inset selected pill;
Button uses the semantic pressed fill for its variant. Mark action toggles with
`aria-pressed`, and expansion triggers with `aria-expanded`. Do not restyle
buttons as tabs. The agent composer's Auto/Chat/Code switch remains
`Tabs variant="track"`, a flat pill sliding along the composer's tray. Keep all
tab treatments flat: use borders and selected fills, not raised edges, gradients, or shadows.
- **FE-65** Components own their touch treatment. Never branch on
`isTouchDevice()` to choose a tab component or variant; set `Tabs`' `touch`
to the style that surface already uses on touch.
Expand Down
36 changes: 36 additions & 0 deletions .agents/rules/web/ui-primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,39 @@ Applies to `apps/web/src/components/ui`, including ports from shadcn-solid.
- In `InputGroup`, keep the input as the single control and place surrounding
content in `Addon` slots. Use `align` for visual placement so accessible DOM
order stays independent of layout.

## Button and ButtonGroup

- Keep the shared `Button` and `@ui` import; migrate callers directly instead of
adding parallel implementations or legacy-prop adapters. The role and variant
policy lives in [the design system rule](design-system.md).
- Buttons perform actions; navigation uses anchors. `variant="link"` changes
appearance, not semantics.
- Name icon-only actions with `aria-label`. Tooltips are independent: use a string
or `{ content, placement, disabled, hotkey, shortcut }`. Do not restore `label`,
root-level `hotkey`/`shortcut`, `square`, `depth`, `glass`, or `noTouchResize`.
- Use `xs` through `xl` for labeled actions and `icon-xs` through `icon-xl` for
square actions. Shared minimum heights are 24/28/32/40/48px; labeled controls
can grow with their content. Preserve the 36px touch minimum and size rendered
glyphs separately. Composer Send, microphone, and attachment controls use
`icon-md`, with 20px desktop and 24px touch glyphs.
- Keep ordinary children in the Button's flex row so trailing carets, truncation,
and custom spacing work. Only loading actions need the reserved-content layer.
Preserve native disabled behavior and activation guards for polymorphic controls.
- Compose groups explicitly with `ButtonGroup.Button`; standalone Buttons do not
inherit group styling. Segments inherit size and variant unless overridden,
remain flat, and own their hover/pressed/disabled states. The frame owns its
material once, without a whole-group hover treatment. Group labels use
`aria-label`; `CopyButton` joins with `as={ButtonGroup.Button}`.
- Keep `aria-expanded` on disclosure controls without treating expanded content
as a selected action. Button highlights an open popup only when the same
trigger has `aria-haspopup`; use `aria-pressed` for genuine action toggles.
- Keep Button's visible focus outline suppressed in ordinary color modes and
retain its forced-colors indicator. Do not remove other controls' focus styling.
- Keep Tooltip's assembled and compound APIs on the same implementation and
clean up closed or detached popups. Do not add document-wide mutation observers
to track individual triggers.
- Keep materials in `src/index.css`; JavaScript only derives accent text contrast
and the corresponding state-mix direction. Reset runtime material assignments
at theme boundaries so previews resolve their own palette. Do not persist
derived Button tokens in authored theme data.
6 changes: 6 additions & 0 deletions .agents/rules/web/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Components, styling, and interaction conventions.
default palette is disabled via `--color-*: initial` in
`apps/web/src/index.css`, so classes like `text-red-500` silently render
nothing. Enforced by ast-grep `tsx-no-raw-tailwind-palette`, CI error.
- Keep runtime theme tokens together in `apps/web/src/index.css`: one
`:root, [data-theme-light]` block for dark defaults, followed by one
`[data-theme-light='true']` override block. Keep any narrower root/device
conditions inside that theme area. `@theme` registers Tailwind tokens;
component utilities and layer/inversion rules keep their own responsibilities.
Do not append separate border, Button, glass, or avatar theme blocks elsewhere.
- **FE-26** Prefer composition over configurability; keep reusable components
small and free of queries/complex state.
- **FE-27** Don't add `cursor-pointer` to clickable elements. Enforced by
Expand Down
3 changes: 2 additions & 1 deletion apps/web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ the user's newest account for that app from the last fifteen minutes.
workflow and mobile controls. Use `ListHeader` for the single-row Email, Drive,
and Tasks headers: title, filters, view options, New, expandable Search. Put sidebar search in
`ViewSidebar.Header` so its icon precedes collapse and its field opens above New.
- Keep desktop headers on the shared `ListHeader` / `HeaderActionGroup` pattern: view `Tabs` as pills after the title, 32px semantic islands with 8px gaps on the right, fully rounded elevated frames, New then expandable Search; preserve touch layouts. Pick each control by role from the [design system rule](../../.agents/rules/web/design-system.md); see [UI conventions](../../docs/agent-guide/ui-conventions.md#top-bars). Never use recessed/inset backgrounds in headers.
- Keep desktop headers on the shared `ListHeader` / `HeaderActionGroup` pattern: view `Tabs` as pills after the title, `sm`/`icon-sm` semantic islands (28px controls) with 8px gaps on the right, fully rounded frames, New then expandable Search; retain `md` sizing for Tabs, New, and Search. Drive and Tasks list-header filter, layout, sort, and grouping islands use `md`/`icon-md` (32px) to match New and Search. Preserve touch layouts. Compose ButtonGroup actions with explicit `ButtonGroup.Button` segments; use `variant="secondary" finish="raised"` on action frames. Standalone Buttons do not inherit group styling. Pick each control by role from the [design system rule](../../.agents/rules/web/design-system.md); see [UI conventions](../../docs/agent-guide/ui-conventions.md#top-bars). Never use recessed/inset backgrounds in headers.
- Keep desktop model and effort controls separate: the model selector opens the catalog and the speedometer opens the runtime's advertised slider stops; retain the mobile effort sheet.
- Passive PR mentions must not retry initial GitHub status failures on remount; preserve confirmed status through refetch errors and keep pending data reads out of their parent Suspense boundary.
- Keep native booking exit navigation in the route shell, outside page loading boundaries, and use the router so the native hash route stays intact.
- Diagnose native full reloads with `native_webview_started`, `native_diagnostic_recovered`, and the shared `frontend_instance_id`; use `reloadApp(reason)` for frontend reloads and keep breadcrumbs content-free. Treat `previous_document_*` checkpoints as last-observed lifecycle evidence, never a crash verdict; clear them on logout. `previous_document_last_*` comes from the native navigation breadcrumb (route and entity type, never ids). See [frontend checkpoints](docs/native-reload-diagnostics.md) and `tauri/src-tauri/README.md`.
Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/components/app/DesktopTitleBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import { afterEach, expect, it, vi } from 'vitest';
import { DesktopTitleBar } from './DesktopTitleBar';

vi.mock('@ui', () => ({
Button: (
props: JSX.ButtonHTMLAttributes<HTMLButtonElement> & { label: string }
) => (
Button: (props: JSX.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
aria-label={props.label}
aria-label={props['aria-label']}
disabled={props.disabled}
onClick={props.onClick}
>
Expand Down
15 changes: 9 additions & 6 deletions apps/web/src/components/app/DesktopTitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export function DesktopTitleBar() {
>
<div class="flex items-center gap-1">
<Button
aria-label="Go Back"
tooltip={{ content: 'Go Back', hotkey: TOKENS.split.go.back }}
variant="ghost"
size="icon-sm"
label="Go Back"
hotkey={TOKENS.split.go.back}
disabled={!activeSplit()?.canGoBack()}
onClick={() => {
if (splitBackInterceptor()?.()) return;
Expand All @@ -32,10 +32,10 @@ export function DesktopTitleBar() {
<CaretLeftIcon class="size-4" />
</Button>
<Button
aria-label="Go Forward"
tooltip={{ content: 'Go Forward', hotkey: TOKENS.split.go.forward }}
variant="ghost"
size="icon-sm"
label="Go Forward"
hotkey={TOKENS.split.go.forward}
disabled={!activeSplit()?.canGoForward()}
onClick={() => activeSplit()?.goForward()}
>
Expand All @@ -51,10 +51,13 @@ export function DesktopTitleBar() {
<Show when={globalSplitManager()}>
{(manager) => (
<Button
aria-label="New Split"
tooltip={{
content: 'New Split',
hotkey: TOKENS.global.createNewSplit,
}}
variant="ghost"
size="icon-sm"
label="New Split"
hotkey={TOKENS.global.createNewSplit}
disabled={!manager().canAppendSplit()}
onClick={() =>
manager().openWithSplit(
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/app/FatalError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ export function FatalError(props: FatalErrorProps) {

<div class="flex flex-row gap-3 justify-center">
<Button
variant="strong"
variant="secondary"
onClick={() => {
window.location.href = window.location.origin + '/app';
}}
>
<HomeIcon class="size-4" /> Home
</Button>
<Button variant="outline" onClick={props.reset}>
<Button variant="secondary" onClick={props.reset}>
<ResetIcon class="size-4" /> Try Again
</Button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/app/FileDownloadHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function FileDownloadHeader(props: {
return (
<SidePanel.HeaderActions>
<HeaderActionGroup label="File download">
<FileDownloadButton download={props.download} />
<FileDownloadButton grouped download={props.download} />
</HeaderActionGroup>
</SidePanel.HeaderActions>
);
Expand Down
28 changes: 19 additions & 9 deletions apps/web/src/components/app/HeaderActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, cn } from '@ui';
import type { JSX, Ref } from 'solid-js';
import { Button, ButtonGroup, type ButtonSize, cn } from '@ui';
import { type JSX, type Ref, Show } from 'solid-js';

/** Shared geometry for labeled actions in a block's header. */
export function HeaderActionButton(props: {
Expand All @@ -11,27 +11,37 @@ export function HeaderActionButton(props: {
disabled?: boolean;
busy?: boolean;
class?: string;
grouped?: boolean;
size?: ButtonSize;
}) {
const Control = props.grouped ? ButtonGroup.Button : Button;
const iconOnly = () => props.size?.startsWith('icon-') ?? false;
return (
<Button
<Control
ref={props.ref}
variant="ghost"
size="md"
size={props.size ?? (props.grouped ? undefined : 'md')}
data-header-action
aria-label={props.label}
tooltip={props.tooltip}
onClick={props.onClick}
disabled={props.disabled}
aria-busy={props.busy}
class={cn(
'@max-[600px]/split-header:w-8 @max-[600px]/split-header:p-0',
!iconOnly() && 'px-2 @max-[600px]/split-header:p-0',
!iconOnly() &&
(props.grouped && props.size !== 'md'
? '@max-[600px]/split-header:w-7'
: '@max-[600px]/split-header:w-8'),
props.class
)}
>
{props.icon}
<span data-header-action-label class="@max-[600px]/split-header:hidden">
{props.label}
</span>
</Button>
<Show when={!iconOnly()}>
<span data-header-action-label class="@max-[600px]/split-header:hidden">
{props.label}
</span>
</Show>
</Control>
);
}
18 changes: 10 additions & 8 deletions apps/web/src/components/app/HeaderActionGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,31 @@ import { cn } from '@ui/utils/classname';
import { type ParentProps, Show } from 'solid-js';

/**
* A desktop header island: related actions share one elevated frame and
* their buttons stay flat. Touch renders the actions without a frame, and a
* A desktop header island: related actions share the secondary raised frame.
* Their segments stay flat. Touch renders actions without a frame, and a
* nested group reuses its parent's frame. An island with no actions renders
* nothing, so conditional children need no wrapper.
*
* @do Group actions by meaning: filters, view options, sharing, navigation, detail toggles.
* @do Use `size="icon-md"` for icon-only actions and `size="md"` for labelled ones.
* @do Use `size="icon-sm"` for icon-only actions; labelled segments inherit `sm`.
* @do Mark on/off toggles with `aria-pressed`; the pressed fill matches a selected tab.
* @dont Do not put Tabs, TabSelector, or Buttons acting as tabs in an island.
* @dont Do not add borders, shadows, or backgrounds to the buttons inside.
*/
export function HeaderActionGroup(
props: ParentProps<{ label: string; class?: string }>
props: ParentProps<{ label: string; class?: string; size?: 'sm' | 'md' }>
) {
const group = useButtonGroupContext();
return (
<Show when={!isTouchDevice() && !group} fallback={props.children}>
<ButtonGroup
label={props.label}
variant="outline"
size="md"
aria-label={props.label}
variant="secondary"
finish="raised"
size={props.size ?? 'sm'}
class={cn(
'hidden h-8 shrink-0 has-[[data-button]]:inline-flex',
'hidden shrink-0 border-0 has-[[data-button]]:inline-flex',
props.size === 'md' ? 'min-h-8' : 'min-h-7',
props.class
)}
>
Expand Down
Loading
Loading