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 18, 2024
1 parent 9c46145 commit 576175b
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 14 deletions.
137 changes: 137 additions & 0 deletions web/packages/shared/components/Controls/MultiselectMenu.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* 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';

type OptionValue = `option-${number}`;

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

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

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'] } },
render: (args => {
const [selected, setSelected] = useState<string[]>([]);
return (
<Flex alignItems="center" minHeight="100px">
<MultiselectMenu {...args} selected={selected} onChange={setSelected} />
</Flex>
);
}) satisfies StoryFn<typeof MultiselectMenu<OptionValue>>,
} satisfies Meta<typeof MultiselectMenu<OptionValue>>;

type Story = StoryObj<typeof MultiselectMenu<OptionValue>>;

const Default: Story = { args: { options } };

const WithCustomLabels: Story = { args: { options: optionsWithCustomLabels } };

const WithDisabledOption: Story = {
args: {
options: [
...options,
{
value: 'option-5',
label: 'Option 5',
disabled: true,
disabledTooltip: 'Lorum ipsum dolor sit amet',
},
],
},
};

export { Default, WithCustomLabels, WithDisabledOption };
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
83 changes: 83 additions & 0 deletions web/packages/shared/components/Controls/SortMenu.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* 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>>;

const Default: StoryObj<typeof SortMenu> = {
render: (({ current, fields }) => {
const [sort, setSort] = useState(current);
return (
<Flex alignItems="center" minHeight="100px">
<SortMenu current={sort} fields={fields} onChange={setSort} />
</Flex>
);
}) satisfies StoryFn<typeof SortMenu>,
};

export { Default as SortMenu };
61 changes: 61 additions & 0 deletions web/packages/shared/components/Controls/ViewModeSwitch.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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>;

const Default: StoryObj<typeof ViewModeSwitch> = {
render: (({ currentViewMode }) => {
const [viewMode, setViewMode] = useState(currentViewMode);
return (
<Flex alignItems="center" minHeight="100px">
<ViewModeSwitch
currentViewMode={viewMode}
setCurrentViewMode={setViewMode}
/>
</Flex>
);
}) satisfies StoryFn<typeof ViewModeSwitch>,
};

export { Default as ViewModeSwitch };
29 changes: 17 additions & 12 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 @@ -75,7 +67,6 @@ export const ViewModeSwitch = ({

const ViewModeSwitchContainer = styled.div`
height: 22px;
width: 48px;
border: ${p => p.theme.borders[1]} ${p => p.theme.colors.spotBackground[2]};
border-radius: ${p => p.theme.radii[2]}px;
display: flex;
Expand All @@ -90,7 +81,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 +94,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 576175b

Please sign in to comment.