Skip to content

Commit

Permalink
test: Add Stories for new control components
Browse files Browse the repository at this point in the history
  • Loading branch information
kiosion committed Nov 15, 2024
1 parent 491b36d commit 06c37c7
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 13 deletions.
148 changes: 148 additions & 0 deletions web/packages/shared/components/Controls/MultiselectMenu.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useState } from 'react';
import { Flex } from 'design';

import { MultiselectMenu } from './MultiselectMenu';

import type { Meta, StoryFn, StoryObj } from '@storybook/react';

export default {
title: 'Shared/Controls/MultiselectMenu',
component: MultiselectMenu,
argTypes: {
buffered: {
control: { type: 'boolean' },
description: 'Buffer selections until "Apply" is clicked',
table: { defaultValue: { summary: 'false' } },
},
showIndicator: {
control: { type: 'boolean' },
description: 'Show indicator when there are selected options',
table: { defaultValue: { summary: 'true' } },
},
showSelectControls: {
control: { type: 'boolean' },
description: 'Show select controls (Select All/Select None)',
table: { defaultValue: { summary: 'true' } },
},
label: {
control: { type: 'text' },
description: 'Label for the multiselect',
},
tooltip: {
control: { type: 'text' },
description: 'Tooltip for the label',
},
selected: {
control: false,
description: 'Currently selected options',
table: { type: { summary: 'T[]' } },
},
onChange: {
control: false,
description: 'Callback when selection changes',
table: { type: { summary: 'selected: T[]' } },
},
options: {
control: false,
description: 'Options to select from',
table: {
type: {
summary:
'Array<{ value: T; label: string | ReactNode; disabled?: boolean; disabledTooltip?: string; }>',
},
},
},
},
args: {
label: 'Select Options',
tooltip: 'Choose multiple options',
buffered: false,
showIndicator: true,
showSelectControls: true,
},
parameters: { controls: { expanded: true, exclude: ['userContext'] } },
} satisfies Meta<typeof MultiselectMenu>;

type Story = StoryObj<typeof MultiselectMenu>;

type OptionValue = 'option1' | 'option2' | 'option3' | 'option4';

const options: {
value: OptionValue;
label: string | React.ReactNode;
disabled?: boolean;
disabledTooltip?: string;
}[] = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
{ value: 'option4', label: 'Option 4' },
];

const Template: StoryFn<typeof MultiselectMenu> = args => {
const [selected, setSelected] = useState<string[]>([]);
return (
<Flex
alignItems="center"
justifyContent="start"
width="100%"
minHeight="100px"
>
<MultiselectMenu {...args} selected={selected} onChange={setSelected} />
</Flex>
);
};

export const Default: Story = {
args: { options },
render: Template,
};

const customOptions: typeof options = [
{
value: 'option1',
label: <strong>Bold Option 1</strong>,
},
{
value: 'option3',
label: <em>Italic Option 3</em>,
},
];

export const WithCustomLabels: Story = {
args: { options: customOptions },
render: Template,
};

export const WithDisabledOption: Story = {
args: {
options: [
...options,
{
value: 'option5',
label: 'Option 5',
disabled: true,
disabledTooltip: 'Option is disabled',
},
],
},
render: Template,
};
3 changes: 1 addition & 2 deletions web/packages/shared/components/Controls/MultiselectMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,7 @@ export const MultiselectMenu = <T extends string>({
<>
<CheckboxInput
type="checkbox"
// @ts-expect-error assigning ReactNode to checkbox name field
name={opt.label}
name={opt.value}
disabled={opt.disabled}
onChange={() => {
handleSelect(opt.value);
Expand Down
88 changes: 88 additions & 0 deletions web/packages/shared/components/Controls/SortMenu.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useState } from 'react';
import { Flex } from 'design';

import { SortMenu } from './SortMenu';

import type { Meta, StoryFn, StoryObj } from '@storybook/react';

export default {
title: 'Shared/Controls/SortMenu',
component: SortMenu<any>,
argTypes: {
current: {
control: false,
description: 'Current sort',
table: {
type: {
summary:
"Array<{ fieldName: Exclude<keyof T, symbol | number>; dir: 'ASC' | 'DESC'>",
},
},
},
fields: {
control: false,
description: 'Fields to sort by',
table: {
type: {
summary:
'{ value: Exclude<keyof T, symbol | number>; label: string }[]',
},
},
},
onChange: {
control: false,
description: 'Callback when fieldName or dir is changed',
table: {
type: {
summary:
"(value: { fieldName: Exclude<keyof T, symbol | number>; dir: 'ASC' | 'DESC' }) => void",
},
},
},
},
args: {
current: { fieldName: 'name', dir: 'ASC' },
fields: [
{ value: 'name', label: 'Name' },
{ value: 'created', label: 'Created' },
{ value: 'updated', label: 'Updated' },
],
},
parameters: { controls: { expanded: true, exclude: ['userContext'] } },
} satisfies Meta<typeof SortMenu<any>>;

type Story = StoryObj<typeof SortMenu>;

export const Default: Story = {
render: (({ current, fields }) => {
const [sort, setSort] = useState(current);
return (
<Flex
alignItems="center"
justifyContent="start"
width="100%"
minHeight="100px"
>
<SortMenu current={sort} fields={fields} onChange={setSort} />
</Flex>
);
}) satisfies StoryFn<typeof SortMenu>,
};
66 changes: 66 additions & 0 deletions web/packages/shared/components/Controls/ViewModeSwitch.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useState } from 'react';
import { Flex } from 'design';

import { ViewMode } from 'gen-proto-ts/teleport/userpreferences/v1/unified_resource_preferences_pb';

import { ViewModeSwitch } from './ViewModeSwitch';

import type { Meta, StoryFn, StoryObj } from '@storybook/react';

export default {
title: 'Shared/Controls/ViewModeSwitch',
component: ViewModeSwitch,
argTypes: {
currentViewMode: {
control: { type: 'radio', options: [ViewMode.CARD, ViewMode.LIST] },
description: 'Current view mode',
table: { defaultValue: { summary: ViewMode.CARD.toString() } },
},
setCurrentViewMode: {
control: false,
description: 'Callback to set current view mode',
table: { type: { summary: '(newViewMode: ViewMode) => void' } },
},
},
args: { currentViewMode: ViewMode.CARD },
parameters: { controls: { expanded: true, exclude: ['userContext'] } },
} satisfies Meta<typeof ViewModeSwitch>;

type Story = StoryObj<typeof ViewModeSwitch>;

export const Default: Story = {
render: (({ currentViewMode }) => {
const [viewMode, setViewMode] = useState(currentViewMode);
return (
<Flex
alignItems="center"
justifyContent="start"
width="100%"
minHeight="100px"
>
<ViewModeSwitch
currentViewMode={viewMode}
setCurrentViewMode={setViewMode}
/>
</Flex>
);
}) satisfies StoryFn<typeof ViewModeSwitch>,
};
28 changes: 17 additions & 11 deletions web/packages/shared/components/Controls/ViewModeSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,10 @@ export const ViewModeSwitch = ({
<ViewModeSwitchButton
className={currentViewMode === ViewMode.CARD ? 'selected' : ''}
onClick={() => setCurrentViewMode(ViewMode.CARD)}
css={`
border-right: 1px solid
${props => props.theme.colors.spotBackground[2]};
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
`}
role="radio"
aria-label="Card View"
aria-checked={currentViewMode === ViewMode.CARD}
first
>
<SquaresFour size="small" color="text.main" />
</ViewModeSwitchButton>
Expand All @@ -58,13 +53,10 @@ export const ViewModeSwitch = ({
<ViewModeSwitchButton
className={currentViewMode === ViewMode.LIST ? 'selected' : ''}
onClick={() => setCurrentViewMode(ViewMode.LIST)}
css={`
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
`}
role="radio"
aria-label="List View"
aria-checked={currentViewMode === ViewMode.LIST}
last
>
<Rows size="small" color="text.main" />
</ViewModeSwitchButton>
Expand All @@ -90,7 +82,7 @@ const ViewModeSwitchContainer = styled.div`
}
`;

const ViewModeSwitchButton = styled.button`
const ViewModeSwitchButton = styled.button<{ first?: boolean; last?: boolean }>`
height: 100%;
width: 100%;
overflow: hidden;
Expand All @@ -103,6 +95,20 @@ const ViewModeSwitchButton = styled.button`
outline: none;
transition: outline-width 150ms ease;
${p =>
p.first &&
`
border-top-left-radius: ${p.theme.radii[2]}px;
border-bottom-left-radius: ${p.theme.radii[2]}px;
border-right: ${p.theme.borders[1]} ${p.theme.colors.spotBackground[2]};
`}
${p =>
p.last &&
`
border-top-right-radius: ${p.theme.radii[2]}px;
border-bottom-right-radius: ${p.theme.radii[2]}px;
`}
&:focus-visible {
outline: ${p => p.theme.borders[1]}
${p => p.theme.colors.text.slightlyMuted};
Expand Down

0 comments on commit 06c37c7

Please sign in to comment.