Skip to content
Draft
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 .changeset/nav-button-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@workflowbuilder/ui': major
'@workflowbuilder/sdk': minor
---

`NavButton` takes `size`, `variant` (`square`, `round`, `plain`), `prefixIcon`, `suffixIcon` and `children` instead of inferring a subtype from the children structure, sizes are letter-based, and the selected state no longer shares a treatment with the pointer-down state. `MenuTriggerButton` is new, and `SegmentPicker` keeps its API while adopting the new slots.
2 changes: 1 addition & 1 deletion .changeset/ui-export-prop-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'@workflowbuilder/ui': minor
---

Component prop types are now exported: `AvatarProps`, `CheckboxProps`, `RadioProps`, `StatusProps`, `TooltipProps`, `MenuProps`, `ModalProps`, `EdgeLabelProps`, `NodeIconProps`, `NodeDescriptionProps`, `NodeAsPortWrapperProps`, `SegmentPickerProps` (with its controlled/uncontrolled variants), the NavButton variant prop types, and `DatePickerProps` now covers the component's full runtime surface (`value`, `defaultValue`, `placeholder`, `valueFormat`, `type`, `error`). Supporting types used in those signatures (`Shape`, `IconNode`) are exported as well.
Component prop types are now exported: `AvatarProps`, `CheckboxProps`, `RadioProps`, `StatusProps`, `TooltipProps`, `MenuProps`, `ModalProps`, `EdgeLabelProps`, `NodeIconProps`, `NodeDescriptionProps`, `NodeAsPortWrapperProps`, `SegmentPickerProps` (with its controlled/uncontrolled variants), `NavButtonProps`, and `DatePickerProps` now covers the component's full runtime surface (`value`, `defaultValue`, `placeholder`, `valueFormat`, `type`, `error`). Supporting types used in those signatures (`Shape`, `IconNode`) are exported as well.
28 changes: 19 additions & 9 deletions apps/ai-studio/src/components/controls/ai-studio-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,28 @@ export function AiStudioControls() {
>
<div className={styles['panel']}>
{isRunning ? (
<NavButton onClick={cancel} tooltip="Cancel execution">
<Icon name="Stop" />
</NavButton>
<NavButton
aria-label="Cancel execution"
onClick={cancel}
tooltip="Cancel execution"
prefixIcon={<Icon name="Stop" />}
/>
) : (
<NavButton onClick={handleExecute} tooltip="Execute (backend)" disabled={isRunning}>
<Icon name="Play" />
</NavButton>
<NavButton
aria-label="Execute (backend)"
onClick={handleExecute}
tooltip="Execute (backend)"
disabled={isRunning}
prefixIcon={<Icon name="Play" />}
/>
)}
{isDone && (
<NavButton onClick={reset} tooltip="Reset">
<Icon name="ArrowCounterClockwise" />
</NavButton>
<NavButton
aria-label="Reset"
onClick={reset}
tooltip="Reset"
prefixIcon={<Icon name="ArrowCounterClockwise" />}
/>
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ export function ButtonsUndoRedo() {

return (
<>
<NavButton onClick={undo} disabled={!canUndo || isReadOnlyMode} tooltip={t('plugins.undoRedo.undo')}>
<Icon name="ArrowUUpLeft" />
</NavButton>
<NavButton onClick={redo} disabled={!canRedo || isReadOnlyMode} tooltip={t('plugins.undoRedo.redo')}>
<Icon name="ArrowUUpRight" />
</NavButton>
<NavButton
aria-label={t('plugins.undoRedo.undo')}
onClick={undo}
disabled={!canUndo || isReadOnlyMode}
tooltip={t('plugins.undoRedo.undo')}
prefixIcon={<Icon name="ArrowUUpLeft" />}
/>
<NavButton
aria-label={t('plugins.undoRedo.redo')}
onClick={redo}
disabled={!canRedo || isReadOnlyMode}
tooltip={t('plugins.undoRedo.redo')}
prefixIcon={<Icon name="ArrowUUpRight" />}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ export function ButtonsUndoRedo() {

return (
<>
<NavButton onClick={undo} disabled={!canUndo || isReadOnlyMode} tooltip={t('plugins.undoRedo.undo')}>
<Icon name="ArrowUUpLeft" />
</NavButton>
<NavButton onClick={redo} disabled={!canRedo || isReadOnlyMode} tooltip={t('plugins.undoRedo.redo')}>
<Icon name="ArrowUUpRight" />
</NavButton>
<NavButton
aria-label={t('plugins.undoRedo.undo')}
onClick={undo}
disabled={!canUndo || isReadOnlyMode}
tooltip={t('plugins.undoRedo.undo')}
prefixIcon={<Icon name="ArrowUUpLeft" />}
/>
<NavButton
aria-label={t('plugins.undoRedo.redo')}
onClick={redo}
disabled={!canRedo || isReadOnlyMode}
tooltip={t('plugins.undoRedo.redo')}
prefixIcon={<Icon name="ArrowUUpRight" />}
/>
</>
);
}
8 changes: 7 additions & 1 deletion apps/docs/scripts/ui-components.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export const COMPONENTS = [
cssSources: ['shared/styles/field-control-size.module.css'],
},
{ slug: 'menu', name: 'Menu', propsType: 'MenuProps', dir: 'menu' },
{
slug: 'menu-trigger-button',
name: 'MenuTriggerButton',
propsType: 'MenuTriggerButtonProps',
dir: 'button/menu-trigger-button',
},
{ slug: 'modal', name: 'Modal', propsType: 'ModalProps', dir: 'modal' },
{
slug: 'number-field',
Expand All @@ -37,7 +43,7 @@ export const COMPONENTS = [
{
slug: 'nav-button',
name: 'NavButton',
propsType: ['NavLabelButtonProps', 'NavIconButtonProps', 'NavIconLabelButtonProps'],
propsType: ['NavLabelButtonProps', 'NavIconButtonProps'],
dir: 'button/nav-button',
},
{ slug: 'radio', name: 'Radio', propsType: 'RadioProps', dir: 'radio-button' },
Expand Down
27 changes: 27 additions & 0 deletions apps/docs/src/components/ui-examples/menu-trigger-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DotsThreeVertical } from '@phosphor-icons/react';
import { Menu, MenuTriggerButton } from '@workflowbuilder/ui';
import { useState } from 'react';

import { ComponentPreview } from './component-preview';

export function MenuTriggerButtonExample() {
const [isOpen, setIsOpen] = useState(false);

return (
<ComponentPreview>
<Menu
open={isOpen}
onOpenChange={setIsOpen}
items={[
{ label: 'Edit', onClick: () => {} },
{ label: 'Duplicate', onClick: () => {} },
{ label: 'Delete', destructive: true, onClick: () => {} },
]}
>
<MenuTriggerButton isOpen={isOpen} aria-label="Open menu">
<DotsThreeVertical />
</MenuTriggerButton>
</Menu>
</ComponentPreview>
);
}
12 changes: 12 additions & 0 deletions apps/docs/src/components/ui-examples/nav-button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.rows {
display: flex;
flex-direction: column;
gap: var(--wb-space-150);
}

.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--wb-space-100);
}
25 changes: 23 additions & 2 deletions apps/docs/src/components/ui-examples/nav-button.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { NavButton } from '@workflowbuilder/ui';
import { ArrowRight, House, Plus } from '@phosphor-icons/react';
import { NAV_BUTTON_SIZES, NavButton } from '@workflowbuilder/ui';

import styles from './nav-button.module.css';

import { ComponentPreview } from './component-preview';

export function NavButtonExample() {
return (
<ComponentPreview>
<NavButton>Nav button</NavButton>
<div className={styles['rows']}>
<div className={styles['row']}>
<NavButton prefixIcon={<House />}>Square</NavButton>
<NavButton variant="round" prefixIcon={<House />}>
Round
</NavButton>
<NavButton variant="plain" prefixIcon={<House />}>
Plain
</NavButton>
<NavButton isSelected prefixIcon={<House />} suffixIcon={<ArrowRight />}>
Selected
</NavButton>
</div>
<div className={styles['row']}>
{NAV_BUTTON_SIZES.map((size) => (
<NavButton key={size} aria-label={`Add (${size})`} size={size} prefixIcon={<Plus />} />
))}
</div>
</div>
</ComponentPreview>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ live, interactive example plus the component's props and CSS variables.
- [DatePicker](/ui-library/ui-components/date-picker/) - Date selection with a calendar popover.
- [Input](/ui-library/ui-components/input/) - Text input with icons and explicit field states.
- [Menu](/ui-library/ui-components/menu/) - Popup menu for dropdowns.
- [MenuTriggerButton](/ui-library/ui-components/menu-trigger-button/) - Icon button for opening a menu.
- [Modal](/ui-library/ui-components/modal/) - Dialog overlay with a backdrop.
- [NavButton](/ui-library/ui-components/nav-button/) - Compact icon / label navigation button.
- [NumberField](/ui-library/ui-components/number-field/) - Numeric input with stepper controls.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Menu Trigger Button
description: A compact icon button that reflects whether its menu is open.
---

import CssVariablesTable from '../../../../components/api/css-variables-table.astro';
import PropsTable from '../../../../components/api/props-table.astro';
import { MenuTriggerButtonExample } from '../../../../components/ui-examples/menu-trigger-button';

`MenuTriggerButton` is a fixed-size NavButton composition for menu trigger icons.
Set `isOpen` from the menu state to render the persistent Pressed state.

<MenuTriggerButtonExample client:visible />

## Usage

```tsx
import { DotsThreeVertical } from '@phosphor-icons/react';
import { Menu, MenuTriggerButton } from '@workflowbuilder/ui';
import { useState } from 'react';

function Example() {
const [isOpen, setIsOpen] = useState(false);

return (
<Menu items={[{ label: 'Edit', onClick: () => {} }]} open={isOpen} onOpenChange={setIsOpen}>
<MenuTriggerButton isOpen={isOpen} aria-label="Open menu">
<DotsThreeVertical />
</MenuTriggerButton>
</Menu>
);
}
```

## Props

<PropsTable slug="menu-trigger-button" />

## CSS variables

It renders a `NavButton`, so its appearance is customized through the
[NavButton variables](/docs/ui-library/ui-components/nav-button/#css-variables).

<CssVariablesTable slug="menu-trigger-button" />
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
---
title: Nav Button
description: A compact, type-safe navigation button with a selected state.
description: A compact navigation button with explicit content slots and a selected state.
---

import CssVariablesTable from '../../../../components/api/css-variables-table.astro';
import PropsTable from '../../../../components/api/props-table.astro';
import { NavButtonExample } from '../../../../components/ui-examples/nav-button';

`NavButton` is a compact, type-safe navigation button. Like `Button`, it
automatically selects the correct type (label, icon, or icon + label) based on
the structure of its `children`, and it adds an `isSelected` state for marking
the active item.
`NavButton` renders its label from `children` and accepts optional `prefixIcon`
and `suffixIcon` props. Leave `children` empty and provide `prefixIcon` for a
square icon-only button, and use `variant` to select square, round, or
no-background styling.

The `isSelected` prop renders the persistent Pressed state, including while the
button is hovered. The mouse-down `:active` state uses a separate Active token.

<NavButtonExample client:visible />

## Usage

```tsx
import { House } from '@phosphor-icons/react';
import { NavButton } from '@workflowbuilder/ui';

function Example() {
return <NavButton isSelected>Overview</NavButton>;
return (
<NavButton isSelected prefixIcon={<House />}>
Overview
</NavButton>
);
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ export function Controls() {
{items.length > 0 && (
<div className={styles['menu-container']}>
<Menu items={items}>
<NavButton tooltip={t('tooltips.menu')}>
<DotsThreeVertical />
</NavButton>
<NavButton
aria-label={t('tooltips.menu')}
tooltip={t('tooltips.menu')}
prefixIcon={<DotsThreeVertical />}
/>
</Menu>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,11 @@ function ProjectSelectionComponent({ onDuplicateClick }: ProjectSelectionProps)
)}
<div className={styles['menu-container']}>
<Menu items={items}>
<NavButton tooltip={t('tooltips.pickTheProject')}>
<CaretDown />
</NavButton>
<NavButton
aria-label={t('tooltips.pickTheProject')}
tooltip={t('tooltips.pickTheProject')}
prefixIcon={<CaretDown />}
/>
</Menu>
</div>
</div>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { useTranslation } from 'react-i18next';

import { Icon } from '@workflow-builder/icons';

import styles from './language-selector.module.css';

type Language = {
code: string;
label: string;
Expand Down Expand Up @@ -36,11 +34,12 @@ export function LanguageSelector() {
return (
<>
<Menu items={languageItems} size="small">
<NavButton tooltip={t('tooltips.changeLanguage')}>
<>
<span className={styles['title']}>{currentLanguage.code.toUpperCase()}</span>
<CaretDown />
</>
<NavButton
aria-label={t('tooltips.changeLanguage')}
suffixIcon={<CaretDown />}
tooltip={t('tooltips.changeLanguage')}
>
{currentLanguage.code.toUpperCase()}
</NavButton>
</Menu>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ export function SaveButton() {
useAutoSaveOnClose();

return (
<NavButton onClick={handleSave} tooltip={t('tooltips.save')}>
<>
<SavingStatus />
<Icon name="FloppyDisk" />
</>
</NavButton>
<NavButton
aria-label={t('tooltips.save')}
onClick={handleSave}
tooltip={t('tooltips.save')}
prefixIcon={
<>
<SavingStatus />
<Icon name="FloppyDisk" />
</>
}
/>
);
}
Loading
Loading