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
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,16 @@ const sizeMap: Record<SAILSize, string> = {
/>
```

### Toggle (switch for boolean input — a!toggleField)
```tsx
<ToggleField choiceLabel="Enable Notifications" value={true} saveInto={setValue} />
```

### ButtonToggle (button-style on/off toggle)
```tsx
<ButtonToggle text="Bold" icon="bold" style="SOLID" value={pressed} saveInto={setPressed} />
```

## Validation

Run `npm run build` to catch TypeScript errors before considering work complete.
Expand Down
13 changes: 6 additions & 7 deletions src/components/ApplicationHeader/ApplicationHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react'
import { ButtonWidget } from '../Button/ButtonWidget'
import { ButtonArrayLayout } from '../Button/ButtonArrayLayout'
import { SwitchField } from '../Switch/SwitchField'
import { ToggleField } from '../Toggle/ToggleField'
import { ButtonToggle } from '../ButtonToggle/ButtonToggle'
import type { SAILColorInput } from '../../types/sail'
import { paletteHexMap, type SAILPaletteColor } from '../../types/palette-colors.generated'
import {
Expand Down Expand Up @@ -144,15 +144,15 @@ export const ApplicationHeader: React.FC<ApplicationHeaderProps> = ({
{showDesignerControls && (
<>
<div className="flex items-center gap-0 ml-4 bg-gray-50 p-1 rounded-sm">
<ToggleField
<ButtonToggle
icon="square-dashed-mouse-pointer"
style="GHOST"
size="SMALL"
value={!showStoriesView}
saveInto={showStoriesView ? onStoryToggle : undefined}
marginBelow="NONE"
/>
<ToggleField
<ButtonToggle
icon="code"
style="GHOST"
size="SMALL"
Expand All @@ -162,9 +162,8 @@ export const ApplicationHeader: React.FC<ApplicationHeaderProps> = ({
</div>

<div className="mt-1 [&>div>div]:flex-row-reverse [&>div>div]:gap-2 [&_label]:mb-1 scale-75">
<SwitchField
label="Preview"
labelPosition="ADJACENT"
<ToggleField
choiceLabel="Preview"
value={previewEnabled}
saveInto={onPreviewToggle}
marginBelow="NONE"
Expand Down Expand Up @@ -207,7 +206,7 @@ export const ApplicationHeader: React.FC<ApplicationHeaderProps> = ({
/>
</div>
<div className="[&_button]:border-0">
<ToggleField
<ButtonToggle
icon="book-open-text"
style={!showStoriesView ? "GHOST" : "SOLID"}
size="SMALL"
Expand Down
188 changes: 188 additions & 0 deletions src/components/ButtonToggle/ButtonToggle.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { expect, userEvent, within } from 'storybook/test'
import { useState } from 'react'
import { ButtonToggle } from './ButtonToggle'

const meta = {
title: 'Components/ButtonToggle',
component: ButtonToggle,
tags: ['autodocs'],
parameters: { layout: 'centered' },
} satisfies Meta<typeof ButtonToggle>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
label: 'Text Formatting',
text: 'Bold',
value: false,
style: 'SOLID',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const toggle = canvas.getByRole('button', { name: /bold/i })
await expect(toggle).toBeVisible()
await expect(toggle).toHaveAttribute('aria-pressed', 'false')
await userEvent.click(toggle)
await expect(toggle).toHaveAttribute('aria-pressed', 'true')
await userEvent.click(toggle)
await expect(toggle).toHaveAttribute('aria-pressed', 'false')
},
}

export const WithIcon: Story = {
args: {
label: 'Favorite',
text: 'Add to Favorites',
icon: 'star',
value: true,
color: 'ACCENT',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const StyleSolid: Story = {
args: {
label: 'SOLID Style',
text: 'Toggle Me',
value: false,
style: 'SOLID',
color: 'ACCENT',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const StyleOutline: Story = {
args: {
label: 'OUTLINE Style',
text: 'Toggle Me',
value: false,
style: 'OUTLINE',
color: 'ACCENT',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const StyleGhost: Story = {
args: {
label: 'GHOST Style',
text: 'Toggle Me',
value: false,
style: 'GHOST',
color: 'ACCENT',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const IconAtStart: Story = {
args: {
label: 'Icon at START',
text: 'Filter',
icon: 'filter',
iconPosition: 'START',
value: false,
style: 'OUTLINE',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const IconAtEnd: Story = {
args: {
label: 'Icon at END',
text: 'Search',
icon: 'arrow-right',
iconPosition: 'END',
value: false,
style: 'OUTLINE',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const ColorAccent: Story = {
args: {
label: 'ACCENT',
text: 'Accent Color',
value: false,
color: 'ACCENT',
style: 'OUTLINE',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const ColorPositive: Story = {
args: {
label: 'POSITIVE',
text: 'Positive Color',
value: false,
color: 'POSITIVE',
style: 'OUTLINE',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const ColorNegative: Story = {
args: {
label: 'NEGATIVE',
text: 'Negative Color',
value: false,
color: 'NEGATIVE',
style: 'OUTLINE',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const ColorSecondary: Story = {
args: {
label: 'SECONDARY',
text: 'Secondary Color',
value: false,
color: 'SECONDARY',
style: 'OUTLINE',
},
render: (args) => {
const [value, setValue] = useState(args.value)
return <ButtonToggle {...args} value={value} saveInto={setValue} />
},
}

export const Disabled: Story = {
args: {
label: 'Disabled Toggle',
text: "Can't Click Me",
value: true,
disabled: true,
},
}
Loading
Loading