Skip to content
Closed
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
106 changes: 64 additions & 42 deletions frontend/documentation/components/Chip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from 'storybook'

import Chip from 'components/base/Chip'
import Icon, { IconName } from 'components/icons/Icon'
import Constants from 'common/constants'
import { contentColours } from 'common/theme/tokens'
import { getTagSwatchUtilities } from 'components/tags/tagSwatch'

const meta: Meta<typeof Chip> = {
Expand All @@ -14,7 +14,7 @@ const meta: Meta<typeof Chip> = {
docs: {
description: {
component:
'Canonical token-based chip primitive: a small labelled pill token. Layout via Bootstrap utilities, colour/radius via token utilities, padding/sizes/border/truncation in SCSS. Leading/trailing icons go in as children. `none` leaves the colour to the caller, for a decorative colour a user picked rather than a semantic role. Selection lives in ToggleChip and count badges are a separate Badge concern. The legacy `.chip` (old SCSS vars + manual dark-mode block, ~35×) migrates onto this under #6606.',
'Canonical token-based chip primitive: a small labelled pill token. Layout via Bootstrap utilities, colour/radius via token utilities, padding/sizes/border/truncation in SCSS. Leading/trailing icons go in as children. `none` leaves the colour to the caller, for a decorative colour a user picked rather than a semantic role. `selected` renders a leading checkbox with a tick. The legacy `.chip` (old SCSS vars + manual dark-mode block, ~35×) migrates onto this under #6606.',
},
},
layout: 'centered',
Expand All @@ -31,41 +31,9 @@ export const Accent: Story = {
args: { children: '"hello"', variant: 'accent' },
}

export const Sizes: Story = {
render: () => (
<div className='d-flex align-items-center gap-2'>
<Chip size='default'>Default</Chip>
<Chip size='sm'>Small</Chip>
<Chip size='xs'>Extra small</Chip>
</div>
),
}

export const Removable: Story = {
args: { children: 'feature-flag', onRemove: () => undefined },
}

export const Truncated: Story = {
args: {
children: '{ "test": "testvalue-that-keeps-going-and-going" }',
truncate: true,
variant: 'accent',
},
}

export const Group: Story = {
render: () => (
<div className='d-flex flex-wrap gap-2'>
<Chip>Development</Chip>
<Chip variant='accent'>Staging</Chip>
<Chip onRemove={() => undefined}>Production</Chip>
</div>
),
}

// How Tag composes Chip: a decorative colour a user picked is not a semantic
// variant, so it arrives as a tag-* swatch class in className. System tags take
// none of it, and carry their state in the icon instead.
// variant, so it arrives as a tag-* swatch class in className. System
// tags take none of it, and carry their state in the icon instead.
const SYSTEM_TAGS: { label: string; icon: IconName }[] = [
{ icon: 'issue-closed', label: 'Issue closed' },
{ icon: 'issue-linked', label: 'Issue open' },
Expand All @@ -79,14 +47,14 @@ const SYSTEM_TAGS: { label: string; icon: IconName }[] = [

export const AsSystemTag: Story = {
name: 'As a system tag',
parameters: { chromatic: { disableSnapshot: false } },
render: () => (
<div className='d-flex flex-wrap gap-2'>
{SYSTEM_TAGS.map(({ icon, label }) => (
<Chip
className='bg-surface-default border-default text-default'
key={label}
size='sm'
variant='none'
size='xs'
>
{label}
<Icon name={icon} />
Expand All @@ -98,18 +66,72 @@ export const AsSystemTag: Story = {

export const AsCustomTag: Story = {
name: 'As a custom tag',
parameters: { chromatic: { disableSnapshot: false } },
render: () => (
<div className='d-flex flex-wrap gap-2'>
{Constants.tagColors.map((colour: string) => (
{Object.values(contentColours).map((colour) => (
<Chip
className={getTagSwatchUtilities(colour)}
className={`border-0 ${getTagSwatchUtilities(colour)}`}
key={colour}
size='sm'
variant='none'
size='xs'
>
Custom
</Chip>
))}
</div>
),
}

/**
* `selected` rings the chip and fades the ones that are not chosen. For a cloud
* of filter chips the chip is the whole control, and anything drawn inside it
* competes with the colour the user picked. See TagFilter.
*/
export const SelectedAsARing: Story = {
name: 'Selected',
render: () => (
<div className='d-flex flex-wrap gap-2 align-items-center'>
<Chip className='tag-light-green' selected>
onboarding
</Chip>
<Chip className='tag-light-mint' selected={false}>
analytics
</Chip>
<Chip className='tag-blue' selected={false}>
billing
</Chip>
</div>
),
}

export const Sizes: Story = {
render: () => (
<div className='d-flex align-items-center gap-2'>
<Chip size='default'>Default</Chip>
<Chip size='sm'>Small</Chip>
<Chip size='xs'>Extra small</Chip>
</div>
),
}

export const Removable: Story = {
args: { children: 'feature-flag', onRemove: () => undefined },
}

export const Truncated: Story = {
args: {
children: '{ "test": "testvalue-that-keeps-going-and-going" }',
truncate: true,
variant: 'accent',
},
}

export const Group: Story = {
render: () => (
<div className='d-flex flex-wrap gap-2'>
<Chip>Development</Chip>
<Chip variant='accent'>Staging</Chip>
<Chip onRemove={() => undefined}>Production</Chip>
</div>
),
}
112 changes: 112 additions & 0 deletions frontend/documentation/components/TagRow.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useState } from 'react'
import type { Meta, StoryObj } from 'storybook'
import type { Tag } from 'common/types/responses'

import TagRow from 'components/tags/TagRow'
import DropdownMenu from 'components/base/DropdownMenu'

const tag = (over: Partial<Tag>): Tag => ({
color: '#c7e6ff',
description: '',
id: 1,
is_permanent: false,
is_system_tag: false,
label: 'billing',
project: 1,
type: 'NONE',
...over,
})

const meta: Meta<typeof TagRow> = {
args: { tag: tag({}) },
component: TagRow,
parameters: {
chromatic: { disableSnapshot: false },
docs: {
description: {
component:
'One tag in a list. Selection is marked on the row rather than inside the chip: a tag is a colour and a name, and whether it is currently picked is a fact about the row. Drawing it inside meant the chip fill ran behind a control that was not part of the tag. The mark is a trailing checkmark, matching every other list in the app: assignees, groups, roles and the table filters.',
},
},
layout: 'padded',
},
title: 'Components/Tags/TagRow',
}
export default meta

type Story = StoryObj<typeof TagRow>

/** No mark: a row that shows a tag without offering to select it. */
export const ReadOnly: Story = { name: 'Not selectable' }

export const Unchecked: Story = {
args: { checked: false, onToggle: () => undefined },
}

export const Checked: Story = {
args: { checked: true, onToggle: () => undefined },
}

/** A stale tag on a plan without stale flags. Faded once, not twice. */
export const Disabled: Story = {
args: {
checked: false,
disabled: true,
onToggle: () => undefined,
tag: tag({ label: 'Stale', type: 'STALE' }),
},
}

export const WithUsage: Story = {
args: { checked: false, onToggle: () => undefined, trailing: '3 features' },
name: 'With a usage count',
}

export const WithMenu: Story = {
args: {
checked: false,
onToggle: () => undefined,
trailing: (
<DropdownMenu
items={[
{ icon: 'setting', label: 'Edit', onClick: () => undefined },
{ icon: 'trash-2', label: 'Delete', onClick: () => undefined },
]}
/>
),
},
name: 'With a menu',
}

/** Selection has to survive a click, which static args cannot show. */
const List: React.FC = () => {
const [picked, setPicked] = useState<number[]>([1])
const tags = [
tag({ color: '#c7e6ff', id: 1, label: 'billing' }),
tag({ color: '#c7e7e2', id: 2, label: 'analytics' }),
tag({ color: '#d6f1d4', id: 3, label: 'onboarding' }),
tag({ color: '#eff0f3', id: 4, label: 'archived' }),
]
return (
<div className='d-flex flex-column' style={{ maxWidth: 380 }}>
{tags.map((t) => (
<TagRow
checked={picked.includes(t.id)}
key={t.id}
onToggle={() =>
setPicked((p) =>
p.includes(t.id) ? p.filter((id) => id !== t.id) : [...p, t.id],
)
}
tag={t}
trailing={`${t.id} features`}
/>
))}
</div>
)
}

export const AList: Story = {
name: 'A list',
render: () => <List />,
}
49 changes: 0 additions & 49 deletions frontend/documentation/components/ToggleChip.stories.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import type { Meta, StoryObj } from 'storybook'

import VCSProviderTag from 'components/tags/VCSProviderTag'
import VCSProviderTag from 'components/VCSProviderTag'
import { VCSProvider } from 'common/types/responses'

const meta: Meta<typeof VCSProviderTag> = {
Expand Down
20 changes: 0 additions & 20 deletions frontend/web/components/ToggleChip.scss

This file was deleted.

40 changes: 0 additions & 40 deletions frontend/web/components/ToggleChip.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { GithubIcon } from 'components/icons/GithubIcon'
import GitlabIcon from 'components/icons/GitlabIcon'
import { GithubIcon } from './icons/GithubIcon'
import GitlabIcon from './icons/GitlabIcon'
import { VCSProvider } from 'common/types/responses'

interface VCSProviderTagProps {
Expand Down
Loading
Loading