From f3c6399a34bc9ec14c60a1b4fc1e371bf1fb9b98 Mon Sep 17 00:00:00 2001 From: Maxim Dietz Date: Tue, 19 Nov 2024 15:55:31 -0500 Subject: [PATCH] chore: Default config for `storybook-controls`, add `storybook-actions` * Set some defaults for `storybook-controls` to prevent `userContext` Decorator showing as a Control for all Stories * Add `@storybook/addon-actions` for displaying callbacks in UI, add to `shared/controls/*` stories * Tidy some incorrect types/controls for `shared/controls/*` stories --- package.json | 2 + pnpm-lock.yaml | 50 +++++++++++++---- web/.storybook/main.ts | 6 ++- web/.storybook/preview.tsx | 2 + web/packages/design/src/Alert/Alert.story.tsx | 9 ++-- .../Controls/MultiselectMenu.story.tsx | 18 +++++-- .../components/Controls/SortMenu.story.tsx | 54 ++++++++++++------- .../Controls/ViewModeSwitch.story.tsx | 41 +++++++++----- 8 files changed, 129 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 722d0a9d55a0d..7abf816bf006b 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,11 @@ }, "devDependencies": { "@gravitational/build": "workspace:*", + "@storybook/addon-actions": "^8.3.4", "@storybook/addon-controls": "^8.3.4", "@storybook/addon-toolbars": "^8.3.4", "@storybook/components": "^8.3.4", + "@storybook/preview-api": "^8.3.4", "@storybook/react": "^8.3.4", "@storybook/react-vite": "^8.3.4", "@storybook/test-runner": "^0.19.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6332382eade7..a4df1003d13a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: '@gravitational/build': specifier: workspace:* version: link:web/packages/build + '@storybook/addon-actions': + specifier: ^8.3.4 + version: 8.3.4(storybook@8.3.4) '@storybook/addon-controls': specifier: ^8.3.4 version: 8.3.4(storybook@8.3.4) @@ -107,6 +110,9 @@ importers: '@storybook/components': specifier: ^8.3.4 version: 8.3.4(storybook@8.3.4) + '@storybook/preview-api': + specifier: ^8.3.4 + version: 8.3.4(storybook@8.3.4) '@storybook/react': specifier: ^8.3.4 version: 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.6.2) @@ -2177,6 +2183,11 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@storybook/addon-actions@8.3.4': + resolution: {integrity: sha512-1y0yD3upKcyzNwwA6loAGW2cRDqExwl4oAT7GJQA4tmabI+fNwmANSgU/ezLvvSUf4Qo0eJHg2Zcn8y+Apq2eA==} + peerDependencies: + storybook: ^8.3.4 + '@storybook/addon-controls@8.3.4': resolution: {integrity: sha512-qQcaK6dczsb6wXkzGZKOjUYNA7FfKBewRv6NvoVKYY6LfhllGOkmUAtYpdtQG8adsZWTSoZaAOJS2vP2uM67lw==} peerDependencies: @@ -2237,11 +2248,6 @@ packages: peerDependencies: storybook: ^8.3.4 - '@storybook/preview-api@8.2.9': - resolution: {integrity: sha512-D8/t+a78OJqQAcT/ABa1C4YM/OaLGQ9IvCsp3Q9ruUqDCwuZBj8bG3D4477dlY4owX2ycC0rWYu3VvuK0EmJjA==} - peerDependencies: - storybook: ^8.2.9 - '@storybook/preview-api@8.3.4': resolution: {integrity: sha512-/YKQ3QDVSHmtFXXCShf5w0XMlg8wkfTpdYxdGv1CKFV8DU24f3N7KWulAgeWWCWQwBzZClDa9kzxmroKlQqx3A==} peerDependencies: @@ -2671,6 +2677,9 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/uuid@9.0.8': + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@types/verror@1.10.5': resolution: {integrity: sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==} @@ -5671,6 +5680,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + polished@4.3.1: + resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} + engines: {node: '>=10'} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -6672,6 +6685,10 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + v8-to-istanbul@9.1.3: resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} engines: {node: '>=10.12.0'} @@ -9006,6 +9023,15 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.0 + '@storybook/addon-actions@8.3.4(storybook@8.3.4)': + dependencies: + '@storybook/global': 5.0.0 + '@types/uuid': 9.0.8 + dequal: 2.0.3 + polished: 4.3.1 + storybook: 8.3.4 + uuid: 9.0.1 + '@storybook/addon-controls@8.3.4(storybook@8.3.4)': dependencies: '@storybook/global': 5.0.0 @@ -9086,10 +9112,6 @@ snapshots: dependencies: storybook: 8.3.4 - '@storybook/preview-api@8.2.9(storybook@8.3.4)': - dependencies: - storybook: 8.3.4 - '@storybook/preview-api@8.3.4(storybook@8.3.4)': dependencies: storybook: 8.3.4 @@ -9162,7 +9184,7 @@ snapshots: '@storybook/core-common': 8.2.9(storybook@8.3.4) '@storybook/csf': 0.1.11 '@storybook/csf-tools': 8.2.9(storybook@8.3.4) - '@storybook/preview-api': 8.2.9(storybook@8.3.4) + '@storybook/preview-api': 8.3.4(storybook@8.3.4) '@swc/core': 1.7.26 '@swc/jest': 0.2.36(@swc/core@1.7.26) expect-playwright: 0.8.0 @@ -9600,6 +9622,8 @@ snapshots: '@types/triple-beam@1.3.5': {} + '@types/uuid@9.0.8': {} + '@types/verror@1.10.5': optional: true @@ -13417,6 +13441,10 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + polished@4.3.1: + dependencies: + '@babel/runtime': 7.25.6 + possible-typed-array-names@1.0.0: {} postcss-value-parser@4.2.0: {} @@ -14565,6 +14593,8 @@ snapshots: uuid@8.3.2: {} + uuid@9.0.1: {} + v8-to-istanbul@9.1.3: dependencies: '@jridgewell/trace-mapping': 0.3.25 diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts index 8dce9b37419d2..9eee7f81ec43f 100644 --- a/web/.storybook/main.ts +++ b/web/.storybook/main.ts @@ -43,7 +43,11 @@ const config: StorybookConfig = { options: { builder: { viteConfigPath: 'web/.storybook/vite.config.mts' } }, }, staticDirs: ['public'], - addons: ['@storybook/addon-toolbars', '@storybook/addon-controls'], + addons: [ + '@storybook/addon-toolbars', + '@storybook/addon-controls', + '@storybook/addon-actions', + ], }; export default config; diff --git a/web/.storybook/preview.tsx b/web/.storybook/preview.tsx index 30b5aac4f353d..0103207d3a13b 100644 --- a/web/.storybook/preview.tsx +++ b/web/.storybook/preview.tsx @@ -93,7 +93,9 @@ const preview: Preview = { order: ['Teleport', 'TeleportE', 'Teleterm', 'Design', 'Shared'], }, }, + controls: { expanded: true, disableSaveFromUI: true }, }, + argTypes: { userContext: { table: { disable: true } } }, loaders: [mswLoader], decorators: [ (Story, meta) => ( diff --git a/web/packages/design/src/Alert/Alert.story.tsx b/web/packages/design/src/Alert/Alert.story.tsx index 3751836206ab9..b5fbc8cc80e03 100644 --- a/web/packages/design/src/Alert/Alert.story.tsx +++ b/web/packages/design/src/Alert/Alert.story.tsx @@ -17,6 +17,7 @@ */ import React from 'react'; +import { action } from '@storybook/addon-actions'; import { Restore } from 'design/Icon'; @@ -119,14 +120,10 @@ const commonProps: AlertProps = { dismissible: true, primaryAction: { content: 'Primary Action', - onClick: () => { - alert('Primary button clicked'); - }, + onClick: action('primaryAction.onClick'), }, secondaryAction: { content: 'Secondary Action', - onClick: () => { - alert('Secondary button clicked'); - }, + onClick: action('secondaryAction.onClick'), }, }; diff --git a/web/packages/shared/components/Controls/MultiselectMenu.story.tsx b/web/packages/shared/components/Controls/MultiselectMenu.story.tsx index 3016d892c64a5..14a13866b9d33 100644 --- a/web/packages/shared/components/Controls/MultiselectMenu.story.tsx +++ b/web/packages/shared/components/Controls/MultiselectMenu.story.tsx @@ -16,18 +16,20 @@ * along with this program. If not, see . */ -import React, { useState } from 'react'; +import { action } from '@storybook/addon-actions'; +import { useArgs } from '@storybook/preview-api'; import { Flex } from 'design'; import { MultiselectMenu } from './MultiselectMenu'; +import type { ReactNode } from 'react'; import type { Meta, StoryFn, StoryObj } from '@storybook/react'; type OptionValue = `option-${number}`; const options: { value: OptionValue; - label: string | React.ReactNode; + label: string | ReactNode; disabled?: boolean; disabledTooltip?: string; }[] = [ @@ -99,16 +101,22 @@ export default { args: { label: 'Select Options', tooltip: 'Choose multiple options', + selected: [], buffered: false, showIndicator: true, showSelectControls: true, + onChange: action('onChange'), }, - parameters: { controls: { expanded: true, exclude: ['userContext'] } }, render: (args => { - const [selected, setSelected] = useState([]); + const [{ selected }, updateArgs] = + useArgs>['args']>(); + const onChange = (value: OptionValue[]) => { + updateArgs({ selected: value }); + args.onChange?.(value); + }; return ( - + ); }) satisfies StoryFn>, diff --git a/web/packages/shared/components/Controls/SortMenu.story.tsx b/web/packages/shared/components/Controls/SortMenu.story.tsx index 56158b8a8d228..32b96383f87ab 100644 --- a/web/packages/shared/components/Controls/SortMenu.story.tsx +++ b/web/packages/shared/components/Controls/SortMenu.story.tsx @@ -16,24 +16,39 @@ * along with this program. If not, see . */ -import React, { useState } from 'react'; +import { action } from '@storybook/addon-actions'; +import { useArgs } from '@storybook/preview-api'; import { Flex } from 'design'; import { SortMenu } from './SortMenu'; import type { Meta, StoryFn, StoryObj } from '@storybook/react'; +const STUB_FIELDS = ['name', 'created', 'updated', 'status'] as const; + export default { title: 'Shared/Controls/SortMenu', component: SortMenu, argTypes: { current: { - control: false, + control: { type: 'select' }, + options: STUB_FIELDS.reduce( + (acc, v) => [...acc, `${v} (Asc)`, `${v} (Desc)`], + [] + ), + mapping: STUB_FIELDS.reduce( + (acc, v) => ({ + ...acc, + [`${v} (Asc)`]: { fieldName: v, dir: 'ASC' }, + [`${v} (Desc)`]: { fieldName: v, dir: 'DESC' }, + }), + {} + ), description: 'Current sort', table: { type: { summary: - "Array<{ fieldName: Exclude; dir: 'ASC' | 'DESC'>", + "Array<{ fieldName: Exclude; dir: 'ASC' | 'DESC' }>", }, }, }, @@ -43,7 +58,7 @@ export default { table: { type: { summary: - '{ value: Exclude; label: string }[]', + 'Array<{ value: Exclude; label: string }>', }, }, }, @@ -59,25 +74,28 @@ export default { }, }, args: { - current: { fieldName: 'name', dir: 'ASC' }, - fields: [ - { value: 'name', label: 'Name' }, - { value: 'created', label: 'Created' }, - { value: 'updated', label: 'Updated' }, - ], + current: { fieldName: STUB_FIELDS[0], dir: 'ASC' }, + fields: STUB_FIELDS.map(v => ({ + value: v, + label: `${v.charAt(0).toUpperCase()}${v.slice(1)}`, + })), + onChange: action('onChange'), }, - parameters: { controls: { expanded: true, exclude: ['userContext'] } }, -} satisfies Meta>; - -const Default: StoryObj = { - render: (({ current, fields }) => { - const [sort, setSort] = useState(current); + render: (args => { + const [{ current }, updateArgs] = + useArgs>['args']>(); + const onChange = (value: typeof current) => { + updateArgs({ current: value }); + args.onChange?.(value); + }; return ( - + ); }) satisfies StoryFn, -}; +} satisfies Meta>; + +const Default: StoryObj = { args: {} }; export { Default as SortMenu }; diff --git a/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx b/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx index adc77e1975715..7621fd92afa52 100644 --- a/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx +++ b/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx @@ -16,11 +16,13 @@ * along with this program. If not, see . */ -import React, { useState } from 'react'; +import { action } from '@storybook/addon-actions'; import { Flex } from 'design'; import { ViewMode } from 'gen-proto-ts/teleport/userpreferences/v1/unified_resource_preferences_pb'; +import { useArgs } from '@storybook/preview-api'; + import { ViewModeSwitch } from './ViewModeSwitch'; import type { Meta, StoryFn, StoryObj } from '@storybook/react'; @@ -30,9 +32,16 @@ export default { component: ViewModeSwitch, argTypes: { currentViewMode: { - control: { type: 'radio', options: [ViewMode.CARD, ViewMode.LIST] }, + control: { + type: 'radio', + labels: { [ViewMode.CARD]: 'Card View', [ViewMode.LIST]: 'List View' }, + }, + options: [ViewMode.CARD, ViewMode.LIST], description: 'Current view mode', - table: { defaultValue: { summary: ViewMode.CARD.toString() } }, + table: { + defaultValue: { summary: ViewMode.CARD.toString() }, + type: { summary: 'ViewMode' }, + }, }, setCurrentViewMode: { control: false, @@ -40,22 +49,28 @@ export default { table: { type: { summary: '(newViewMode: ViewMode) => void' } }, }, }, - args: { currentViewMode: ViewMode.CARD }, - parameters: { controls: { expanded: true, exclude: ['userContext'] } }, -} satisfies Meta; - -const Default: StoryObj = { - render: (({ currentViewMode }) => { - const [viewMode, setViewMode] = useState(currentViewMode); + args: { + currentViewMode: ViewMode.CARD, + setCurrentViewMode: action('setCurrentViewMode'), + }, + render: (args => { + const [{ currentViewMode }, updateArgs] = + useArgs['args']>(); + const setCurrentViewMode = (value: ViewMode) => { + updateArgs({ currentViewMode: value }); + args?.setCurrentViewMode(value); + }; return ( ); }) satisfies StoryFn, -}; +} satisfies Meta; + +const Default: StoryObj = { args: {} }; export { Default as ViewModeSwitch };