diff --git a/CLAUDE.md b/CLAUDE.md index 1c1bc90b0d08..7dc9583de891 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,6 +3,7 @@ - "Was working before" = base branch, not previous commit. Base branch is almost always `nojima/HOTPOT-next-670-clean-2` (not `master`). Always run `gh pr view --json baseRefName` to confirm before any `git diff` or `git log` comparison. - Never use `npm`. Always `yarn`. - Never silently drop features/behavior — ask first, present options. +- In tests/stories, use `testuser` / `testuser-mac` as placeholder usernames — never real usernames like `chrisnojima`. - No DOM elements (`
`, ``, etc.) in plain `.tsx` files — use `Kb.*`. Guard desktop-only DOM with `Styles.isMobile`. - Temp files go in `/tmp/`. - Remove unused code when editing: styles, imports, vars, params, dead helpers. diff --git a/shared/.storybook/main.ts b/shared/.storybook/main.ts new file mode 100644 index 000000000000..6a6924057c25 --- /dev/null +++ b/shared/.storybook/main.ts @@ -0,0 +1,119 @@ +import path from 'path' +import webpack from 'webpack' +import {createRequire} from 'module' +import {fileURLToPath} from 'url' +import type {StorybookConfig} from '@storybook/react-webpack5' + +const require = createRequire(import.meta.url) +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.resolve(__dirname, '..') +const nullModulePath = path.resolve(rootDir, 'null-module.js') +const ignoredModules = require('../ignored-modules') as Array + +const makeAliases = (): Record => { + // Sort longest-first: webpack checks in insertion order; longer prefixes must come first + // so subpath entries (e.g. 'foo/bar') are matched before their parent package ('foo'). + const sortedModules = [...ignoredModules].sort((a, b) => b.length - a.length) + const alias = sortedModules.reduce>((acc, name) => { + acc[name] = nullModulePath + return acc + }, {}) + return { + ...alias, + 'react-native$': 'react-native-web', + 'react-native-reanimated': false, + 'react-native/Libraries/Image/resolveAssetSource': nullModulePath, + 'react-native-safe-area-context': path.resolve(rootDir, 'desktop/stubs/react-native-safe-area-context.js'), + '@react-native-picker/picker': path.resolve(rootDir, 'desktop/stubs/react-native-picker.js'), + // electron stub MUST come before '@' (insertion order matters for webpack alias matching) + '@/util/electron$': path.resolve(__dirname, 'mocks/electron.ts'), + '@': rootDir, + } +} + +const config: StorybookConfig = { + stories: ['../**/*.stories.tsx'], + addons: [], + framework: { + name: '@storybook/react-webpack5', + options: { + builder: { + lazyCompilation: false, + }, + }, + }, + staticDirs: [ + {from: '../fonts/electron', to: '/fonts/electron'}, + {from: '../images', to: '/images'}, + ], + typescript: { + check: false, + reactDocgen: false, + }, + webpackFinal: webpackConfig => { + // Aliases + extensions (.tsx/.ts must be listed so webpack resolves index files and bare paths) + webpackConfig.resolve = webpackConfig.resolve ?? {} + webpackConfig.resolve.alias = { + ...(webpackConfig.resolve.alias ?? {}), + ...makeAliases(), + } + webpackConfig.resolve.extensions = ['.desktop.tsx', '.desktop.ts', '.tsx', '.ts', '.js', '.jsx', '.json'] + + // Storybook 10 does not include a JS/TS transpiler by default — add babel-loader + // so that TypeScript and JSX in story files and preview config are compiled. + // We provide explicit presets rather than relying on babel.config.js caller detection + // (the project config only enables @babel/preset-react for test env, not webpack). + webpackConfig.module = webpackConfig.module ?? {rules: []} + webpackConfig.module.rules = webpackConfig.module.rules ?? [] + webpackConfig.module.rules.push({ + test: /\.(tsx?|jsx?)$/, + exclude: /node_modules/, + use: [ + { + loader: require.resolve('babel-loader'), + options: { + presets: [ + ['@babel/preset-env', {targets: {browsers: 'last 2 Chrome versions'}}], + ['@babel/preset-react', {runtime: 'automatic'}], + '@babel/preset-typescript', + ], + // No module-resolver here — webpack handles '@' alias directly so + // the webpack alias overrides (e.g. @/util/electron → mock) apply correctly. + }, + }, + ], + }) + + // Fonts as assets (mirrors desktop/webpack.config.mts) + webpackConfig.module.rules.push({ + test: /\.ttf$/, + type: 'asset/resource', + }) + + // Null-load native-only files (must run before other loaders) + webpackConfig.module.rules.unshift({ + test: /\.(native|ios|android)\.(ts|js)x?$/, + use: ['null-loader'], + }) + + // Platform globals — same as desktop/webpack.config.mts makeDefineValues + webpackConfig.plugins = webpackConfig.plugins ?? [] + webpackConfig.plugins.push( + new webpack.DefinePlugin({ + isMobile: JSON.stringify(false), + isElectron: JSON.stringify(true), + isAndroid: JSON.stringify(false), + isIOS: JSON.stringify(false), + __DEV__: JSON.stringify(true), + __HOT__: JSON.stringify(false), + __PROFILE__: JSON.stringify(false), + __VERSION__: JSON.stringify('storybook'), + __FILE_SUFFIX__: JSON.stringify(''), + }) + ) + + return webpackConfig + }, +} + +export default config diff --git a/shared/.storybook/mocks/electron.ts b/shared/.storybook/mocks/electron.ts new file mode 100644 index 000000000000..58b912b3a9d4 --- /dev/null +++ b/shared/.storybook/mocks/electron.ts @@ -0,0 +1,43 @@ +import type {KB2} from '../../util/electron' + +const stub: KB2 = { + constants: { + assetRoot: '/', + configOverload: {}, + dokanPath: '', + downloadFolder: '', + env: { + APPDATA: '', + HOME: '/tmp', + KEYBASE_AUTOSTART: '', + KEYBASE_CRASH_REPORT: '', + KEYBASE_DEVEL_USE_XDG: '', + KEYBASE_RESTORE_UI: '', + KEYBASE_RUN_MODE: 'prod', + KEYBASE_START_UI: '', + KEYBASE_XDG_OVERRIDE: '', + LANG: 'en_US.UTF-8', + LC_ALL: '', + LC_TIME: '', + LOCALAPPDATA: '', + XDG_CACHE_HOME: '', + XDG_CONFIG_HOME: '', + XDG_DATA_HOME: '', + XDG_DOWNLOAD_DIR: '', + XDG_RUNTIME_DIR: '', + }, + helloDetails: {argv: [], clientType: 2 as const, desc: 'Main Renderer', pid: 0, version: ''}, + isRenderer: true, + pathSep: '/' as const, + platform: 'darwin' as const, + startDarkMode: false, + windowsBinPath: '', + }, + functions: { + mainWindowDispatch: () => {}, + }, +} + +export default stub +export const injectPreload = () => {} +export const waitOnKB2Loaded = (cb: () => void) => cb() diff --git a/shared/.storybook/preview-head.html b/shared/.storybook/preview-head.html new file mode 100644 index 000000000000..8f9f655741be --- /dev/null +++ b/shared/.storybook/preview-head.html @@ -0,0 +1,46 @@ + diff --git a/shared/.storybook/preview.ts b/shared/.storybook/preview.ts new file mode 100644 index 000000000000..3f0a75e6f160 --- /dev/null +++ b/shared/.storybook/preview.ts @@ -0,0 +1,76 @@ +import React from 'react' +import type {Preview} from '@storybook/react' +import type {KB2} from '../util/electron' +import {initDesktopStyles} from '@/styles' +import {useDarkModeState} from '@/stores/darkmode' + +// Inject a minimal KB2 stub so util/electron.tsx's getStashed() doesn't throw. +// The real app sets this from the Electron preload script; storybook sets it here. +const kb2Stub: KB2 = { + constants: { + assetRoot: '/', + configOverload: {}, + dokanPath: '', + downloadFolder: '', + env: { + APPDATA: '', + HOME: '/tmp', + KEYBASE_AUTOSTART: '', + KEYBASE_CRASH_REPORT: '', + KEYBASE_DEVEL_USE_XDG: '', + KEYBASE_RESTORE_UI: '', + KEYBASE_RUN_MODE: 'prod', + KEYBASE_START_UI: '', + KEYBASE_XDG_OVERRIDE: '', + LANG: 'en_US.UTF-8', + LC_ALL: '', + LC_TIME: '', + LOCALAPPDATA: '', + XDG_CACHE_HOME: '', + XDG_CONFIG_HOME: '', + XDG_DATA_HOME: '', + XDG_DOWNLOAD_DIR: '', + XDG_RUNTIME_DIR: '', + }, + helloDetails: {argv: [], clientType: 2 as const, desc: 'Main Renderer', pid: 0, version: ''}, + isRenderer: true, + pathSep: '/' as const, + platform: 'darwin' as const, + startDarkMode: false, + windowsBinPath: '', + }, + functions: { + mainWindowDispatch: () => {}, + }, +} +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access +;(globalThis as any)._fromPreload = kb2Stub + +initDesktopStyles() + +export const globalTypes = { + darkMode: { + defaultValue: false, + }, +} + +const preview: Preview = { + decorators: [ + (Story, context) => { + const dark = !!context.globals['darkMode'] + const target = dark ? 'alwaysDark' : 'alwaysLight' + // false = don't write to config (no RPC available in storybook) + if (useDarkModeState.getState().darkModePreference !== target) { + useDarkModeState.getState().dispatch.setDarkModePreference(target, false) + } + // Required for light-dark() CSS vars to resolve correctly + document.documentElement.style.colorScheme = dark ? 'dark' : 'light' + return React.createElement('div', {style: {background: 'var(--color-white)', padding: 16}}, React.createElement(Story)) + }, + ], + parameters: { + layout: 'fullscreen', + }, +} + +export default preview diff --git a/shared/chat/avatars.stories.tsx b/shared/chat/avatars.stories.tsx new file mode 100644 index 000000000000..acf8592cbf1a --- /dev/null +++ b/shared/chat/avatars.stories.tsx @@ -0,0 +1,57 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {Avatars, TeamAvatar} from './avatars' + +const meta: Meta = { + component: Avatars, + title: 'Chat/Avatars', +} +export default meta +type Story = StoryObj + +export const SingleUser: Story = { + args: {participantOne: 'alice'}, +} + +export const TwoUsers: Story = { + args: {participantOne: 'alice', participantTwo: 'bob'}, +} + +export const Muted: Story = { + args: {participantOne: 'alice', participantTwo: 'bob', isMuted: true}, +} + +export const Selected: Story = { + args: {participantOne: 'alice', participantTwo: 'bob', isSelected: true}, +} + +export const Locked: Story = { + args: {participantOne: 'alice', participantTwo: 'bob', isLocked: true}, +} + +export const SmallSize: Story = { + args: {participantOne: 'alice', singleSize: 32}, +} + +export const LargeSize: Story = { + args: {participantOne: 'alice', singleSize: 96}, +} + +type TeamStory = StoryObj + +export const Team: TeamStory = { + render: () => ( + + ), +} + +export const TeamMuted: TeamStory = { + render: () => ( + + ), +} + +export const TeamSelected: TeamStory = { + render: () => ( + + ), +} diff --git a/shared/chat/inbox/row/big-teams-label.stories.tsx b/shared/chat/inbox/row/big-teams-label.stories.tsx new file mode 100644 index 000000000000..8eb2232a4ecb --- /dev/null +++ b/shared/chat/inbox/row/big-teams-label.stories.tsx @@ -0,0 +1,11 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {BigTeamsLabel} from './big-teams-label' + +const meta: Meta = { + component: BigTeamsLabel, + title: 'Chat/BigTeamsLabel', +} +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/shared/chat/inbox/row/teams-divider.stories.tsx b/shared/chat/inbox/row/teams-divider.stories.tsx new file mode 100644 index 000000000000..ba55c3b97f6a --- /dev/null +++ b/shared/chat/inbox/row/teams-divider.stories.tsx @@ -0,0 +1,31 @@ +import type {Meta, StoryObj} from '@storybook/react' +import TeamsDivider from './teams-divider' + +const meta: Meta = { + component: TeamsDivider, + title: 'Chat/TeamsDivider', + args: { + toggle: () => {}, + smallTeamsExpanded: false, + showButton: false, + hiddenCount: 0, + }, +} +export default meta +type Story = StoryObj + +export const NoBigTeams: Story = { + args: {showButton: false, hiddenCount: 0, smallTeamsExpanded: false}, +} + +export const WithButton: Story = { + args: {showButton: true, hiddenCount: 12, smallTeamsExpanded: false}, +} + +export const WithButtonAndBadge: Story = { + args: {showButton: true, hiddenCount: 5, smallTeamsExpanded: false, badgeCount: 3}, +} + +export const Expanded: Story = { + args: {showButton: true, hiddenCount: 8, smallTeamsExpanded: true}, +} diff --git a/shared/common-adapters/badge.stories.tsx b/shared/common-adapters/badge.stories.tsx new file mode 100644 index 000000000000..a2c558dcd756 --- /dev/null +++ b/shared/common-adapters/badge.stories.tsx @@ -0,0 +1,29 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Badge from './badge' + +const meta: Meta = { + component: Badge, + title: 'CommonAdapters/Badge', +} +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {badgeNumber: 3}, +} + +export const LargeNumber: Story = { + args: {badgeNumber: 99}, +} + +export const VeryLargeNumber: Story = { + args: {badgeNumber: 1234}, +} + +export const WithBorder: Story = { + args: {badgeNumber: 5, border: true}, +} + +export const Large: Story = { + args: {badgeNumber: 2, height: 24, fontSize: 14}, +} diff --git a/shared/common-adapters/banner.stories.tsx b/shared/common-adapters/banner.stories.tsx new file mode 100644 index 000000000000..7daca28e41de --- /dev/null +++ b/shared/common-adapters/banner.stories.tsx @@ -0,0 +1,62 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {Banner, BannerParagraph} from './banner' + +const meta: Meta = { + component: Banner, + title: 'CommonAdapters/Banner', + args: {color: 'blue', children: 'This is an informational banner message.'}, +} +export default meta +type Story = StoryObj + +export const Blue: Story = { + args: {color: 'blue', children: 'Your device has been revoked. Please provision again.'}, +} + +export const Red: Story = { + args: {color: 'red', children: 'Something went wrong. Please try again.'}, +} + +export const Yellow: Story = { + args: {color: 'yellow', children: 'You are in read-only mode due to a connectivity issue.'}, +} + +export const Green: Story = { + args: {color: 'green', children: 'Your files are fully synced.'}, +} + +export const Grey: Story = { + args: {color: 'grey', children: 'You have no unread messages.'}, +} + +export const WithCloseButton: Story = { + args: { + color: 'blue', + children: 'You have a new message.', + onClose: () => {}, + }, +} + +export const Small: Story = { + args: { + color: 'yellow', + children: 'Low disk space.', + small: true, + }, +} + +export const WithParagraph: Story = { + args: { + color: 'red', + children: ( + {}, text: 'alice'}, + "'s proofs have changed since you last followed them.", + ]} + /> + ), + }, +} diff --git a/shared/common-adapters/checkbox.stories.tsx b/shared/common-adapters/checkbox.stories.tsx new file mode 100644 index 000000000000..3a62773214a3 --- /dev/null +++ b/shared/common-adapters/checkbox.stories.tsx @@ -0,0 +1,41 @@ +import {useState} from 'react' +import type {Meta, StoryObj} from '@storybook/react' +import Checkbox from './checkbox' + +const meta: Meta = { + component: Checkbox, + title: 'CommonAdapters/Checkbox', + args: {label: 'Enable notifications', checked: false, onCheck: () => {}}, +} +export default meta +type Story = StoryObj + +export const Unchecked: Story = { + args: {label: 'Enable notifications', checked: false}, +} + +export const Checked: Story = { + args: {label: 'Enable notifications', checked: true}, +} + +export const Disabled: Story = { + args: {label: 'Disabled option', checked: false, disabled: true}, +} + +export const DisabledChecked: Story = { + args: {label: 'Disabled and checked', checked: true, disabled: true}, +} + +export const WithSubtitle: Story = { + args: { + label: 'Enable sound', + labelSubtitle: 'Play a sound for each new message', + checked: false, + }, +} + +const InteractiveCheckbox = () => { + const [checked, setChecked] = useState(false) + return +} +export const Interactive: Story = {render: () => } diff --git a/shared/common-adapters/icon.stories.tsx b/shared/common-adapters/icon.stories.tsx new file mode 100644 index 000000000000..76dc1b5133a3 --- /dev/null +++ b/shared/common-adapters/icon.stories.tsx @@ -0,0 +1,58 @@ +import type * as React from 'react' +import type {Meta, StoryObj} from '@storybook/react' +import Icon from './icon' +import {Box2} from './box' +import * as Styles from '@/styles' + +const iconShowcase: Array<{type: React.ComponentProps['type']; label: string}> = [ + {label: 'Search', type: 'iconfont-search'}, + {label: 'Close', type: 'iconfont-close'}, + {label: 'Edit', type: 'iconfont-edit'}, + {label: 'Add', type: 'iconfont-new'}, + {label: 'Lock', type: 'iconfont-lock'}, + {label: 'Star', type: 'iconfont-star'}, + {label: 'Check', type: 'iconfont-check'}, + {label: 'Arrow Up', type: 'iconfont-arrow-up'}, + {label: 'Arrow Down', type: 'iconfont-arrow-down'}, +] + +const meta: Meta = { + component: Icon, + title: 'CommonAdapters/Icon', + args: {type: 'iconfont-search'}, +} +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {type: 'iconfont-search'}, +} + +export const Colored: Story = { + args: {type: 'iconfont-star', color: Styles.globalColors.blue}, +} + +export const Large: Story = { + args: {type: 'iconfont-search', sizeType: 'Big'}, +} + +export const Small: Story = { + args: {type: 'iconfont-search', sizeType: 'Small'}, +} + +export const Clickable: Story = { + args: {type: 'iconfont-edit', onClick: () => {}, color: Styles.globalColors.blue}, +} + +export const Showcase: Story = { + render: () => ( + + {iconShowcase.map(({type, label}) => ( + + + {label} + + ))} + + ), +} diff --git a/shared/common-adapters/loading-state-view.stories.tsx b/shared/common-adapters/loading-state-view.stories.tsx new file mode 100644 index 000000000000..b93ecc382885 --- /dev/null +++ b/shared/common-adapters/loading-state-view.stories.tsx @@ -0,0 +1,34 @@ +import type {Meta, StoryObj} from '@storybook/react' +import LoadingStateView from './loading-state-view' + +const meta: Meta = { + component: LoadingStateView, + title: 'CommonAdapters/LoadingStateView', + args: {loading: true}, +} +export default meta +type Story = StoryObj + +export const Loading: Story = { + args: {loading: true}, +} + +export const LoadingWithProgress: Story = { + args: {loading: true, progress: 0.6}, +} + +export const LoadingWhite: Story = { + args: {loading: true, white: true}, + decorators: [ + Story => ( +
+ +
+ ), + ], +} + +export const NotLoading: Story = { + args: {loading: false}, + // Should render nothing +} diff --git a/shared/common-adapters/meta.stories.tsx b/shared/common-adapters/meta.stories.tsx new file mode 100644 index 000000000000..0e93da3f4314 --- /dev/null +++ b/shared/common-adapters/meta.stories.tsx @@ -0,0 +1,35 @@ +import type {Meta, StoryObj} from '@storybook/react' +import MetaTag from './meta' +import * as Styles from '@/styles' + +const meta: Meta = { + component: MetaTag, + title: 'CommonAdapters/Meta', + args: {title: 'new', backgroundColor: Styles.globalColors.blue}, +} +export default meta +type Story = StoryObj + +export const New: Story = { + args: {title: 'new', backgroundColor: Styles.globalColors.blue}, +} + +export const Public: Story = { + args: {title: 'public', backgroundColor: Styles.globalColors.green}, +} + +export const Admin: Story = { + args: {title: 'admin', backgroundColor: Styles.globalColors.blue}, +} + +export const Error: Story = { + args: {title: 'error', backgroundColor: Styles.globalColors.red}, +} + +export const Small: Story = { + args: {title: 'beta', backgroundColor: Styles.globalColors.purple, size: 'Small'}, +} + +export const NoUppercase: Story = { + args: {title: 'keybase.io', backgroundColor: Styles.globalColors.blueGrey, noUppercase: true, color: Styles.globalColors.black_50}, +} diff --git a/shared/common-adapters/progress-bar.stories.tsx b/shared/common-adapters/progress-bar.stories.tsx new file mode 100644 index 000000000000..7aacb2f0b092 --- /dev/null +++ b/shared/common-adapters/progress-bar.stories.tsx @@ -0,0 +1,30 @@ +import type {Meta, StoryObj} from '@storybook/react' +import ProgressBar from './progress-bar' + +const meta: Meta = { + component: ProgressBar, + title: 'CommonAdapters/ProgressBar', + args: {ratio: 0.5}, +} +export default meta +type Story = StoryObj + +export const Empty: Story = { + args: {ratio: 0}, +} + +export const Quarter: Story = { + args: {ratio: 0.25}, +} + +export const Half: Story = { + args: {ratio: 0.5}, +} + +export const ThreeQuarters: Story = { + args: {ratio: 0.75}, +} + +export const Full: Story = { + args: {ratio: 1}, +} diff --git a/shared/common-adapters/radio-button.stories.tsx b/shared/common-adapters/radio-button.stories.tsx new file mode 100644 index 000000000000..e8e206a330cf --- /dev/null +++ b/shared/common-adapters/radio-button.stories.tsx @@ -0,0 +1,33 @@ +import {useState} from 'react' +import type {Meta, StoryObj} from '@storybook/react' +import RadioButton from './radio-button' + +const meta: Meta = { + component: RadioButton, + title: 'CommonAdapters/RadioButton', + args: {label: 'Option A', selected: false, onSelect: () => {}}, +} +export default meta +type Story = StoryObj + +export const Unselected: Story = { + args: {label: 'Option A', selected: false}, +} + +export const Selected: Story = { + args: {label: 'Option A', selected: true}, +} + +export const Disabled: Story = { + args: {label: 'Disabled option', selected: false, disabled: true}, +} + +export const DisabledSelected: Story = { + args: {label: 'Disabled selected', selected: true, disabled: true}, +} + +const InteractiveButton = () => { + const [selected, setSelected] = useState(false) + return +} +export const Interactive: Story = {render: () => } diff --git a/shared/common-adapters/search-filter.stories.tsx b/shared/common-adapters/search-filter.stories.tsx new file mode 100644 index 000000000000..8cb900565fec --- /dev/null +++ b/shared/common-adapters/search-filter.stories.tsx @@ -0,0 +1,54 @@ +import type {Meta, StoryObj} from '@storybook/react' +import SearchFilter from './search-filter' + +const meta: Meta = { + component: SearchFilter, + title: 'CommonAdapters/SearchFilter', + args: { + placeholderText: 'Search', + size: 'full-width', + }, +} +export default meta +type Story = StoryObj + +export const FullWidth: Story = { + args: { + size: 'full-width', + placeholderText: 'Search people or teams', + }, +} + +export const Small: Story = { + args: { + size: 'small', + placeholderText: 'Search', + }, +} + +export const WithIcon: Story = { + args: { + size: 'full-width', + placeholderText: 'Search', + icon: 'iconfont-search', + }, +} + +export const WithValue: Story = { + args: { + size: 'full-width', + placeholderText: 'Search', + valueControlled: true, + value: 'alice', + }, +} + +export const Waiting: Story = { + args: { + size: 'full-width', + placeholderText: 'Search', + waiting: true, + valueControlled: true, + value: 'searching...', + }, +} diff --git a/shared/common-adapters/section-divider.stories.tsx b/shared/common-adapters/section-divider.stories.tsx new file mode 100644 index 000000000000..9bfe9c4e4e85 --- /dev/null +++ b/shared/common-adapters/section-divider.stories.tsx @@ -0,0 +1,42 @@ +import {useState} from 'react' +import type {Meta, StoryObj} from '@storybook/react' +import SectionDivider from './section-divider' + +const meta: Meta = { + component: SectionDivider, + title: 'CommonAdapters/SectionDivider', +} +export default meta +type Story = StoryObj + +export const SimpleLabel: Story = { + args: {label: 'Section Header'}, +} + +export const WithSpinner: Story = { + args: {label: 'Loading section', showSpinner: true}, +} + +const ExpandedSection = () => { + const [collapsed, setCollapsed] = useState(false) + return ( + setCollapsed(c => !c)} + /> + ) +} +export const CollapsibleExpanded: Story = {render: () => } + +const CollapsedSection = () => { + const [collapsed, setCollapsed] = useState(true) + return ( + setCollapsed(c => !c)} + /> + ) +} +export const CollapsibleCollapsed: Story = {render: () => } diff --git a/shared/common-adapters/switch-toggle.stories.tsx b/shared/common-adapters/switch-toggle.stories.tsx new file mode 100644 index 000000000000..b59f3b1d1097 --- /dev/null +++ b/shared/common-adapters/switch-toggle.stories.tsx @@ -0,0 +1,26 @@ +import type {Meta, StoryObj} from '@storybook/react' +import SwitchToggle from './switch-toggle' + +const meta: Meta = { + component: SwitchToggle, + title: 'CommonAdapters/SwitchToggle', + args: {color: 'green', on: false}, +} +export default meta +type Story = StoryObj + +export const GreenOff: Story = { + args: {color: 'green', on: false}, +} + +export const GreenOn: Story = { + args: {color: 'green', on: true}, +} + +export const BlueOn: Story = { + args: {color: 'blue', on: true}, +} + +export const RedOn: Story = { + args: {color: 'red', on: true}, +} diff --git a/shared/common-adapters/tabs.stories.tsx b/shared/common-adapters/tabs.stories.tsx new file mode 100644 index 000000000000..e8fe9772b1e2 --- /dev/null +++ b/shared/common-adapters/tabs.stories.tsx @@ -0,0 +1,42 @@ +import {useState} from 'react' +import type {Meta, StoryObj} from '@storybook/react' +import Tabs from './tabs' +import type {Tab} from './tabs' + +const simpleTabs: Array> = [ + {title: 'profile'}, + {title: 'followers'}, + {title: 'following'}, +] + +const badgedTabs: Array> = [ + {title: 'chat', badgeNumber: 3}, + {title: 'files'}, + {title: 'devices', badgeNumber: 1}, + {title: 'settings'}, +] + +const meta: Meta = { + component: Tabs, + title: 'CommonAdapters/Tabs', +} +export default meta +type Story = StoryObj + +const BasicTabs = () => { + const [selected, setSelected] = useState('profile') + return +} +export const Basic: Story = {render: () => } + +const WithBadgesTabs = () => { + const [selected, setSelected] = useState('chat') + return +} +export const WithBadges: Story = {render: () => } + +const WithProgressTabs = () => { + const [selected, setSelected] = useState('profile') + return +} +export const WithProgress: Story = {render: () => } diff --git a/shared/common-adapters/text.stories.tsx b/shared/common-adapters/text.stories.tsx new file mode 100644 index 000000000000..7e77e27ce64c --- /dev/null +++ b/shared/common-adapters/text.stories.tsx @@ -0,0 +1,74 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Text from './text' +import {Box2} from './box' +import * as Styles from '@/styles' + +const meta: Meta = { + component: Text, + title: 'CommonAdapters/Text', + args: {type: 'Body', children: 'The quick brown fox jumps over the lazy dog'}, +} +export default meta +type Story = StoryObj + +export const Body: Story = { + args: {type: 'Body', children: 'Body text — used for standard content.'}, +} + +export const BodySmall: Story = { + args: {type: 'BodySmall', children: 'BodySmall — secondary information or metadata.'}, +} + +export const BodySemibold: Story = { + args: {type: 'BodySemibold', children: 'BodySemibold — slightly emphasized content.'}, +} + +export const Header: Story = { + args: {type: 'Header', children: 'Header — section titles and dialog headings.'}, +} + +export const HeaderBig: Story = { + args: {type: 'HeaderBig', children: 'HeaderBig — page-level headings.'}, +} + +export const BodyPrimaryLink: Story = { + args: {type: 'BodyPrimaryLink', children: 'Primary link text', onClick: () => {}}, +} + +export const BodySmallError: Story = { + args: {type: 'BodySmallError', children: 'Something went wrong.'}, +} + +export const BodyTiny: Story = { + args: {type: 'BodyTiny', children: 'BodyTiny — captions and fine print.'}, +} + +export const Negative: Story = { + args: {type: 'Body', children: 'Negative (light text for dark backgrounds)', negative: true}, + decorators: [ + Story => ( + + + + ), + ], +} + +export const LineClamp: Story = { + args: { + type: 'Body', + children: 'This is a very long string that will be clamped to a single line because it exceeds the available width in most containers and we want to demonstrate the lineClamp feature.', + lineClamp: 1, + style: {maxWidth: 300}, + }, +} + +export const TypeShowcase: Story = { + render: () => ( + + {(['HeaderBig', 'Header', 'Body', 'BodySemibold', 'BodySmall', 'BodyTiny'] as const).map(type => ( + {type}: The quick brown fox + ))} + + ), +} diff --git a/shared/common-adapters/waiting-button.stories.tsx b/shared/common-adapters/waiting-button.stories.tsx new file mode 100644 index 000000000000..28e7b3ef1114 --- /dev/null +++ b/shared/common-adapters/waiting-button.stories.tsx @@ -0,0 +1,34 @@ +import type {Meta, StoryObj} from '@storybook/react' +import WaitingButton from './waiting-button' + +const meta: Meta = { + component: WaitingButton, + title: 'CommonAdapters/WaitingButton', + args: {label: 'Submit', onClick: () => {}}, +} +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {label: 'Submit', type: 'Default'}, +} + +export const Success: Story = { + args: {label: 'Save', type: 'Success'}, +} + +export const Danger: Story = { + args: {label: 'Delete', type: 'Danger'}, +} + +export const Disabled: Story = { + args: {label: 'Submit', disabled: true}, +} + +export const Small: Story = { + args: {label: 'OK', small: true}, +} + +export const FullWidth: Story = { + args: {label: 'Continue', fullWidth: true}, +} diff --git a/shared/crypto/input.stories.tsx b/shared/crypto/input.stories.tsx new file mode 100644 index 000000000000..f6c72bbe0dc1 --- /dev/null +++ b/shared/crypto/input.stories.tsx @@ -0,0 +1,67 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {CryptoBanner} from './input' +import type {CommonState} from './helpers' + +const makeState = (overrides: Partial = {}): CommonState => ({ + bytesComplete: 0, + bytesTotal: 0, + errorMessage: '', + inProgress: false, + input: '', + inputType: 'text', + output: '', + outputSenderFullname: undefined, + outputSenderUsername: undefined, + outputSigned: false, + outputStatus: undefined, + outputType: undefined, + outputValid: false, + warningMessage: '', + ...overrides, +}) + +const meta: Meta = { + component: CryptoBanner, + title: 'Crypto/CryptoBanner', + args: { + infoMessage: 'Add your cryptographic signature to a message or file.', + state: makeState(), + }, +} +export default meta +type Story = StoryObj + +export const InfoSign: Story = { + args: { + infoMessage: 'Add your cryptographic signature to a message or file.', + state: makeState(), + }, +} + +export const InfoEncrypt: Story = { + args: { + infoMessage: "Encrypt to anyone, even if they're not on Keybase yet.", + state: makeState(), + }, +} + +export const ErrorState: Story = { + args: { + state: makeState({errorMessage: 'This Saltpack format is unexpected. Did you mean to decrypt it?'}), + }, +} + +export const WarningState: Story = { + args: { + state: makeState({warningMessage: 'Note: Encrypted for an SBS user who has not joined Keybase yet.'}), + }, +} + +export const ErrorAndWarning: Story = { + args: { + state: makeState({ + errorMessage: 'Failed to sign text.', + warningMessage: 'Warning: something looks off.', + }), + }, +} diff --git a/shared/crypto/output.stories.tsx b/shared/crypto/output.stories.tsx new file mode 100644 index 000000000000..0ffbf62def36 --- /dev/null +++ b/shared/crypto/output.stories.tsx @@ -0,0 +1,46 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {OutputInfoBanner} from './output' +import * as Kb from '@/common-adapters' + +const meta: Meta = { + component: OutputInfoBanner, + title: 'Crypto/OutputInfoBanner', + args: { + outputStatus: 'success', + }, +} +export default meta +type Story = StoryObj + +export const SignSuccess: Story = { + args: { + outputStatus: 'success', + children: ( + + This is your signed message, using Saltpack. Anyone who has it can verify you signed it. + + ), + }, +} + +export const EncryptSuccess: Story = { + args: { + outputStatus: 'success', + children: ( + + This is your encrypted message, using Saltpack. + + ), + }, +} + +export const NotSuccess: Story = { + args: { + outputStatus: 'pending', + children: ( + + This should not be visible. + + ), + }, +} diff --git a/shared/crypto/recipients.stories.tsx b/shared/crypto/recipients.stories.tsx new file mode 100644 index 000000000000..fcb39567c152 --- /dev/null +++ b/shared/crypto/recipients.stories.tsx @@ -0,0 +1,36 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Recipients from './recipients' + +const meta: Meta = { + component: Recipients, + title: 'Crypto/Recipients', + args: { + inProgress: false, + onAddRecipients: () => {}, + onClearRecipients: () => {}, + recipients: [], + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = {} + +export const OneRecipient: Story = { + args: { + recipients: ['alice'], + }, +} + +export const MultipleRecipients: Story = { + args: { + recipients: ['alice', 'bob', 'charlie'], + }, +} + +export const InProgress: Story = { + args: { + recipients: ['alice', 'bob'], + inProgress: true, + }, +} diff --git a/shared/crypto/sub-nav/left-nav.stories.tsx b/shared/crypto/sub-nav/left-nav.stories.tsx new file mode 100644 index 000000000000..2df7afc9fc42 --- /dev/null +++ b/shared/crypto/sub-nav/left-nav.stories.tsx @@ -0,0 +1,30 @@ +import type {Meta, StoryObj} from '@storybook/react' +import LeftNav from './left-nav.desktop' +import * as Crypto from '@/constants/crypto' + +const meta: Meta = { + component: LeftNav, + title: 'Crypto/LeftNav', + args: { + onClick: () => {}, + selected: Crypto.encryptTab, + }, +} +export default meta +type Story = StoryObj + +export const EncryptSelected: Story = { + args: {selected: Crypto.encryptTab}, +} + +export const DecryptSelected: Story = { + args: {selected: Crypto.decryptTab}, +} + +export const SignSelected: Story = { + args: {selected: Crypto.signTab}, +} + +export const VerifySelected: Story = { + args: {selected: Crypto.verifyTab}, +} diff --git a/shared/crypto/sub-nav/nav-row.stories.tsx b/shared/crypto/sub-nav/nav-row.stories.tsx new file mode 100644 index 000000000000..f90c70fdd71d --- /dev/null +++ b/shared/crypto/sub-nav/nav-row.stories.tsx @@ -0,0 +1,73 @@ +import type {Meta, StoryObj} from '@storybook/react' +import NavRow from './nav-row' + +const meta: Meta = { + component: NavRow, + title: 'Crypto/NavRow', + args: { + tab: 'encryptTab', + onClick: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const DesktopEncrypt: Story = { + args: { + title: 'Encrypt', + icon: 'iconfont-lock', + isSelected: false, + }, +} + +export const DesktopEncryptSelected: Story = { + args: { + title: 'Encrypt', + icon: 'iconfont-lock', + isSelected: true, + }, +} + +export const DesktopDecrypt: Story = { + args: { + title: 'Decrypt', + tab: 'decryptTab', + icon: 'iconfont-unlock', + isSelected: false, + }, +} + +export const DesktopSign: Story = { + args: { + title: 'Sign', + tab: 'signTab', + icon: 'iconfont-check', + isSelected: false, + }, +} + +export const DesktopVerify: Story = { + args: { + title: 'Verify', + tab: 'verifyTab', + icon: 'iconfont-verify', + isSelected: false, + }, +} + +export const MobileEncrypt: Story = { + args: { + title: 'Encrypt', + description: "Encrypt to anyone, even if they're not on Keybase yet.", + illustration: 'icon-encrypt-64', + }, +} + +export const MobileDecrypt: Story = { + args: { + title: 'Decrypt', + tab: 'decryptTab', + description: 'Decrypt any ciphertext or .encrypted.saltpack file.', + illustration: 'icon-decrypt-64', + }, +} diff --git a/shared/devices/device-icon.stories.tsx b/shared/devices/device-icon.stories.tsx new file mode 100644 index 000000000000..1d4591265a30 --- /dev/null +++ b/shared/devices/device-icon.stories.tsx @@ -0,0 +1,45 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import DeviceIcon from './device-icon' + +const makeDevice = ( + type: T.Devices.DeviceType, + deviceNumberOfType: number = 0 +): T.Devices.Device => ({ + created: Date.now(), + currentDevice: false, + deviceID: 'test-id', + deviceNumberOfType, + lastUsed: Date.now(), + name: 'test device', + type, +}) + +const meta: Meta = { + component: DeviceIcon, + title: 'Devices/DeviceIcon', +} +export default meta +type Story = StoryObj + +export const DesktopSize32: Story = { + args: {device: makeDevice('desktop', 0), size: 32}, +} +export const DesktopSize32Current: Story = { + args: {device: makeDevice('desktop', 0), size: 32, current: true}, +} +export const DesktopSize64: Story = { + args: {device: makeDevice('desktop', 1), size: 64}, +} +export const MobileSize32: Story = { + args: {device: makeDevice('mobile', 0), size: 32}, +} +export const MobileSize64: Story = { + args: {device: makeDevice('mobile', 0), size: 64}, +} +export const PaperKeySize32: Story = { + args: {device: makeDevice('backup', 0), size: 32}, +} +export const PaperKeySize64: Story = { + args: {device: makeDevice('backup', 0), size: 64}, +} diff --git a/shared/devices/device-page.stories.tsx b/shared/devices/device-page.stories.tsx new file mode 100644 index 000000000000..9e580db97d51 --- /dev/null +++ b/shared/devices/device-page.stories.tsx @@ -0,0 +1,75 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import DevicePage from './device-page' + +const now = Date.now() +const monthAgo = now - 30 * 24 * 60 * 60 * 1000 +const weekAgo = now - 7 * 24 * 60 * 60 * 1000 +const yearAgo = now - 365 * 24 * 60 * 60 * 1000 + +const makeDevice = (overrides: Partial = {}): T.Devices.Device => ({ + created: yearAgo, + currentDevice: false, + deviceID: 'device-001', + deviceNumberOfType: 0, + lastUsed: weekAgo, + name: 'My Device', + type: 'desktop', + provisionerName: 'testuser-mac', + ...overrides, +}) + +const meta: Meta = { + component: DevicePage, + title: 'Devices/DevicePage', + args: {canRevoke: true}, +} +export default meta +type Story = StoryObj + +export const DesktopActive: Story = { + args: { + device: makeDevice({name: 'work-laptop', currentDevice: false}), + canRevoke: true, + }, +} + +export const CurrentDevice: Story = { + args: { + device: makeDevice({name: 'testuser-mac', currentDevice: true, lastUsed: now}), + canRevoke: false, + }, +} + +export const LastDevice: Story = { + args: { + device: makeDevice({name: 'only-device', currentDevice: true}), + canRevoke: false, + }, +} + +export const MobileDevice: Story = { + args: { + device: makeDevice({name: 'iPhone 15', type: 'mobile', deviceNumberOfType: 1}), + canRevoke: true, + }, +} + +export const PaperKey: Story = { + args: { + device: makeDevice({name: 'My paper key', type: 'backup', created: monthAgo}), + canRevoke: true, + }, +} + +export const RevokedDevice: Story = { + args: { + device: makeDevice({ + name: 'old-laptop', + revokedAt: monthAgo, + revokedByName: 'testuser-mac', + lastUsed: monthAgo, + }), + canRevoke: false, + }, +} diff --git a/shared/devices/row.stories.tsx b/shared/devices/row.stories.tsx new file mode 100644 index 000000000000..d5abe2f5a506 --- /dev/null +++ b/shared/devices/row.stories.tsx @@ -0,0 +1,80 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import DeviceRow, {BadgedDeviceIDsContext} from './row' + +const now = Date.now() +const weekAgo = now - 7 * 24 * 60 * 60 * 1000 + +const makeDevice = (overrides: Partial = {}): T.Devices.Device => ({ + created: weekAgo, + currentDevice: false, + deviceID: 'device-001', + deviceNumberOfType: 0, + lastUsed: weekAgo, + name: 'My Device', + type: 'desktop', + ...overrides, +}) + +const meta: Meta = { + component: DeviceRow, + title: 'Devices/DeviceRow', + args: { + canRevoke: true, + firstItem: true, + }, +} +export default meta +type Story = StoryObj + +export const DesktopCurrent: Story = { + args: { + device: makeDevice({name: 'testuser-mac', currentDevice: true}), + }, +} + +export const DesktopActive: Story = { + args: { + device: makeDevice({name: 'work-laptop', currentDevice: false}), + firstItem: false, + }, +} + +export const MobileActive: Story = { + args: { + device: makeDevice({name: 'iPhone 15', type: 'mobile'}), + firstItem: false, + }, +} + +export const PaperKey: Story = { + args: { + device: makeDevice({name: 'Paper key', type: 'backup'}), + firstItem: false, + }, +} + +export const Revoked: Story = { + args: { + device: makeDevice({ + name: 'old-laptop', + revokedAt: weekAgo, + revokedByName: 'testuser-mac', + }), + firstItem: false, + }, +} + +export const NewBadge: Story = { + decorators: [ + Story => ( + + + + ), + ], + args: { + device: makeDevice({name: 'new-phone', type: 'mobile', deviceID: 'device-new'}), + firstItem: false, + }, +} diff --git a/shared/fs/common/comma-separated-name.stories.tsx b/shared/fs/common/comma-separated-name.stories.tsx new file mode 100644 index 000000000000..9e49e12e566d --- /dev/null +++ b/shared/fs/common/comma-separated-name.stories.tsx @@ -0,0 +1,30 @@ +import type {Meta, StoryObj} from '@storybook/react' +import CommaSeparatedName from './comma-separated-name' + +const meta: Meta = { + component: CommaSeparatedName, + title: 'FS/CommaSeparatedName', + args: {type: 'Body'}, +} +export default meta +type Story = StoryObj + +export const SingleName: Story = { + args: {name: 'alice', type: 'Body'}, +} + +export const TwoNames: Story = { + args: {name: 'alice,bob', type: 'Body'}, +} + +export const MultipleNames: Story = { + args: {name: 'alice,bob,charlie,dave', type: 'BodySmall'}, +} + +export const Selectable: Story = { + args: {name: 'alice,bob', type: 'BodySemibold', selectable: true}, +} + +export const Centered: Story = { + args: {name: 'alice,bob,charlie', type: 'Header', center: true}, +} diff --git a/shared/fs/footer/upload.stories.tsx b/shared/fs/footer/upload.stories.tsx new file mode 100644 index 000000000000..7e9167d958dc --- /dev/null +++ b/shared/fs/footer/upload.stories.tsx @@ -0,0 +1,64 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Upload from './upload' + +const meta: Meta = { + component: Upload, + title: 'FS/Upload', + args: { + showing: true, + files: 0, + totalSyncingBytes: 0, + timeLeft: '', + }, +} +export default meta +type Story = StoryObj + +export const Showing: Story = { + args: { + showing: true, + files: 3, + fileName: 'photo.jpg', + totalSyncingBytes: 1024 * 1024, + timeLeft: '2 minutes', + }, +} + +export const SingleFile: Story = { + args: { + showing: true, + files: 1, + fileName: 'document.pdf', + totalSyncingBytes: 512 * 1024, + timeLeft: '30 seconds', + }, +} + +export const Done: Story = { + args: { + showing: true, + files: 0, + totalSyncingBytes: 0, + timeLeft: '', + }, +} + +export const SmallMode: Story = { + args: { + showing: true, + files: 2, + fileName: 'report.docx', + totalSyncingBytes: 256 * 1024, + timeLeft: '1 minute', + smallMode: true, + }, +} + +export const Hidden: Story = { + args: { + showing: false, + files: 0, + totalSyncingBytes: 0, + timeLeft: '', + }, +} diff --git a/shared/fs/simple-screens/loading.stories.tsx b/shared/fs/simple-screens/loading.stories.tsx new file mode 100644 index 000000000000..09253a116306 --- /dev/null +++ b/shared/fs/simple-screens/loading.stories.tsx @@ -0,0 +1,15 @@ +import type {Meta, StoryObj} from '@storybook/react' +import LoadingScreen from './loading' + +const meta: Meta = { + component: LoadingScreen, + title: 'FS/LoadingScreen', +} +export default meta +type Story = StoryObj + +export const Default: Story = {} + +export const WithReason: Story = { + args: {why: ' listing files'}, +} diff --git a/shared/git/nav-header.stories.tsx b/shared/git/nav-header.stories.tsx new file mode 100644 index 000000000000..85ded46718a2 --- /dev/null +++ b/shared/git/nav-header.stories.tsx @@ -0,0 +1,11 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {HeaderTitle} from './nav-header' + +const meta: Meta = { + component: HeaderTitle, + title: 'Git/NavHeaderTitle', +} +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/shared/login/loading.stories.tsx b/shared/login/loading.stories.tsx new file mode 100644 index 000000000000..2bdb264ff5c4 --- /dev/null +++ b/shared/login/loading.stories.tsx @@ -0,0 +1,41 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {Splash} from './loading' + +const meta: Meta = { + component: Splash, + title: 'Login/Splash', +} +export default meta +type Story = StoryObj + +export const Loading: Story = { + args: { + failed: '', + status: 'Loading...', + }, +} + +export const StillTrying: Story = { + args: { + failed: '', + status: 'Loading... (still trying)', + }, +} + +export const Failed: Story = { + args: { + failed: 'connection refused', + status: '', + onRetry: () => {}, + }, +} + +export const FailedWithFeedback: Story = { + args: { + allowFeedback: true, + failed: 'connection refused', + status: '', + onRetry: () => {}, + onFeedback: () => {}, + }, +} diff --git a/shared/login/recover-password/error.stories.tsx b/shared/login/recover-password/error.stories.tsx new file mode 100644 index 000000000000..ff4258e4b700 --- /dev/null +++ b/shared/login/recover-password/error.stories.tsx @@ -0,0 +1,21 @@ +import type {Meta, StoryObj} from '@storybook/react' +import ConnectedError from './error' + +const meta: Meta = { + component: ConnectedError, + title: 'Login/RecoverPasswordError', +} +export default meta +type Story = StoryObj + +export const GenericError: Story = { + args: { + route: {params: {error: 'An unexpected error occurred. Please try again.'}}, + }, +} + +export const NetworkError: Story = { + args: { + route: {params: {error: 'Network error: could not reach Keybase servers.'}}, + }, +} diff --git a/shared/login/recover-password/explain-device.stories.tsx b/shared/login/recover-password/explain-device.stories.tsx new file mode 100644 index 000000000000..31745d3d559c --- /dev/null +++ b/shared/login/recover-password/explain-device.stories.tsx @@ -0,0 +1,34 @@ +import type {Meta, StoryObj} from '@storybook/react' +import * as T from '@/constants/types' +import ExplainDevice from './explain-device' + +const meta: Meta = { + component: ExplainDevice, + title: 'Login/RecoverPasswordExplainDevice', +} +export default meta +type Story = StoryObj + +export const Desktop: Story = { + args: { + route: { + params: { + deviceName: 'work-laptop', + deviceType: T.RPCGen.DeviceType.desktop, + username: 'testuser', + }, + }, + }, +} + +export const Mobile: Story = { + args: { + route: { + params: { + deviceName: 'iPhone 15', + deviceType: T.RPCGen.DeviceType.mobile, + username: 'testuser', + }, + }, + }, +} diff --git a/shared/login/recover-password/paper-key.stories.tsx b/shared/login/recover-password/paper-key.stories.tsx new file mode 100644 index 000000000000..203a52e08d03 --- /dev/null +++ b/shared/login/recover-password/paper-key.stories.tsx @@ -0,0 +1,24 @@ +import type {Meta, StoryObj} from '@storybook/react' +import PaperKey from './paper-key' + +const meta: Meta = { + component: PaperKey, + title: 'Login/RecoverPasswordPaperKey', + args: { + route: {params: {}}, + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = { + args: { + route: {params: {}}, + }, +} + +export const WithError: Story = { + args: { + route: {params: {error: 'Incorrect paper key. Please try again.'}}, + }, +} diff --git a/shared/login/recover-password/prompt-reset-account.stories.tsx b/shared/login/recover-password/prompt-reset-account.stories.tsx new file mode 100644 index 000000000000..bffb1ece594f --- /dev/null +++ b/shared/login/recover-password/prompt-reset-account.stories.tsx @@ -0,0 +1,31 @@ +import type {Meta, StoryObj} from '@storybook/react' +import PromptReset from './prompt-reset-shared' + +const meta: Meta = { + component: PromptReset, + title: 'Login/RecoverPasswordPromptReset', +} +export default meta +type Story = StoryObj + +export const AccountReset: Story = { + args: { + skipPassword: true, + username: 'testuser', + }, +} + +export const ResetPassword: Story = { + args: { + resetPassword: true, + skipPassword: false, + username: 'testuser', + }, +} + +export const KnowPassword: Story = { + args: { + skipPassword: false, + username: 'testuser', + }, +} diff --git a/shared/login/reset/waiting.stories.tsx b/shared/login/reset/waiting.stories.tsx new file mode 100644 index 000000000000..ee5ee459b03d --- /dev/null +++ b/shared/login/reset/waiting.stories.tsx @@ -0,0 +1,26 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Waiting from './waiting' + +const meta: Meta = { + component: Waiting, + title: 'Login/ResetWaiting', +} +export default meta +type Story = StoryObj + +const futureTime = Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days from now + +export const CheckEmailOrPhone: Story = { + args: { + pipelineStarted: false, + username: 'testuser', + }, +} + +export const PipelineStarted: Story = { + args: { + endTime: futureTime, + pipelineStarted: true, + username: 'testuser', + }, +} diff --git a/shared/login/signup/error.stories.tsx b/shared/login/signup/error.stories.tsx new file mode 100644 index 000000000000..d0a2ab7eff82 --- /dev/null +++ b/shared/login/signup/error.stories.tsx @@ -0,0 +1,28 @@ +import type {Meta, StoryObj} from '@storybook/react' +import ConnectedSignupError from './error' + +const meta: Meta = { + component: ConnectedSignupError, + title: 'Login/SignupError', +} +export default meta +type Story = StoryObj + +export const GenericError: Story = { + args: { + route: {params: {errorMessage: 'An unexpected error occurred. Please try again.'}}, + }, +} + +export const NetworkError: Story = { + args: { + // errorCode 8 is scapinetworkerror + route: {params: {errorCode: 8, errorMessage: 'Network error.'}}, + }, +} + +export const NoMessage: Story = { + args: { + route: {params: {}}, + }, +} diff --git a/shared/login/user-card/index.stories.tsx b/shared/login/user-card/index.stories.tsx new file mode 100644 index 000000000000..ab94c8cd62a3 --- /dev/null +++ b/shared/login/user-card/index.stories.tsx @@ -0,0 +1,55 @@ +import type {Meta, StoryObj} from '@storybook/react' +import * as Kb from '@/common-adapters' +import UserCard from './index' + +const meta: Meta = { + component: UserCard, + title: 'Login/UserCard', +} +export default meta +type Story = StoryObj + +export const WithUsername: Story = { + args: { + username: 'testuser', + children: ( + + Card content here + + ), + }, +} + +export const WithoutUsername: Story = { + args: { + children: ( + + No username yet + + ), + }, +} + +export const LargeAvatar: Story = { + args: { + username: 'testuser', + avatarSize: 128, + children: ( + + Large avatar + + ), + }, +} + +export const SmallAvatar: Story = { + args: { + username: 'testuser', + avatarSize: 48, + children: ( + + Small avatar + + ), + }, +} diff --git a/shared/menubar/out-of-date.stories.tsx b/shared/menubar/out-of-date.stories.tsx new file mode 100644 index 000000000000..9b73be02eeac --- /dev/null +++ b/shared/menubar/out-of-date.stories.tsx @@ -0,0 +1,54 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import OutOfDate from './out-of-date' + +const makeOutOfDate = (overrides: Partial = {}): T.Config.OutOfDate => ({ + critical: false, + message: '', + outOfDate: true, + updating: false, + ...overrides, +}) + +const meta: Meta = { + component: OutOfDate, + title: 'Menubar/OutOfDate', +} +export default meta +type Story = StoryObj + +export const NonCritical: Story = { + args: { + outOfDate: makeOutOfDate(), + }, +} + +export const Critical: Story = { + args: { + outOfDate: makeOutOfDate({critical: true}), + }, +} + +export const NonCriticalWithMessage: Story = { + args: { + outOfDate: makeOutOfDate({message: 'Please update to get the latest security fixes'}), + }, +} + +export const CriticalWithMessage: Story = { + args: { + outOfDate: makeOutOfDate({critical: true, message: 'Critical security patch required'}), + }, +} + +export const Updating: Story = { + args: { + outOfDate: makeOutOfDate({updating: true}), + }, +} + +export const CriticalUpdating: Story = { + args: { + outOfDate: makeOutOfDate({critical: true, updating: true}), + }, +} diff --git a/shared/package.json b/shared/package.json index 54802b630aa0..ce1c25de2583 100644 --- a/shared/package.json +++ b/shared/package.json @@ -64,6 +64,8 @@ "rn:start": "npx expo start --clear 2>&1", "rn:start:log": "npx expo start --clear 2>&1 | tee /tmp/metro.log", "rn:start:legacy": "./react-native/packageAndBuild.sh", + "storybook": "storybook dev -p 6006 --config-dir .storybook", + "storybook:screenshot": "node --experimental-strip-types scripts/screenshot-storybook.mts", "sync:kb-modules": "yarn run _helper sync-local-rnmodules", "test:e2e:desktop": "playwright test --config tests/e2e/electron/playwright.config.ts", "test:e2e:desktop:branch": "playwright test --config tests/e2e/electron/playwright.config.ts tests/e2e/electron/flows/team-member.test.ts && yarn test:e2e:desktop:report", @@ -153,6 +155,8 @@ "@react-native/babel-preset": "0.85.3", "@react-native/eslint-config": "0.85.3", "@react-native/metro-config": "0.85.3", + "@storybook/react": "10.4.1", + "@storybook/react-webpack5": "10.4.1", "@testing-library/dom": "10.4.1", "@testing-library/react": "16.3.2", "@types/google-libphonenumber": "7.4.30", @@ -184,6 +188,7 @@ "null-loader": "4.0.1", "patch-package": "8.0.1", "react-refresh": "0.18.0", + "storybook": "10.4.1", "style-loader": "4.0.0", "terser-webpack-plugin": "5.6.1", "typescript": "6.0.3", diff --git a/shared/people/announcement.stories.tsx b/shared/people/announcement.stories.tsx new file mode 100644 index 000000000000..e06ca20c6037 --- /dev/null +++ b/shared/people/announcement.stories.tsx @@ -0,0 +1,69 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Announcement from './announcement' + +const meta: Meta = { + component: Announcement, + title: 'People/Announcement', + args: { + badged: false, + dismissable: false, + dismissAnnouncement: () => {}, + getData: () => {}, + id: 1, + text: 'Keybase has a new feature available.', + }, +} +export default meta +type Story = StoryObj + +export const Basic: Story = { + args: { + text: 'Keybase has a new feature available.', + badged: false, + dismissable: false, + }, +} + +export const Badged: Story = { + args: { + text: 'Important update: please review your security settings.', + badged: true, + dismissable: false, + }, +} + +export const WithConfirmButton: Story = { + args: { + text: 'You can now add your phone number for account recovery.', + confirmLabel: 'Add phone', + badged: false, + dismissable: false, + }, +} + +export const Dismissable: Story = { + args: { + text: 'Check out the new Keybase Teams feature.', + confirmLabel: 'Learn more', + badged: false, + dismissable: true, + }, +} + +export const WithIconUrl: Story = { + args: { + text: 'Your Keybase profile has been verified.', + iconUrl: 'https://keybase.io/images/icons/icon-keybase-logo-48@2x.png', + badged: false, + dismissable: false, + }, +} + +export const LongText: Story = { + args: { + text: 'Keybase now supports end-to-end encrypted git repositories for teams. All members of your team will be able to securely collaborate on code without exposing it to outside parties. Visit the Git tab to create your first team repository.', + confirmLabel: 'Open Git', + badged: true, + dismissable: true, + }, +} diff --git a/shared/people/follow-notification.stories.tsx b/shared/people/follow-notification.stories.tsx new file mode 100644 index 000000000000..6039a24eae80 --- /dev/null +++ b/shared/people/follow-notification.stories.tsx @@ -0,0 +1,93 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type {NewFollow} from './follow-notification' +import FollowNotification from './follow-notification' + +const now = new Date() +const hourAgo = new Date(now.getTime() - 60 * 60 * 1000) +const dayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000) + +const makeFollow = (username: string, contactDescription?: string): NewFollow => ({ + username, + ...(contactDescription ? {contactDescription} : {}), +}) + +const meta: Meta = { + component: FollowNotification, + title: 'People/FollowNotification', + args: { + onClickUser: () => {}, + badged: false, + notificationTime: hourAgo, + type: 'follow', + newFollows: [makeFollow('alice')], + }, +} +export default meta +type Story = StoryObj + +export const SingleFollow: Story = { + args: { + newFollows: [makeFollow('alice')], + notificationTime: hourAgo, + type: 'follow', + badged: false, + }, +} + +export const SingleFollowBadged: Story = { + args: { + newFollows: [makeFollow('bob')], + notificationTime: now, + type: 'follow', + badged: true, + }, +} + +export const SingleContact: Story = { + args: { + newFollows: [makeFollow('carol', 'Carol White (+1-555-0001)')], + notificationTime: dayAgo, + type: 'contact', + badged: false, + }, +} + +export const MultiFollow: Story = { + args: { + newFollows: [makeFollow('alice'), makeFollow('bob'), makeFollow('carol')], + notificationTime: hourAgo, + type: 'follow', + badged: false, + }, +} + +export const MultiFollowBadged: Story = { + args: { + newFollows: [makeFollow('dave'), makeFollow('eve')], + notificationTime: now, + type: 'follow', + badged: true, + }, +} + +export const MultiFollowWithAdditional: Story = { + args: { + newFollows: [makeFollow('frank'), makeFollow('grace'), makeFollow('heidi')], + numAdditional: 7, + notificationTime: dayAgo, + type: 'follow', + badged: false, + }, +} + +export const MultiContact: Story = { + args: { + newFollows: [ + makeFollow('user1', 'Contact A (+1-555-0002)'), + makeFollow('user2', 'Contact B (+1-555-0003)'), + ], + notificationTime: hourAgo, + type: 'contact', + badged: false, + }, +} diff --git a/shared/people/follow-suggestions.stories.tsx b/shared/people/follow-suggestions.stories.tsx new file mode 100644 index 000000000000..0c70e4c8112c --- /dev/null +++ b/shared/people/follow-suggestions.stories.tsx @@ -0,0 +1,64 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type {FollowSuggestion} from './follow-suggestions' +import FollowSuggestions from './follow-suggestions' + +const makeSuggestion = (overrides: Partial = {}): FollowSuggestion => ({ + followsMe: false, + fullName: '', + iFollow: false, + username: 'user', + ...overrides, +}) + +const meta: Meta = { + component: FollowSuggestions, + title: 'People/FollowSuggestions', +} +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + suggestions: [ + makeSuggestion({username: 'alice', fullName: 'Alice Smith', followsMe: true}), + makeSuggestion({username: 'bob', fullName: 'Bob Johnson'}), + makeSuggestion({username: 'carol', fullName: 'Carol White', iFollow: true}), + makeSuggestion({username: 'dave'}), + makeSuggestion({username: 'eve', fullName: 'Eve Brown', followsMe: true, iFollow: true}), + ], + }, +} + +export const FewSuggestions: Story = { + args: { + suggestions: [ + makeSuggestion({username: 'keybase', fullName: 'Keybase'}), + makeSuggestion({username: 'kbnews', fullName: 'Keybase News'}), + ], + }, +} + +export const ManySuggestions: Story = { + args: { + suggestions: [ + makeSuggestion({username: 'alice', fullName: 'Alice Smith'}), + makeSuggestion({username: 'bob', fullName: 'Bob Johnson'}), + makeSuggestion({username: 'carol', fullName: 'Carol White'}), + makeSuggestion({username: 'dave', fullName: 'Dave Davis'}), + makeSuggestion({username: 'eve', fullName: 'Eve Brown'}), + makeSuggestion({username: 'frank', fullName: 'Frank Lee'}), + makeSuggestion({username: 'grace', fullName: 'Grace Park'}), + makeSuggestion({username: 'heidi', fullName: 'Heidi Chan'}), + ], + }, +} + +export const NoFullNames: Story = { + args: { + suggestions: [ + makeSuggestion({username: 'user1'}), + makeSuggestion({username: 'user2'}), + makeSuggestion({username: 'user3'}), + ], + }, +} diff --git a/shared/people/item.stories.tsx b/shared/people/item.stories.tsx new file mode 100644 index 000000000000..4684c3e8dd6e --- /dev/null +++ b/shared/people/item.stories.tsx @@ -0,0 +1,71 @@ +import type {Meta, StoryObj} from '@storybook/react' +import * as Kb from '@/common-adapters' +import PeopleItem from './item' + +const when = new Date('2024-03-15T10:00:00Z') + +const meta: Meta = { + component: PeopleItem, + title: 'People/Item', + args: { + badged: false, + when, + }, +} +export default meta +type Story = StoryObj + +export const BasicText: Story = { + args: { + children: Someone followed you., + badged: false, + when, + }, +} + +export const Badged: Story = { + args: { + children: New activity on your account., + badged: true, + when, + }, +} + +export const WithIcon: Story = { + args: { + children: marcos followed you., + badged: false, + icon: , + when, + }, +} + +export const WithButtons: Story = { + args: { + children: Add your phone number to your account., + badged: true, + icon: , + buttons: [ + {label: 'Add phone', onClick: () => {}}, + {label: 'Skip', mode: 'Secondary' as const, onClick: () => {}}, + ], + when, + }, +} + +export const MultiFormat: Story = { + args: { + children: marcelina, max, and 3 others started following you., + badged: false, + format: 'multi' as const, + when, + }, +} + +export const NoTimestamp: Story = { + args: { + children: A pinned announcement from Keybase., + badged: false, + icon: , + }, +} diff --git a/shared/pinentry/index.desktop.stories.tsx b/shared/pinentry/index.desktop.stories.tsx new file mode 100644 index 000000000000..5909864507e0 --- /dev/null +++ b/shared/pinentry/index.desktop.stories.tsx @@ -0,0 +1,70 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import * as RPCGen from '@/constants/rpc/rpc-gen' +import Pinentry from './index.desktop' + +const makeFeature = (overrides: Partial = {}): T.RPCGen.Feature => ({ + allow: true, + defaultValue: false, + label: 'Show typing', + readonly: false, + ...overrides, +}) + +const meta: Meta = { + component: Pinentry, + title: 'Pinentry/Pinentry', + args: { + onCancel: () => {}, + onSubmit: () => {}, + prompt: 'Enter your passphrase', + type: RPCGen.PassphraseType.passPhrase, + }, +} +export default meta +type Story = StoryObj + +export const Passphrase: Story = { + args: { + prompt: 'Enter your Keybase passphrase', + type: RPCGen.PassphraseType.passPhrase, + }, +} + +export const PassphraseWithShowTyping: Story = { + args: { + prompt: 'Enter your Keybase passphrase', + type: RPCGen.PassphraseType.passPhrase, + showTyping: makeFeature({allow: true, defaultValue: false, label: 'Show typing'}), + }, +} + +export const PaperKey: Story = { + args: { + prompt: 'Enter your paper key', + type: RPCGen.PassphraseType.paperKey, + }, +} + +export const PaperKeyWithError: Story = { + args: { + prompt: 'Enter your paper key', + type: RPCGen.PassphraseType.paperKey, + retryLabel: 'Incorrect paper key, please try again', + }, +} + +export const VerifyPassphrase: Story = { + args: { + prompt: 'Verify your passphrase to continue', + type: RPCGen.PassphraseType.verifyPassPhrase, + }, +} + +export const CustomLabels: Story = { + args: { + prompt: 'Unlock your encrypted folder', + type: RPCGen.PassphraseType.passPhrase, + submitLabel: 'Unlock', + }, +} diff --git a/shared/profile/modal.stories.tsx b/shared/profile/modal.stories.tsx new file mode 100644 index 000000000000..7efe220b1f19 --- /dev/null +++ b/shared/profile/modal.stories.tsx @@ -0,0 +1,31 @@ +import type {Meta, StoryObj} from '@storybook/react' +import * as Kb from '@/common-adapters' +import Modal from './modal' + +const meta: Meta = { + component: Modal, + title: 'Profile/Modal', +} +export default meta +type Story = StoryObj + +export const WithCancelButton: Story = { + args: { + children: Modal content goes here., + onCancel: () => {}, + }, +} + +export const WithoutCancelButton: Story = { + args: { + children: Modal content without a cancel button., + }, +} + +export const SkipButton: Story = { + args: { + children: Modal with onCancel but skipButton=true (no button rendered)., + onCancel: () => {}, + skipButton: true, + }, +} diff --git a/shared/profile/platform-icon.stories.tsx b/shared/profile/platform-icon.stories.tsx new file mode 100644 index 000000000000..7c70282ef41a --- /dev/null +++ b/shared/profile/platform-icon.stories.tsx @@ -0,0 +1,66 @@ +import type {Meta, StoryObj} from '@storybook/react' +import PlatformIcon from './platform-icon' + +const meta: Meta = { + component: PlatformIcon, + title: 'Profile/PlatformIcon', +} +export default meta +type Story = StoryObj + +export const Twitter: Story = { + args: { + platform: 'twitter', + overlay: 'iconfont-proof-good', + }, +} + +export const GitHub: Story = { + args: { + platform: 'github', + overlay: 'iconfont-proof-good', + }, +} + +export const Reddit: Story = { + args: { + platform: 'reddit', + overlay: 'iconfont-proof-good', + }, +} + +export const HackerNews: Story = { + args: { + platform: 'hackernews', + overlay: 'iconfont-proof-good', + }, +} + +export const Website: Story = { + args: { + platform: 'web', + overlay: 'iconfont-proof-good', + }, +} + +export const Bitcoin: Story = { + args: { + platform: 'btc', + overlay: 'iconfont-proof-good', + }, +} + +export const PGP: Story = { + args: { + platform: 'pgp', + overlay: 'iconfont-proof-good', + }, +} + +export const BrokenProof: Story = { + args: { + platform: 'twitter', + overlay: 'iconfont-proof-broken', + overlayColor: 'red', + }, +} diff --git a/shared/provision/code-page/qr-image.stories.tsx b/shared/provision/code-page/qr-image.stories.tsx new file mode 100644 index 000000000000..6cdbe33c2e5e --- /dev/null +++ b/shared/provision/code-page/qr-image.stories.tsx @@ -0,0 +1,26 @@ +import type {Meta, StoryObj} from '@storybook/react' +import QrImage from './qr-image' + +const meta: Meta = { + component: QrImage, + title: 'Provision/QrImage', + args: { + code: 'deadbeef cafe babe 1234 5678 abcd efgh ijkl mnop', + }, +} +export default meta +type Story = StoryObj + +export const Default: Story = {} + +export const SmallCells: Story = { + args: { + cellSize: 8, + }, +} + +export const LargeCells: Story = { + args: { + cellSize: 10, + }, +} diff --git a/shared/provision/error.stories.tsx b/shared/provision/error.stories.tsx new file mode 100644 index 000000000000..0debbe1303f7 --- /dev/null +++ b/shared/provision/error.stories.tsx @@ -0,0 +1,75 @@ +import type {Meta, StoryObj} from '@storybook/react' +import * as T from '@/constants/types' +import RenderError from './error' +import type {ProvisionRouteError} from '@/stores/provision' + +const makeError = (overrides: Partial = {}): ProvisionRouteError => ({ + code: T.RPCGen.StatusCode.scgeneric, + desc: 'Something went wrong.', + details: '', + message: 'Generic error', + ...overrides, +}) + +const meta: Meta = { + component: RenderError, + title: 'Provision/Error', +} +export default meta +type Story = StoryObj + +export const NoError: Story = { + args: { + route: {params: {}}, + }, +} + +export const GenericError: Story = { + args: { + route: { + params: { + error: makeError({desc: 'Is the other device using the username you expect? It seems to be different.'}), + }, + }, + }, +} + +export const Offline: Story = { + args: { + route: { + params: { + error: makeError({code: T.RPCGen.StatusCode.scdeviceprovisionoffline}), + }, + }, + }, +} + +export const BadPassword: Story = { + args: { + route: { + params: { + error: makeError({code: T.RPCGen.StatusCode.scbadloginpassword}), + }, + }, + }, +} + +export const NoProvision: Story = { + args: { + route: { + params: { + error: makeError({code: T.RPCGen.StatusCode.scdevicenoprovision}), + }, + }, + }, +} + +export const Deleted: Story = { + args: { + route: { + params: { + error: makeError({code: T.RPCGen.StatusCode.scdeleted}), + }, + }, + }, +} diff --git a/shared/provision/paper-key.stories.tsx b/shared/provision/paper-key.stories.tsx new file mode 100644 index 000000000000..f83bd9f60f59 --- /dev/null +++ b/shared/provision/paper-key.stories.tsx @@ -0,0 +1,35 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {PaperKey} from './paper-key' + +const meta: Meta = { + component: PaperKey, + title: 'Provision/PaperKey', + args: { + hint: 'testuser-mac...', + error: '', + waiting: false, + onSubmit: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = {} + +export const WithHint: Story = { + args: { + hint: 'family laptop...', + }, +} + +export const WithError: Story = { + args: { + error: 'Incorrect paper key. Please try again.', + }, +} + +export const Waiting: Story = { + args: { + waiting: true, + }, +} diff --git a/shared/provision/select-other-device.stories.tsx b/shared/provision/select-other-device.stories.tsx new file mode 100644 index 000000000000..811f5a9c01b1 --- /dev/null +++ b/shared/provision/select-other-device.stories.tsx @@ -0,0 +1,55 @@ +import type {Meta, StoryObj} from '@storybook/react' +import SelectOtherDevice from './select-other-device' +import type {Device} from '@/stores/provision' + +const makeDevice = (overrides: Partial = {}): Device => ({ + deviceNumberOfType: 0, + id: 'device-001' as Device['id'], + name: 'My Device', + type: 'desktop', + ...overrides, +}) + +const meta: Meta = { + component: SelectOtherDevice, + title: 'Provision/SelectOtherDevice', + args: { + onBack: () => {}, + onSelect: () => {}, + onResetAccount: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const MultipleDevices: Story = { + args: { + devices: [ + makeDevice({name: 'work-laptop', type: 'desktop', deviceNumberOfType: 0}), + makeDevice({name: 'iPhone 15', type: 'mobile', deviceNumberOfType: 1, id: 'device-002' as Device['id']}), + makeDevice({name: 'Paper key', type: 'backup', deviceNumberOfType: 0, id: 'device-003' as Device['id']}), + ], + }, +} + +export const SingleDesktop: Story = { + args: { + devices: [makeDevice({name: 'work-laptop', type: 'desktop'})], + }, +} + +export const PasswordRecovery: Story = { + args: { + passwordRecovery: true, + devices: [ + makeDevice({name: 'work-laptop', type: 'desktop'}), + makeDevice({name: 'iPhone 15', type: 'mobile', id: 'device-002' as Device['id']}), + ], + }, +} + +export const Empty: Story = { + args: { + devices: [], + }, +} diff --git a/shared/provision/troubleshooting.stories.tsx b/shared/provision/troubleshooting.stories.tsx new file mode 100644 index 000000000000..d3a59650e7c8 --- /dev/null +++ b/shared/provision/troubleshooting.stories.tsx @@ -0,0 +1,33 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Troubleshooting from './troubleshooting' + +const meta: Meta = { + component: Troubleshooting, + title: 'Provision/Troubleshooting', + args: { + onCancel: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const QRMode: Story = { + args: { + mode: 'QR', + otherDeviceType: 'desktop', + }, +} + +export const TextMode: Story = { + args: { + mode: 'text', + otherDeviceType: 'desktop', + }, +} + +export const OtherDeviceMobile: Story = { + args: { + mode: 'QR', + otherDeviceType: 'mobile', + }, +} diff --git a/shared/router-v2/header/syncing-folders.stories.tsx b/shared/router-v2/header/syncing-folders.stories.tsx new file mode 100644 index 000000000000..877dabef9048 --- /dev/null +++ b/shared/router-v2/header/syncing-folders.stories.tsx @@ -0,0 +1,64 @@ +import type {Meta, StoryObj} from '@storybook/react' +import PieSlice from '@/fs/common/pie-slice' +import * as Kb from '@/common-adapters' + +// SyncingFolders default export wraps store hooks; story the inner pure UI directly. +const SyncingFoldersUI = ({ + progress, + tooltip, + negative, +}: { + progress: number + tooltip: string + negative?: boolean +}) => ( + + + + + Syncing folders... + + + +) + +const meta: Meta = { + component: SyncingFoldersUI, + title: 'RouterV2/SyncingFolders', + args: { + progress: 0.5, + tooltip: '50 MB / 100 MB', + negative: false, + }, +} +export default meta +type Story = StoryObj + +export const HalfwayDone: Story = { + args: { + progress: 0.5, + tooltip: '50 MB / 100 MB', + }, +} + +export const AlmostDone: Story = { + args: { + progress: 0.9, + tooltip: '90 MB / 100 MB', + }, +} + +export const JustStarted: Story = { + args: { + progress: 0.05, + tooltip: '5 MB / 100 MB', + }, +} + +export const NegativeVariant: Story = { + args: { + progress: 0.6, + tooltip: '60 MB / 100 MB', + negative: true, + }, +} diff --git a/shared/scripts/screenshot-storybook.mts b/shared/scripts/screenshot-storybook.mts new file mode 100644 index 000000000000..078a9f768a1f --- /dev/null +++ b/shared/scripts/screenshot-storybook.mts @@ -0,0 +1,118 @@ +/** + * Builds storybook statically, serves it, screenshots every story, saves PNGs + * to tests/results/storybook-desktop/. Self-contained — no running dev server needed. + */ +import {chromium} from '@playwright/test' +import {execSync} from 'child_process' +import fs from 'fs' +import http from 'http' +import path from 'path' +import {fileURLToPath, URL as NodeURL} from 'url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const sharedDir = path.resolve(__dirname, '..') +const buildDir = '/tmp/storybook-static' +const outputDir = path.resolve(__dirname, '../tests/results/storybook-desktop') +const PORT = 6007 +const CONCURRENCY = 6 + +// Build storybook to a static directory +console.log('Building storybook (this compiles everything upfront)...') +execSync(`node_modules/.bin/storybook build --output-dir ${buildDir} --config-dir .storybook`, { + cwd: sharedDir, + stdio: 'inherit', +}) +console.log('Build complete.') + +const MIME: Record = { + '.html': 'text/html', + '.js': 'application/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.woff2': 'font/woff2', + '.woff': 'font/woff', + '.ttf': 'font/ttf', + '.ico': 'image/x-icon', + '.svg': 'image/svg+xml', +} +const server = http.createServer((req, res) => { + let urlPath = new NodeURL(req.url ?? '/', `http://localhost`).pathname + if (urlPath === '/') urlPath = '/index.html' + const filePath = path.join(buildDir, urlPath) + if (!filePath.startsWith(buildDir + path.sep)) { + res.writeHead(403) + res.end('Forbidden') + return + } + try { + const data = fs.readFileSync(filePath) + const ext = path.extname(filePath) + res.writeHead(200, {'Content-Type': MIME[ext] ?? 'application/octet-stream'}) + res.end(data) + } catch { + res.writeHead(404) + res.end('Not found') + } +}) +await new Promise(resolve => server.listen(PORT, '127.0.0.1', resolve)) +const storybookUrl = `http://localhost:${PORT}` +console.log(`Serving static build at ${storybookUrl}`) + +const {entries} = (await (await fetch(`${storybookUrl}/index.json`)).json()) as { + entries: Record +} +const stories = Object.entries(entries).filter(([, e]) => e.type === 'story') +console.log(`Found ${stories.length} stories — concurrency ${CONCURRENCY}`) + +fs.rmSync(outputDir, {recursive: true, force: true}) +fs.mkdirSync(outputDir, {recursive: true}) + +const executablePath = process.env['CHROME_PATH'] +const browser = await chromium.launch(executablePath ? {executablePath} : {}) + +const queue = [...stories] +let done = 0 +const total = stories.length * 2 + +await Promise.all( + Array.from({length: CONCURRENCY}, async () => { + const page = await browser.newPage({viewport: {width: 1280, height: 800}}) + + while (queue.length) { + const item = queue.shift() + if (!item) break + const [id, {title, name}] = item + try { + const storyDir = path.join(outputDir, title.replaceAll('/', '-')) + fs.mkdirSync(storyDir, {recursive: true}) + const slug = name.replaceAll(/\s+/g, '-') + + await page.emulateMedia({colorScheme: 'light'}) + await page.goto(`${storybookUrl}/iframe.html?id=${id}&viewMode=story`, { + waitUntil: 'load', + timeout: 10000, + }) + await page.screenshot({path: path.join(storyDir, `${slug}.png`), fullPage: true}) + done++ + console.log(` [${done}/${total}] ${title}/${name} (light)`) + + await page.emulateMedia({colorScheme: 'dark'}) + await page.goto(`${storybookUrl}/iframe.html?id=${id}&viewMode=story&globals=darkMode:true`, { + waitUntil: 'load', + timeout: 10000, + }) + await page.screenshot({path: path.join(storyDir, `${slug}-dark.png`), fullPage: true}) + done++ + console.log(` [${done}/${total}] ${title}/${name} (dark)`) + } catch (err) { + console.warn(` SKIP ${title}/${name}: ${(err as Error).message.split('\n')[0]}`) + } + } + await page.close() + }) +) + +await browser.close() +server.close() +console.log(`\nDone — ${done}/${total} screenshots in ${outputDir}`) diff --git a/shared/settings/group.stories.tsx b/shared/settings/group.stories.tsx new file mode 100644 index 000000000000..d67e5f6bdfca --- /dev/null +++ b/shared/settings/group.stories.tsx @@ -0,0 +1,70 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Group from './group' + +const meta: Meta = { + component: Group, + title: 'Settings/Group', + args: { + allowEdit: true, + groupName: 'email', + onToggle: () => {}, + unsubscribedFromAll: false, + }, +} +export default meta +type Story = StoryObj + +export const WithSettings: Story = { + args: { + title: 'Email notifications', + settings: [ + {description: 'When someone follows me', name: 'follow', subscribed: true}, + {description: 'When someone requests access to a team I manage', name: 'teamRequest', subscribed: false}, + {description: 'New team announcements', name: 'teamAnnounce', subscribed: true}, + ], + }, +} + +export const WithUnsubscribeAll: Story = { + args: { + title: 'Email notifications', + unsub: 'email', + settings: [ + {description: 'When someone follows me', name: 'follow', subscribed: true}, + {description: 'New team announcements', name: 'teamAnnounce', subscribed: true}, + ], + unsubscribedFromAll: false, + onToggleUnsubscribeAll: () => {}, + }, +} + +export const UnsubscribedFromAll: Story = { + args: { + title: 'Email notifications', + unsub: 'email', + settings: [ + {description: 'When someone follows me', name: 'follow', subscribed: false}, + {description: 'New team announcements', name: 'teamAnnounce', subscribed: false}, + ], + unsubscribedFromAll: true, + onToggleUnsubscribeAll: () => {}, + }, +} + +export const Disabled: Story = { + args: { + allowEdit: false, + title: 'Push notifications', + label: 'Enable in your device settings first.', + settings: [ + {description: 'New messages', name: 'chat', subscribed: false}, + {description: 'Someone follows you', name: 'follow', subscribed: false}, + ], + }, +} + +export const NoSettings: Story = { + args: { + title: 'No items group', + }, +} diff --git a/shared/settings/proxy.stories.tsx b/shared/settings/proxy.stories.tsx new file mode 100644 index 000000000000..a9ebf6b8fe60 --- /dev/null +++ b/shared/settings/proxy.stories.tsx @@ -0,0 +1,11 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {ProxySettings} from './proxy' + +const meta: Meta = { + component: ProxySettings, + title: 'Settings/Proxy', +} +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/shared/signup/device-name.stories.tsx b/shared/signup/device-name.stories.tsx new file mode 100644 index 000000000000..5c650892587c --- /dev/null +++ b/shared/signup/device-name.stories.tsx @@ -0,0 +1,26 @@ +import type {Meta, StoryObj} from '@storybook/react' +import ConnectedEnterDevicename from './device-name' + +// ConnectedEnterDevicename reads inviteCode/username from route.params and manages +// its own local state. The RPC calls are triggered by user interaction only. +const meta: Meta = { + component: ConnectedEnterDevicename, + title: 'Signup/EnterDevicename', + args: { + route: {params: {}}, + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = { + args: { + route: {params: {}}, + }, +} + +export const WithUsername: Story = { + args: { + route: {params: {username: 'testuser', inviteCode: 'abc123'}}, + }, +} diff --git a/shared/signup/email.stories.tsx b/shared/signup/email.stories.tsx new file mode 100644 index 000000000000..6541f9e5344b --- /dev/null +++ b/shared/signup/email.stories.tsx @@ -0,0 +1,40 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {EnterEmailBody} from './email' + +const meta: Meta = { + component: EnterEmailBody, + title: 'Signup/EnterEmail', + args: { + onChangeEmail: () => {}, + onContinue: () => {}, + email: '', + searchable: true, + onChangeSearchable: () => {}, + showSearchable: true, + iconType: 'icon-email-add-96', + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = {} + +export const Filled: Story = { + args: { + email: 'user@example.com', + }, +} + +export const NotSearchable: Story = { + args: { + email: 'private@example.com', + searchable: false, + }, +} + +export const HideSearchable: Story = { + args: { + showSearchable: false, + iconType: 'icon-email-add-64', + }, +} diff --git a/shared/signup/phone-number/index.stories.tsx b/shared/signup/phone-number/index.stories.tsx new file mode 100644 index 000000000000..12d2cacca201 --- /dev/null +++ b/shared/signup/phone-number/index.stories.tsx @@ -0,0 +1,37 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {EnterPhoneNumberBody} from './index' + +const meta: Meta = { + component: EnterPhoneNumberBody, + title: 'Signup/EnterPhoneNumber', + args: { + onChangeNumber: () => {}, + onContinue: () => {}, + searchable: true, + iconType: 'icon-phone-number-add-96', + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = {} + +export const WithSearchable: Story = { + args: { + onChangeSearchable: () => {}, + searchable: true, + }, +} + +export const NotSearchable: Story = { + args: { + onChangeSearchable: () => {}, + searchable: false, + }, +} + +export const SmallIcon: Story = { + args: { + iconType: 'icon-phone-number-add-64', + }, +} diff --git a/shared/signup/phone-number/verify-body.stories.tsx b/shared/signup/phone-number/verify-body.stories.tsx new file mode 100644 index 000000000000..5937d1d30690 --- /dev/null +++ b/shared/signup/phone-number/verify-body.stories.tsx @@ -0,0 +1,33 @@ +import type {Meta, StoryObj} from '@storybook/react' +import VerifyBody from './verify-body' + +const meta: Meta = { + component: VerifyBody, + title: 'Signup/PhoneNumberVerifyBody', + args: { + onChangeCode: () => {}, + onResend: () => {}, + resendWaiting: false, + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = { + args: { + code: '', + }, +} + +export const Filled: Story = { + args: { + code: '123456', + }, +} + +export const ResendWaiting: Story = { + args: { + code: '', + resendWaiting: true, + }, +} diff --git a/shared/signup/username.stories.tsx b/shared/signup/username.stories.tsx new file mode 100644 index 000000000000..c824d9b49293 --- /dev/null +++ b/shared/signup/username.stories.tsx @@ -0,0 +1,26 @@ +import type {Meta, StoryObj} from '@storybook/react' +import ConnectedEnterUsername from './username' + +// ConnectedEnterUsername reads inviteCode/username from route.params and manages +// its own local state. RPC calls are triggered by user interaction only. +const meta: Meta = { + component: ConnectedEnterUsername, + title: 'Signup/EnterUsername', + args: { + route: {params: {}}, + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = { + args: { + route: {params: {}}, + }, +} + +export const Prefilled: Story = { + args: { + route: {params: {username: 'testuser'}}, + }, +} diff --git a/shared/team-building/contact-restricted.stories.tsx b/shared/team-building/contact-restricted.stories.tsx new file mode 100644 index 000000000000..8fa735aba543 --- /dev/null +++ b/shared/team-building/contact-restricted.stories.tsx @@ -0,0 +1,37 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {ContactRestricted} from './contact-restricted' + +const meta: Meta = { + component: ContactRestricted, + title: 'TeamBuilding/ContactRestricted', +} +export default meta +type Story = StoryObj + +export const NewFolder: Story = { + args: { + source: 'newFolder', + usernames: ['restricteduser'], + }, +} + +export const TeamAddAllFailedSolo: Story = { + args: { + source: 'teamAddAllFailed', + usernames: ['restricteduser'], + }, +} + +export const TeamAddAllFailedMultiple: Story = { + args: { + source: 'teamAddAllFailed', + usernames: ['alice', 'bob', 'charlie'], + }, +} + +export const TeamAddSomeFailed: Story = { + args: { + source: 'teamAddSomeFailed', + usernames: ['alice', 'bob'], + }, +} diff --git a/shared/team-building/continue-button.stories.tsx b/shared/team-building/continue-button.stories.tsx new file mode 100644 index 000000000000..af077b0f1bcf --- /dev/null +++ b/shared/team-building/continue-button.stories.tsx @@ -0,0 +1,26 @@ +import type {Meta, StoryObj} from '@storybook/react' +import ContinueButton from './continue-button' + +const meta: Meta = { + component: ContinueButton, + title: 'TeamBuilding/ContinueButton', + args: { + label: 'Continue', + onClick: () => {}, + disabled: false, + }, +} +export default meta +type Story = StoryObj + +export const Enabled: Story = { + args: {label: 'Continue', disabled: false}, +} + +export const Disabled: Story = { + args: {label: 'Continue', disabled: true}, +} + +export const CustomLabel: Story = { + args: {label: 'Add to team', disabled: false}, +} diff --git a/shared/team-building/filtered-service-tab-bar.stories.tsx b/shared/team-building/filtered-service-tab-bar.stories.tsx new file mode 100644 index 000000000000..b49e4ecb6faa --- /dev/null +++ b/shared/team-building/filtered-service-tab-bar.stories.tsx @@ -0,0 +1,35 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import {FilteredServiceTabBar} from './filtered-service-tab-bar' + +const meta: Meta = { + component: FilteredServiceTabBar, + title: 'TeamBuilding/FilteredServiceTabBar', + args: { + selectedService: 'keybase', + onChangeService: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const AllServices: Story = {} + +export const KeybaseOnly: Story = { + // Returns null (single keybase service), rendered as empty + args: {filterServices: ['keybase'] as Array}, +} + +export const SocialServicesOnly: Story = { + args: { + filterServices: ['twitter', 'github', 'reddit'] as Array, + selectedService: 'twitter', + }, +} + +export const ContactServicesOnly: Story = { + args: { + filterServices: ['phone', 'email'] as Array, + selectedService: 'phone', + }, +} diff --git a/shared/team-building/go-button.stories.tsx b/shared/team-building/go-button.stories.tsx new file mode 100644 index 000000000000..f9f7c68421dc --- /dev/null +++ b/shared/team-building/go-button.stories.tsx @@ -0,0 +1,21 @@ +import type {Meta, StoryObj} from '@storybook/react' +import GoButton from './go-button' + +const meta: Meta = { + component: GoButton, + title: 'TeamBuilding/GoButton', + args: { + label: 'Start', + onClick: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const LabelStart: Story = { + args: {label: 'Start'}, +} + +export const LabelAdd: Story = { + args: {label: 'Add'}, +} diff --git a/shared/team-building/service-tab-bar.stories.tsx b/shared/team-building/service-tab-bar.stories.tsx new file mode 100644 index 000000000000..59ba692dca5b --- /dev/null +++ b/shared/team-building/service-tab-bar.stories.tsx @@ -0,0 +1,63 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import {ServiceTabBar} from './service-tab-bar' + +const allServices: Array = [ + 'keybase', + 'phone', + 'email', + 'twitter', + 'github', + 'reddit', + 'hackernews', +] + +const meta: Meta = { + component: ServiceTabBar, + title: 'TeamBuilding/ServiceTabBar', + args: { + services: allServices, + selectedService: 'keybase', + onChangeService: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const KeybaseSelected: Story = { + args: {selectedService: 'keybase'}, +} + +export const TwitterSelected: Story = { + args: {selectedService: 'twitter'}, +} + +export const PhoneSelected: Story = { + args: {selectedService: 'phone'}, +} + +export const EmailSelected: Story = { + args: {selectedService: 'email'}, +} + +export const FewServices: Story = { + args: { + services: ['keybase', 'twitter', 'github'] as Array, + selectedService: 'keybase', + }, +} + +export const WithServicesShown: Story = { + args: { + services: allServices, + selectedService: 'keybase', + servicesShown: 3, + }, +} + +export const MinimalBorder: Story = { + args: { + selectedService: 'github', + minimalBorder: true, + }, +} diff --git a/shared/team-building/team-box.stories.tsx b/shared/team-building/team-box.stories.tsx new file mode 100644 index 000000000000..1b9dca6dad57 --- /dev/null +++ b/shared/team-building/team-box.stories.tsx @@ -0,0 +1,71 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import TeamBox from './team-box' + +const makeUser = ( + username: string, + service: T.TB.ServiceIdWithContact = 'keybase', + prettyName = '' +): T.TB.SelectedUser => ({ + prettyName, + service, + userId: `${username}@${service}`, + username, +}) + +const meta: Meta = { + component: TeamBox, + title: 'TeamBuilding/TeamBox', + args: { + allowPhoneEmail: true, + onChangeText: () => {}, + onEnterKeyDown: () => {}, + onDownArrowKeyDown: () => {}, + onUpArrowKeyDown: () => {}, + onRemove: () => {}, + onFinishTeamBuilding: () => {}, + searchString: '', + teamSoFar: [], + }, +} +export default meta +type Story = StoryObj + +export const Empty: Story = {} + +export const OneKeybaseUser: Story = { + args: { + teamSoFar: [makeUser('testuser')], + }, +} + +export const MixedServices: Story = { + args: { + teamSoFar: [ + makeUser('testuser'), + makeUser('twitterfriend', 'twitter'), + makeUser('15551234567', 'phone'), + makeUser('user@example.com', 'email'), + ], + }, +} + +export const ManyUsers: Story = { + args: { + teamSoFar: [ + makeUser('alice'), + makeUser('bob'), + makeUser('charlie'), + makeUser('david'), + makeUser('eve'), + makeUser('frank'), + ], + }, +} + +export const WithGoButtonLabel: Story = { + args: { + teamSoFar: [makeUser('testuser')], + goButtonLabel: 'Add', + }, +} diff --git a/shared/team-building/user-bubble.stories.tsx b/shared/team-building/user-bubble.stories.tsx new file mode 100644 index 000000000000..47ab0d4f9c98 --- /dev/null +++ b/shared/team-building/user-bubble.stories.tsx @@ -0,0 +1,57 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type * as T from '@/constants/types' +import UserBubble from './user-bubble' + +const meta: Meta = { + component: UserBubble, + title: 'TeamBuilding/UserBubble', + args: { + username: 'testuser', + service: 'keybase' as T.TB.ServiceIdWithContact, + tooltip: 'testuser', + onRemove: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const KeybaseUser: Story = { + args: { + username: 'testuser', + service: 'keybase', + tooltip: 'testuser', + }, +} + +export const TwitterUser: Story = { + args: { + username: 'twitteruser', + service: 'twitter' as T.TB.ServiceIdWithContact, + tooltip: 'twitteruser@twitter', + }, +} + +export const GitHubUser: Story = { + args: { + username: 'githubuser', + service: 'github' as T.TB.ServiceIdWithContact, + tooltip: 'githubuser@github', + }, +} + +export const PhoneContact: Story = { + args: { + // E164 format without leading '+' + username: '15551234567', + service: 'phone' as T.TB.ServiceIdWithContact, + tooltip: '+1 (555) 123-4567', + }, +} + +export const EmailContact: Story = { + args: { + username: 'user@example.com', + service: 'email' as T.TB.ServiceIdWithContact, + tooltip: 'user@example.com', + }, +} diff --git a/shared/teams/new-team/index.stories.tsx b/shared/teams/new-team/index.stories.tsx new file mode 100644 index 000000000000..9e3708aa165c --- /dev/null +++ b/shared/teams/new-team/index.stories.tsx @@ -0,0 +1,23 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {CreateNewTeam} from './index' + +const meta: Meta = { + component: CreateNewTeam, + title: 'Teams/CreateNewTeam', + args: { + onCancel: () => {}, + onSubmit: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const NewTopLevelTeam: Story = {} + +export const NewSubteam: Story = { + args: {baseTeam: 'keybase'}, +} + +export const NewDeepSubteam: Story = { + args: {baseTeam: 'keybase.design'}, +} diff --git a/shared/teams/role-button.stories.tsx b/shared/teams/role-button.stories.tsx new file mode 100644 index 000000000000..5aedc55cd1ee --- /dev/null +++ b/shared/teams/role-button.stories.tsx @@ -0,0 +1,37 @@ +import type {Meta, StoryObj} from '@storybook/react' +import RoleButton from './role-button' + +const meta: Meta = { + component: RoleButton, + title: 'Teams/RoleButton', + args: { + selectedRole: 'reader', + onClick: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const Reader: Story = { + args: {selectedRole: 'reader'}, +} + +export const Writer: Story = { + args: {selectedRole: 'writer'}, +} + +export const Admin: Story = { + args: {selectedRole: 'admin'}, +} + +export const Owner: Story = { + args: {selectedRole: 'owner'}, +} + +export const Bot: Story = { + args: {selectedRole: 'bot'}, +} + +export const Loading: Story = { + args: {selectedRole: 'admin', loading: true}, +} diff --git a/shared/teams/role-picker.stories.tsx b/shared/teams/role-picker.stories.tsx new file mode 100644 index 000000000000..7b6bb744a3a2 --- /dev/null +++ b/shared/teams/role-picker.stories.tsx @@ -0,0 +1,55 @@ +import type {Meta, StoryObj} from '@storybook/react' +import {FloatingRolePicker} from './role-picker' + +// FloatingRolePicker wraps RolePicker (which is not directly exported). +// Stories use open={true} to render the picker content visibly. + +const meta: Meta = { + component: FloatingRolePicker, + title: 'Teams/RolePicker', + args: { + onConfirm: () => {}, + open: true, + }, +} +export default meta +type Story = StoryObj + +export const DefaultReader: Story = { + args: {presetRole: 'reader'}, +} + +export const PresetWriter: Story = { + args: {presetRole: 'writer'}, +} + +export const PresetAdmin: Story = { + args: {presetRole: 'admin'}, +} + +export const PresetOwner: Story = { + args: {presetRole: 'owner'}, +} + +export const WithCancelButton: Story = { + args: {presetRole: 'reader', onCancel: () => {}}, +} + +export const WithSetIndividually: Story = { + args: {presetRole: 'reader', includeSetIndividually: true, onCancel: () => {}}, +} + +export const PluralRoles: Story = { + args: {presetRole: 'writer', plural: true}, +} + +export const DisabledOwner: Story = { + args: { + presetRole: 'reader', + disabledRoles: {owner: 'Cannot invite an owner via email.'}, + }, +} + +export const Waiting: Story = { + args: {presetRole: 'writer', waiting: true}, +} diff --git a/shared/tests/e2e/electron/helpers/fixtures.ts b/shared/tests/e2e/electron/helpers/fixtures.ts index b305494a3ad8..5aec0b409ebd 100644 --- a/shared/tests/e2e/electron/helpers/fixtures.ts +++ b/shared/tests/e2e/electron/helpers/fixtures.ts @@ -1,4 +1,4 @@ -import {test as base, type Page} from '@playwright/test' +import {test as base, type Page, type WorkerInfo} from '@playwright/test' import {connectToElectron} from './connect' import {NAV_TAB_CHAT} from '@/tests/e2e/shared/test-ids' @@ -9,12 +9,19 @@ export const test = base.extend<{page: Page}, WorkerFixtures>({ // Playwright requires object destructuring syntax here — it uses static analysis to // detect fixture dependencies, so a plain identifier like `_fixtures` breaks injection. // eslint-disable-next-line no-empty-pattern - async ({}, setup) => { + async ({}, setup, workerInfo: WorkerInfo) => { + const isDark = workerInfo.project.name.endsWith('-dark') const {page} = await connectToElectron() - // Reload to clear any in-memory state left over from previous test runs + // emulateMedia sets prefers-color-scheme via CDP and persists across reloads + await page.emulateMedia({colorScheme: isDark ? 'dark' : 'light'}) + // Reload to clear in-memory state and apply the new color scheme await page.reload() await page.getByTestId(NAV_TAB_CHAT).waitFor({timeout: 30_000}) - await setup(page) + try { + await setup(page) + } finally { + await page.emulateMedia({colorScheme: null}) + } // Do NOT close — that kills the Electron process }, {scope: 'worker'}, diff --git a/shared/tests/e2e/electron/playwright.config.ts b/shared/tests/e2e/electron/playwright.config.ts index 1b33966ac9d3..d360d19b20cf 100644 --- a/shared/tests/e2e/electron/playwright.config.ts +++ b/shared/tests/e2e/electron/playwright.config.ts @@ -18,5 +18,6 @@ export default defineConfig({ }, projects: [ {name: 'electron-flows', testMatch: 'flows/**/*.test.ts'}, + {name: 'electron-flows-dark', testMatch: 'flows/**/*.test.ts'}, ], }) diff --git a/shared/tests/e2e/generate-electron-report.mts b/shared/tests/e2e/generate-electron-report.mts index 82bece2800a7..838c89c2d43d 100644 --- a/shared/tests/e2e/generate-electron-report.mts +++ b/shared/tests/e2e/generate-electron-report.mts @@ -10,6 +10,8 @@ const {PNG} = require('pngjs') as {PNG: {sync: {read: (buf: Buffer) => {data: Bu const resultsPath = 'tests/results/report/results.json' const debugDir = 'tests/results/electron-debug' const prevDir = 'tests/results/electron-prev' +const storybookDir = 'tests/results/storybook-desktop' +const storybookPrevDir = 'tests/results/storybook-prev' const outputPath = 'tests/results/electron-report.html' type Attachment = {name: string; contentType: string; body?: string; path?: string} @@ -21,6 +23,14 @@ type Report = {suites: PlaywrightSuite[]} type DiffResult = {pct: number; changed: number; total: number} +type StorybookCase = { + relPath: string + label: string + screenshotPath: string + prevScreenshotPath: string | null + diff: DiffResult | null +} + type TestCase = { key: string label: string @@ -64,51 +74,91 @@ function parseReport(report: Report): TestCase[] { const cases: TestCase[] = [] for (const suite of report.suites) { for (const {suiteName, spec} of flattenSpecs(suite)) { - const key = `${slugify(suiteName)}-${slugify(spec.title)}` - // take the last non-skipped result (handles retries) - const allResults = spec.tests.flatMap(t => t.results) - const result = allResults.filter(r => r.status !== 'skipped').at(-1) ?? allResults.at(-1) - if (!result) continue - - const passed = spec.ok - const durationMs = result.duration - const errorMessage = !passed - ? (result.errors?.[0]?.message?.split('\n')[0] ?? 'test failed') - : null - - const screenshotAtt = result.attachments.find(a => a.name === 'screenshot' && a.contentType === 'image/png') - let screenshotPath: string | null = null - if (screenshotAtt) { - const buf = screenshotAtt.body - ? Buffer.from(screenshotAtt.body, 'base64') - : screenshotAtt.path ? fs.readFileSync(screenshotAtt.path) : null - if (buf) { - fs.mkdirSync(debugDir, {recursive: true}) - screenshotPath = path.join(debugDir, `${key}.png`) - fs.writeFileSync(screenshotPath, buf) - } + const baseKey = `${slugify(suiteName)}-${slugify(spec.title)}` + + // Group by project so light and dark produce separate TestCase entries + const byProject = new Map() + for (const t of spec.tests) { + const proj = t.projectName + if (!byProject.has(proj)) byProject.set(proj, []) + byProject.get(proj)!.push(t) } - const prevPath = path.join(prevDir, `${key}.png`) - const prevScreenshotPath = fs.existsSync(prevPath) ? prevPath : null - - const diff = screenshotPath && prevScreenshotPath ? computeDiff(screenshotPath, prevScreenshotPath) : null - - cases.push({ - key, - label: `${suiteName} · ${spec.title}`, - passed, - durationMs, - screenshotPath, - prevScreenshotPath, - diff, - errorMessage, - }) + for (const [projectName, tests] of byProject) { + const isDark = projectName.endsWith('-dark') + const key = isDark ? `${baseKey}-dark` : baseKey + const label = isDark ? `${suiteName} · ${spec.title} (dark)` : `${suiteName} · ${spec.title}` + + // take the last non-skipped result (handles retries) + const allResults = tests.flatMap(t => t.results) + const result = allResults.filter(r => r.status !== 'skipped').at(-1) ?? allResults.at(-1) + if (!result) continue + + const passed = tests.every(t => t.status === 'expected' || t.status === 'flaky' || t.status === 'skipped') + const durationMs = result.duration + const errorMessage = !passed + ? (result.errors?.[0]?.message?.split('\n')[0] ?? 'test failed') + : null + + const screenshotAtt = result.attachments.find(a => a.name === 'screenshot' && a.contentType === 'image/png') + let screenshotPath: string | null = null + if (screenshotAtt) { + const buf = screenshotAtt.body + ? Buffer.from(screenshotAtt.body, 'base64') + : screenshotAtt.path ? fs.readFileSync(screenshotAtt.path) : null + if (buf) { + fs.mkdirSync(debugDir, {recursive: true}) + screenshotPath = path.join(debugDir, `${key}.png`) + fs.writeFileSync(screenshotPath, buf) + } + } + + const prevPath = path.join(prevDir, `${key}.png`) + const prevScreenshotPath = fs.existsSync(prevPath) ? prevPath : null + const diff = screenshotPath && prevScreenshotPath ? computeDiff(screenshotPath, prevScreenshotPath) : null + + cases.push({key, label, passed, durationMs, screenshotPath, prevScreenshotPath, diff, errorMessage}) + } } } return cases } +function parseStorybookScreenshots(): StorybookCase[] { + if (!fs.existsSync(storybookDir)) return [] + const cases: StorybookCase[] = [] + function walk(dir: string, rel: string) { + for (const entry of fs.readdirSync(dir, {withFileTypes: true})) { + const fullPath = path.join(dir, entry.name) + const relPath = rel ? `${rel}/${entry.name}` : entry.name + if (entry.isDirectory()) { + walk(fullPath, relPath) + } else if (entry.name.endsWith('.png')) { + const label = relPath.replace(/\.png$/, '').replaceAll('/', ' / ').replace(/-/g, ' ') + const prevPath = path.join(storybookPrevDir, relPath) + const prevScreenshotPath = fs.existsSync(prevPath) ? prevPath : null + const diff = prevScreenshotPath ? computeDiff(fullPath, prevScreenshotPath) : null + cases.push({relPath, label, screenshotPath: fullPath, prevScreenshotPath, diff}) + } + } + } + walk(storybookDir, '') + return cases.sort((a, b) => a.relPath.localeCompare(b.relPath)) +} + +function saveStorybookBaseline(cases: StorybookCase[]) { + fs.mkdirSync(storybookPrevDir, {recursive: true}) + let saved = 0 + for (const c of cases) { + if (!fs.existsSync(c.screenshotPath)) continue + const destDir = path.dirname(path.join(storybookPrevDir, c.relPath)) + fs.mkdirSync(destDir, {recursive: true}) + fs.copyFileSync(c.screenshotPath, path.join(storybookPrevDir, c.relPath)) + saved++ + } + console.log(`Storybook baseline saved: ${saved} screenshots to ${storybookPrevDir}/`) +} + function formatDuration(ms: number): string { return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s` } @@ -117,31 +167,32 @@ function escapeHtml(s: string): string { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') } -function buildHtml(cases: TestCase[], timestamp: string): string { +function buildHtml(cases: TestCase[], storybookCases: StorybookCase[], timestamp: string): string { const totalPassed = cases.filter(c => c.passed).length const totalFailed = cases.length - totalPassed const allPassed = totalFailed === 0 - const hasDiff = cases.some(c => c.diff !== null) + const hasDiff = cases.some(c => c.diff !== null) || storybookCases.some(c => c.diff !== null) + + const rel = (p: string) => path.relative(path.dirname(outputPath), p) - const cards = cases.map((c, i) => { + const e2eCards = cases.map((c, i) => { const badge = c.passed ? 'PASS' : 'FAIL' const error = c.errorMessage ? `
${escapeHtml(c.errorMessage)}
` : '' const deltaBadge = c.diff ? `Δ ${c.diff.pct.toFixed(1)}%` : '' - const rel = (p: string) => path.relative(path.dirname(outputPath), p) let visual: string if (c.screenshotPath && c.prevScreenshotPath) { visual = `
- current - baseline + current + baseline
BASELINE
NOW
` } else if (c.screenshotPath) { - visual = `
${c.label}
` + visual = `
${escapeHtml(c.label)}
` } else { visual = `
No screenshot
` } @@ -152,7 +203,38 @@ function buildHtml(cases: TestCase[], timestamp: string): string {
` }).join('\n') - return buildPage('Keybase Electron E2E Tests', allPassed, totalPassed, totalFailed, cases.length, hasDiff, timestamp, cards) + const sbOffset = cases.length + const sbCards = storybookCases.map((c, i) => { + const deltaBadge = c.diff + ? `Δ ${c.diff.pct.toFixed(1)}%` + : '' + + let visual: string + if (c.prevScreenshotPath) { + visual = `
+ current + baseline +
+
BASELINE
+
NOW
+
` + } else { + visual = `
${escapeHtml(c.label)}
` + } + + return `
+
${deltaBadge}${escapeHtml(c.label)}
+ ${visual} +
` + }).join('\n') + + const storybookSection = storybookCases.length > 0 + ? `
Storybook · ${storybookCases.length} stories
\n${sbCards}` + : '' + + const allCards = [e2eCards, storybookSection].filter(Boolean).join('\n') + + return buildPage('Keybase Electron E2E Tests', allPassed, totalPassed, totalFailed, cases.length, hasDiff, timestamp, allCards) } function saveBaseline(cases: TestCase[]) { @@ -183,20 +265,24 @@ function main() { process.exit(1) } + const storybookCases = parseStorybookScreenshots() + if (isSaveBaseline) { saveBaseline(cases) + saveStorybookBaseline(storybookCases) return } const timestamp = new Date().toLocaleString() - const html = buildHtml(cases, timestamp) + const html = buildHtml(cases, storybookCases, timestamp) const outDir = path.dirname(outputPath) if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, {recursive: true}) fs.writeFileSync(outputPath, html) - const withDiff = cases.filter(c => c.diff !== null).length + const withDiff = [...cases, ...storybookCases].filter(c => c.diff !== null).length const diffNote = withDiff > 0 ? `, ${withDiff} vs baseline` : '' - console.log(`Report written to ${outputPath} (${cases.filter(c => c.passed).length}/${cases.length} passed${diffNote})`) + const sbNote = storybookCases.length > 0 ? `, ${storybookCases.length} storybook stories` : '' + console.log(`Report written to ${outputPath} (${cases.filter(c => c.passed).length}/${cases.length} passed${diffNote}${sbNote})`) } main() diff --git a/shared/tests/e2e/generate-ios-report.mts b/shared/tests/e2e/generate-ios-report.mts index cb31786dc9b1..6d9edd2d99d9 100644 --- a/shared/tests/e2e/generate-ios-report.mts +++ b/shared/tests/e2e/generate-ios-report.mts @@ -161,16 +161,16 @@ function buildHtml(results: ScreenshotResult[], timestamp: string, title: string let visual: string if (r.screenshotPath && r.prevScreenshotPath) { visual = `
- current - baseline + current + baseline
BASELINE
NOW
` } else if (r.screenshotPath) { - visual = `
${r.name}
` + visual = `
${escapeHtml(r.name)}
` } else if (r.failureScreenshotPath) { - visual = `
failure
` + visual = `
failure
` } else { visual = `
No screenshot
` } @@ -207,8 +207,9 @@ ${sharedCss(allPassed)}
-

${title}

+

${title}

${passed} passed · ${failed} failed · ${total} total${hasDiff ? ' · vs baseline' : ''}${timestamp}
+
${cards}
${sliderScript()} @@ -220,7 +221,11 @@ export function sharedCss(allPassed: boolean): string { return `*{box-sizing:border-box;margin:0;padding:0} body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f0f0f0;color:#222} header{background:${allPassed ? '#1a7a3a' : '#c0392b'};color:#fff;padding:20px 28px} +.hdr-top{display:flex;align-items:center;gap:12px} h1{font-size:20px;font-weight:600} +#slideshow-btn{background:rgba(255,255,255,.2);border:1px solid rgba(255,255,255,.4);color:#fff;border-radius:5px;padding:4px 10px;font-size:14px;cursor:pointer;line-height:1} +#slideshow-btn:hover{background:rgba(255,255,255,.35)} +#slideshow-btn.active{background:rgba(255,255,255,.35)} .meta{margin-top:6px;font-size:13px;opacity:.85;display:flex;gap:16px;align-items:center} .ts{opacity:.7} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:20px 28px} @@ -244,6 +249,7 @@ h1{font-size:20px;font-weight:600} .grip{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border-radius:50%;width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:11px;box-shadow:0 1px 4px rgba(0,0,0,.4)} .lbl{position:absolute;bottom:8px;font-size:10px;font-weight:700;letter-spacing:.05em;padding:2px 7px;border-radius:3px;background:rgba(0,0,0,.55);color:#fff;pointer-events:none} .lbl-l{left:8px}.lbl-r{right:8px} +.section-hdr{grid-column:1/-1;font-size:15px;font-weight:600;padding:10px 0 4px;border-bottom:2px solid #ddd;margin-top:8px;color:#444} .expand-btn{position:absolute;bottom:10px;right:10px;background:rgba(0,0,0,.55);color:#fff;border:none;border-radius:5px;padding:5px 8px;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .15s;z-index:5} .compare:hover .expand-btn,.solo-wrap:hover .expand-btn{opacity:1} .overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.88);z-index:1000;align-items:center;justify-content:center} @@ -258,7 +264,18 @@ h1{font-size:20px;font-weight:600} .ov-compare .grip{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border-radius:50%;width:30px;height:30px;display:flex;align-items:center;justify-content:center;font-size:13px;box-shadow:0 1px 6px rgba(0,0,0,.5)} .ov-compare .lbl{position:absolute;bottom:10px;font-size:11px;font-weight:700;letter-spacing:.05em;padding:3px 9px;border-radius:3px;background:rgba(0,0,0,.55);color:#fff;pointer-events:none} .ov-compare .lbl-l{left:10px}.ov-compare .lbl-r{right:10px} -.ov-solo{display:block;max-height:90vh;max-width:90vw;width:auto;height:auto}` +@keyframes ov-fadein{from{opacity:0}to{opacity:1}} +.ov-solo{display:block;max-height:90vh;max-width:90vw;width:auto;height:auto;animation:ov-fadein .25s ease} +.ov-controls{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:1002;display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.55);border-radius:24px;padding:6px 14px} +.ov-controls button{background:none;border:none;color:#fff;font-size:18px;line-height:1;cursor:pointer;padding:2px 6px;border-radius:4px;opacity:.85} +.ov-controls button:hover{opacity:1;background:rgba(255,255,255,.15)} +.ov-counter{color:rgba(255,255,255,.8);font-size:12px;font-weight:600;letter-spacing:.04em;min-width:52px;text-align:center} +.filter-wrap{padding:10px 28px 14px} +#filter-input{width:100%;max-width:480px;padding:6px 12px;border-radius:6px;border:1px solid rgba(255,255,255,.3);background:rgba(255,255,255,.15);color:#fff;font-size:13px;outline:none} +#filter-input::placeholder{color:rgba(255,255,255,.6)} +#filter-input:focus{background:rgba(255,255,255,.25);border-color:rgba(255,255,255,.6)} +.card.hidden{display:none} +.section-hdr.hidden{display:none}` } export function sliderScript(): string { @@ -285,7 +302,17 @@ document.body.appendChild(overlay) const ovInner = overlay.querySelector('.ov-inner') const ovClose = overlay.querySelector('.ov-close') -function closeOverlay() { overlay.classList.remove('open') } +const ovControls = document.createElement('div') +ovControls.className = 'ov-controls' +ovControls.innerHTML = '' +ovControls.style.display = 'none' +document.body.appendChild(ovControls) +const ovPrev = ovControls.querySelector('.ov-prev') +const ovPlayPause = ovControls.querySelector('.ov-playpause') +const ovNext = ovControls.querySelector('.ov-next') +const ovCounter = ovControls.querySelector('.ov-counter') + +function closeOverlay() { overlay.classList.remove('open'); stopSlideshow() } ovClose.addEventListener('click', closeOverlay) overlay.addEventListener('click', e => { if (e.target === overlay) closeOverlay() }) document.addEventListener('keydown', e => { if (e.key === 'Escape') closeOverlay() }) @@ -333,6 +360,104 @@ document.querySelectorAll('.solo-wrap').forEach(el => { openSolo(el.querySelector('img').src) }) }) + +// ── slideshow ──────────────────────────────────────────────────────────────── +let slideshowTimer = null +let slideshowPlaying = false +let slideshowIdx = 0 +let slideshowImgs = [] +const slideshowBtn = document.getElementById('slideshow-btn') + +function slideshowImages() { + return Array.from(document.querySelectorAll('.grid .card:not(.hidden)')).flatMap(card => { + const img = card.querySelector('.img-after') ?? card.querySelector('img.solo') + return img ? [img.src] : [] + }) +} + +function showSlide(idx) { + if (!slideshowImgs.length) return + slideshowIdx = ((idx % slideshowImgs.length) + slideshowImgs.length) % slideshowImgs.length + openSolo(slideshowImgs[slideshowIdx]) + ovCounter.textContent = \`\${slideshowIdx + 1}/\${slideshowImgs.length}\` +} + +function scheduleNext() { + clearTimeout(slideshowTimer) + if (slideshowPlaying) slideshowTimer = setTimeout(() => { showSlide(slideshowIdx + 1); scheduleNext() }, 750) +} + +function setSlideshowPlaying(playing) { + slideshowPlaying = playing + ovPlayPause.textContent = playing ? '⏸' : '▶' + slideshowBtn.textContent = playing ? '⏸' : '▶' + slideshowBtn.classList.toggle('active', playing) + if (playing) scheduleNext() + else clearTimeout(slideshowTimer) +} + +function stopSlideshow() { + setSlideshowPlaying(false) + ovControls.style.display = 'none' +} + +ovPrev.addEventListener('click', e => { e.stopPropagation(); showSlide(slideshowIdx - 1); scheduleNext() }) +ovNext.addEventListener('click', e => { e.stopPropagation(); showSlide(slideshowIdx + 1); scheduleNext() }) +ovPlayPause.addEventListener('click', e => { e.stopPropagation(); setSlideshowPlaying(!slideshowPlaying) }) +document.addEventListener('keydown', e => { + if (!overlay.classList.contains('open') || !ovControls.style.display || ovControls.style.display === 'none') return + if (e.key === 'ArrowLeft') { e.preventDefault(); showSlide(slideshowIdx - 1); scheduleNext() } + else if (e.key === 'ArrowRight') { e.preventDefault(); showSlide(slideshowIdx + 1); scheduleNext() } + else if (e.key === ' ') { e.preventDefault(); setSlideshowPlaying(!slideshowPlaying) } +}) + +slideshowBtn.addEventListener('click', () => { + if (ovControls.style.display !== 'none') { + stopSlideshow() + closeOverlay() + } else { + slideshowImgs = slideshowImages() + if (!slideshowImgs.length) return + ovControls.style.display = 'flex' + showSlide(0) + setSlideshowPlaying(true) + } +}) + +// ── filter ─────────────────────────────────────────────────────────────────── +const filterInput = document.getElementById('filter-input') + +function applyFilter(q) { + const lq = q.toLowerCase() + document.querySelectorAll('.grid .card').forEach(card => { + const name = card.querySelector('.name')?.textContent?.toLowerCase() ?? '' + card.classList.toggle('hidden', lq.length > 0 && !name.includes(lq)) + }) + document.querySelectorAll('.section-hdr').forEach(hdr => { + let el = hdr.nextElementSibling + let anyVisible = false + while (el && !el.classList.contains('section-hdr')) { + if (el.classList.contains('card') && !el.classList.contains('hidden')) { anyVisible = true; break } + el = el.nextElementSibling + } + hdr.classList.toggle('hidden', lq.length > 0 && !anyVisible) + }) +} + +function syncUrl(q) { + const url = new URL(location.href) + if (q) url.searchParams.set('q', q) + else url.searchParams.delete('q') + history.replaceState(null, '', url) +} + +filterInput.addEventListener('input', () => { + applyFilter(filterInput.value) + syncUrl(filterInput.value) +}) + +const initQ = new URL(location.href).searchParams.get('q') ?? '' +if (initQ) { filterInput.value = initQ; applyFilter(initQ) } ` } diff --git a/shared/tracker/bio.stories.tsx b/shared/tracker/bio.stories.tsx new file mode 100644 index 000000000000..3cad69c1cfdd --- /dev/null +++ b/shared/tracker/bio.stories.tsx @@ -0,0 +1,122 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Bio from './bio' + +const meta: Meta = { + component: Bio, + title: 'Tracker/Bio', + args: { + blocked: false, + followThem: false, + followsYou: false, + hidFromFollowers: false, + inTracker: false, + username: 'alice', + }, +} +export default meta +type Story = StoryObj + +export const Basic: Story = { + args: { + username: 'alice', + fullname: 'Alice Smith', + bio: 'Software engineer and open source enthusiast.', + location: 'San Francisco, CA', + followersCount: 142, + followingCount: 37, + followThem: false, + followsYou: false, + }, +} + +export const FollowsYou: Story = { + args: { + username: 'bob', + fullname: 'Bob Johnson', + bio: 'Cryptographer and privacy advocate.', + followersCount: 500, + followingCount: 120, + followThem: false, + followsYou: true, + }, +} + +export const FollowEachOther: Story = { + args: { + username: 'carol', + fullname: 'Carol White', + bio: 'Building secure communications for everyone.', + location: 'New York, NY', + followersCount: 88, + followingCount: 55, + followThem: true, + followsYou: true, + }, +} + +export const YouFollowThem: Story = { + args: { + username: 'dave', + fullname: 'Dave Davis', + bio: 'Open source contributor.', + followersCount: 210, + followingCount: 90, + followThem: true, + followsYou: false, + }, +} + +export const InTracker: Story = { + args: { + username: 'eve', + fullname: 'Eve Brown', + bio: 'This is a longer bio that will get clamped when displayed inside the tracker popup because inTracker is true and lineClamp kicks in.', + location: 'Austin, TX — a long location that gets truncated too', + followersCount: 1024, + followingCount: 256, + followThem: false, + followsYou: true, + inTracker: true, + }, +} + +export const Blocked: Story = { + args: { + username: 'badactor', + fullname: 'Bad Actor', + blocked: true, + followThem: false, + followsYou: false, + }, +} + +export const HidFromFollowers: Story = { + args: { + username: 'quietuser', + fullname: 'Quiet User', + bio: 'Prefers to stay off the radar.', + hidFromFollowers: true, + followThem: false, + followsYou: false, + }, +} + +export const NoBioOrLocation: Story = { + args: { + username: 'minimal', + fullname: 'Minimal User', + followersCount: 0, + followingCount: 0, + followThem: false, + followsYou: false, + }, +} + +export const WithSbsDescription: Story = { + args: { + username: '', + sbsDescription: 'frank@proton.me on ProtonMail', + followThem: false, + followsYou: false, + }, +} diff --git a/shared/tsconfig.desktop.json b/shared/tsconfig.desktop.json index 656d2551129f..cf08a4d443eb 100644 --- a/shared/tsconfig.desktop.json +++ b/shared/tsconfig.desktop.json @@ -7,7 +7,7 @@ "outDir": "./.tsOuts/.tsOut-desktop/emit", "tsBuildInfoFile": "./.tsOuts/.tsOut-desktop/cache" }, - "include": ["./**/*.mts", "./**/*.mjs", "./**/*.ts", "./**/*.tsx"], + "include": ["./**/*.mts", "./**/*.mjs", "./**/*.ts", "./**/*.tsx", "./.storybook/**/*.ts", "./.storybook/**/*.tsx"], "exclude": [ "**/node_modules", "./desktop/dist", diff --git a/shared/unlock-folders/device-list.desktop.stories.tsx b/shared/unlock-folders/device-list.desktop.stories.tsx new file mode 100644 index 000000000000..61b9fc68b787 --- /dev/null +++ b/shared/unlock-folders/device-list.desktop.stories.tsx @@ -0,0 +1,61 @@ +import type {Meta, StoryObj} from '@storybook/react' +import type {UnlockFolderDevice} from './store' +import DeviceList from './device-list.desktop' + +const makeDevice = (overrides: Partial = {}): UnlockFolderDevice => ({ + deviceID: 'device-001' as UnlockFolderDevice['deviceID'], + name: 'My Device', + type: 'desktop', + ...overrides, +}) + +const meta: Meta = { + component: DeviceList, + title: 'UnlockFolders/DeviceList', + args: { + toPaperKeyInput: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const SingleDesktop: Story = { + args: { + devices: [makeDevice({name: 'work-laptop', type: 'desktop'})], + }, +} + +export const MultipleDevices: Story = { + args: { + devices: [ + makeDevice({deviceID: 'd1' as UnlockFolderDevice['deviceID'], name: 'work-laptop', type: 'desktop'}), + makeDevice({deviceID: 'd2' as UnlockFolderDevice['deviceID'], name: 'iPhone 15', type: 'mobile'}), + makeDevice({deviceID: 'd3' as UnlockFolderDevice['deviceID'], name: 'Paper key', type: 'backup'}), + ], + }, +} + +export const MobileOnly: Story = { + args: { + devices: [makeDevice({name: 'iPhone 15', type: 'mobile'})], + }, +} + +export const PaperKeyOnly: Story = { + args: { + devices: [makeDevice({name: 'My paper key backup', type: 'backup'})], + }, +} + +export const ManyDevices: Story = { + args: { + devices: [ + makeDevice({deviceID: 'd1' as UnlockFolderDevice['deviceID'], name: 'home-desktop', type: 'desktop'}), + makeDevice({deviceID: 'd2' as UnlockFolderDevice['deviceID'], name: 'work-laptop', type: 'desktop'}), + makeDevice({deviceID: 'd3' as UnlockFolderDevice['deviceID'], name: 'iPhone 15', type: 'mobile'}), + makeDevice({deviceID: 'd4' as UnlockFolderDevice['deviceID'], name: 'iPad Pro', type: 'mobile'}), + makeDevice({deviceID: 'd5' as UnlockFolderDevice['deviceID'], name: 'Paper key alpha', type: 'backup'}), + makeDevice({deviceID: 'd6' as UnlockFolderDevice['deviceID'], name: 'Paper key beta', type: 'backup'}), + ], + }, +} diff --git a/shared/unlock-folders/paper-key-input.desktop.stories.tsx b/shared/unlock-folders/paper-key-input.desktop.stories.tsx new file mode 100644 index 000000000000..adf44b893eaf --- /dev/null +++ b/shared/unlock-folders/paper-key-input.desktop.stories.tsx @@ -0,0 +1,28 @@ +import type {Meta, StoryObj} from '@storybook/react' +import PaperKeyInput from './paper-key-input.desktop' + +const meta: Meta = { + component: PaperKeyInput, + title: 'UnlockFolders/PaperKeyInput', + args: { + onBack: () => {}, + onContinue: () => {}, + waiting: false, + }, +} +export default meta +type Story = StoryObj + +export const Default: Story = {} + +export const WithError: Story = { + args: { + paperkeyError: 'Incorrect paper key. Please try again.', + }, +} + +export const Waiting: Story = { + args: { + waiting: true, + }, +} diff --git a/shared/unlock-folders/success.desktop.stories.tsx b/shared/unlock-folders/success.desktop.stories.tsx new file mode 100644 index 000000000000..42dad673212d --- /dev/null +++ b/shared/unlock-folders/success.desktop.stories.tsx @@ -0,0 +1,14 @@ +import type {Meta, StoryObj} from '@storybook/react' +import Success from './success.desktop' + +const meta: Meta = { + component: Success, + title: 'UnlockFolders/Success', + args: { + onClose: () => {}, + }, +} +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/shared/wallets/markdown-memo.stories.tsx b/shared/wallets/markdown-memo.stories.tsx new file mode 100644 index 000000000000..ac1da56dc0d5 --- /dev/null +++ b/shared/wallets/markdown-memo.stories.tsx @@ -0,0 +1,39 @@ +import type {Meta, StoryObj} from '@storybook/react' +import MarkdownMemo from './markdown-memo' + +const meta: Meta = { + component: MarkdownMemo, + title: 'Wallets/MarkdownMemo', +} +export default meta +type Story = StoryObj + +export const PlainText: Story = { + args: {memo: 'Payment for coffee'}, +} + +export const LongText: Story = { + args: { + memo: 'This is a longer payment memo that goes into more detail about the transaction and why it was made.', + }, +} + +export const WithMarkdownBold: Story = { + args: {memo: 'Payment for **coffee** and *snacks*'}, +} + +export const WithMarkdownLink: Story = { + args: {memo: 'See invoice at https://example.com/invoice/123'}, +} + +export const EmptyMemo: Story = { + // renders null when memo is empty + args: {memo: ''}, +} + +export const HideDivider: Story = { + args: { + memo: 'Payment without divider', + hideDivider: true, + }, +} diff --git a/shared/wallets/remove-account.stories.tsx b/shared/wallets/remove-account.stories.tsx new file mode 100644 index 000000000000..16e20d733ae2 --- /dev/null +++ b/shared/wallets/remove-account.stories.tsx @@ -0,0 +1,33 @@ +import type {Meta, StoryObj} from '@storybook/react' +import RemoveAccount from './remove-account' + +const meta: Meta = { + component: RemoveAccount, + title: 'Wallets/RemoveAccount', +} +export default meta +type Story = StoryObj + +export const WithBalance: Story = { + args: { + accountID: 'GABC1234', + name: 'My Savings', + balanceDescription: '125.0000000 XLM', + }, +} + +export const ZeroBalance: Story = { + args: { + accountID: 'GABC5678', + name: 'Empty Wallet', + balanceDescription: '0.0000000 XLM', + }, +} + +export const LongName: Story = { + args: { + accountID: 'GABC9999', + name: 'My Very Long Wallet Account Name That Might Wrap', + balanceDescription: '9999.9999999 XLM', + }, +} diff --git a/shared/wallets/wallet-popup.stories.tsx b/shared/wallets/wallet-popup.stories.tsx new file mode 100644 index 000000000000..9c61cb3079a7 --- /dev/null +++ b/shared/wallets/wallet-popup.stories.tsx @@ -0,0 +1,39 @@ +import type {Meta, StoryObj} from '@storybook/react' +import * as Kb from '@/common-adapters' +import WalletPopup from './wallet-popup' + +const meta: Meta = { + component: WalletPopup, + title: 'Wallets/WalletPopup', + args: { + children: ( + + Wallet content here + Some descriptive text about this wallet action. + + ), + }, +} +export default meta +type Story = StoryObj + +export const NoButtons: Story = {} + +export const WithButtons: Story = { + args: { + bottomButtons: [ + {}} />, + {}} />, + ], + }, +} + +export const ColumnButtons: Story = { + args: { + buttonBarDirection: 'column', + bottomButtons: [ + {}} />, + {}} />, + ], + }, +} diff --git a/shared/yarn.lock b/shared/yarn.lock index a40c8e63f1aa..eb20aa1fb0a3 100644 --- a/shared/yarn.lock +++ b/shared/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.4.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.5.0.tgz#b5b71a25a4d16afa2482592ddfa62fccc60bc7d1" + integrity sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q== + "@asamuzakjp/css-color@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz#cc42f5b85c593f79f1fa4f25d2b9b321e61d1794" @@ -13,7 +18,7 @@ "@csstools/css-tokenizer" "^3.0.3" lru-cache "^10.4.3" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.20.0", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.20.0", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== @@ -27,7 +32,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== -"@babel/core@7.29.7", "@babel/core@^7.20.0", "@babel/core@^7.23.9", "@babel/core@^7.24.4", "@babel/core@^7.25.2", "@babel/core@^7.27.4": +"@babel/core@7.29.7", "@babel/core@^7.18.9", "@babel/core@^7.20.0", "@babel/core@^7.23.9", "@babel/core@^7.24.4", "@babel/core@^7.25.2", "@babel/core@^7.27.4", "@babel/core@^7.28.0": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== @@ -1062,7 +1067,7 @@ "@babel/parser" "^7.29.7" "@babel/types" "^7.29.7" -"@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7": +"@babel/traverse@^7.18.9", "@babel/traverse@^7.28.0", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d" integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw== @@ -1075,7 +1080,7 @@ "@babel/types" "^7.29.7" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.26.0", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.26.0", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.4.4": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== @@ -1217,6 +1222,14 @@ "@emnapi/wasi-threads" "1.2.1" tslib "^2.4.0" +"@emnapi/core@1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.9.2.tgz#3870265ecffc7352d01ead62d8d83d8358a2d034" + integrity sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + "@emnapi/runtime@1.10.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" @@ -1224,6 +1237,13 @@ dependencies: tslib "^2.4.0" +"@emnapi/runtime@1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.9.2.tgz#8b469a3db160817cadb1de9050211a9d1ea84fa2" + integrity sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw== + dependencies: + tslib "^2.4.0" + "@emnapi/wasi-threads@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" @@ -1236,6 +1256,136 @@ resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA== +"@esbuild/aix-ppc64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz#82b74f92aa78d720b714162939fb248c90addf53" + integrity sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg== + +"@esbuild/android-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz#f78cb8a3121fc205a53285adb24972db385d185d" + integrity sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ== + +"@esbuild/android-arm@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.7.tgz#593e10a1450bbfcac6cb321f61f468453bac209d" + integrity sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ== + +"@esbuild/android-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.7.tgz#453143d073326033d2d22caf9e48de4bae274b07" + integrity sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg== + +"@esbuild/darwin-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz#6f23000fb9b40b7e04b7d0606c0693bd0632f322" + integrity sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw== + +"@esbuild/darwin-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz#27393dd18bb1263c663979c5f1576e00c2d024be" + integrity sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ== + +"@esbuild/freebsd-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz#22e4638fa502d1c0027077324c97640e3adf3a62" + integrity sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w== + +"@esbuild/freebsd-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz#9224b8e4fea924ce2194e3efc3e9aebf822192d6" + integrity sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ== + +"@esbuild/linux-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz#4f5d1c27527d817b35684ae21419e57c2bda0966" + integrity sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A== + +"@esbuild/linux-arm@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz#b9e9d070c8c1c0449cf12b20eac37d70a4595921" + integrity sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA== + +"@esbuild/linux-ia32@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz#3f80fb696aa96051a94047f35c85b08b21c36f9e" + integrity sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg== + +"@esbuild/linux-loong64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz#9be1f2c28210b13ebb4156221bba356fe1675205" + integrity sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q== + +"@esbuild/linux-mips64el@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz#4ab5ee67a3dfcbcb5e8fd7883dae6e735b1163b8" + integrity sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw== + +"@esbuild/linux-ppc64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz#dac78c689f6499459c4321e5c15032c12307e7ea" + integrity sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ== + +"@esbuild/linux-riscv64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz#050f7d3b355c3a98308e935bc4d6325da91b0027" + integrity sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ== + +"@esbuild/linux-s390x@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz#d61f715ce61d43fe5844ad0d8f463f88cbe4fef6" + integrity sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw== + +"@esbuild/linux-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz#ca8e1aa478fc8209257bf3ac8f79c4dc2982f32a" + integrity sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA== + +"@esbuild/netbsd-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz#1650f2c1b948deeb3ef948f2fc30614723c09690" + integrity sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w== + +"@esbuild/netbsd-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz#65772ab342c4b3319bf0705a211050aac1b6e320" + integrity sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw== + +"@esbuild/openbsd-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz#37ed7cfa66549d7955852fce37d0c3de4e715ea1" + integrity sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A== + +"@esbuild/openbsd-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz#01bf3d385855ef50cb33db7c4b52f957c34cd179" + integrity sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg== + +"@esbuild/openharmony-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz#6c1f94b34086599aabda4eac8f638294b9877410" + integrity sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw== + +"@esbuild/sunos-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz#4b0dd17ae0a6941d2d0fd35a906392517071a90d" + integrity sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA== + +"@esbuild/win32-arm64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz#34193ab5565d6ff68ca928ac04be75102ccb2e77" + integrity sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA== + +"@esbuild/win32-ia32@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz#eb67f0e4482515d8c1894ede631c327a4da9fc4d" + integrity sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw== + +"@esbuild/win32-x64@0.27.7": + version "0.27.7" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz#8fe30b3088b89b4873c3a6cc87597ae3920c0a8b" + integrity sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg== + "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" @@ -2315,6 +2465,214 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@oxc-parser/binding-android-arm-eabi@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.127.0.tgz#b75e796249ee22f632e40e942746c4bf648cee92" + integrity sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ== + +"@oxc-parser/binding-android-arm64@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.127.0.tgz#e264467fe39f80018f62fa0dae82db0b80260444" + integrity sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg== + +"@oxc-parser/binding-darwin-arm64@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.127.0.tgz#0576d35109c00dcc6277200ba2eca7b47e07f1b1" + integrity sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg== + +"@oxc-parser/binding-darwin-x64@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.127.0.tgz#efa1ba49075aa318ff540a1c2f8a442017417206" + integrity sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw== + +"@oxc-parser/binding-freebsd-x64@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.127.0.tgz#817ba3c508d751d94d6e6fd86af69ddaa27da531" + integrity sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA== + +"@oxc-parser/binding-linux-arm-gnueabihf@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.127.0.tgz#b1c3096c654771998480316ef10d1e5d29edc79b" + integrity sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ== + +"@oxc-parser/binding-linux-arm-musleabihf@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.127.0.tgz#c44a8f10e6c903685825aebf1289fc2086aed61e" + integrity sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g== + +"@oxc-parser/binding-linux-arm64-gnu@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.127.0.tgz#61c245abfab6f63045915b5c9cfa7d335ad7c440" + integrity sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ== + +"@oxc-parser/binding-linux-arm64-musl@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.127.0.tgz#358bbd90e5c85b6c35125f5a6ff084e09b694c04" + integrity sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA== + +"@oxc-parser/binding-linux-ppc64-gnu@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.127.0.tgz#b7ea7b51bf54db4c42819187f760e069d433dac3" + integrity sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ== + +"@oxc-parser/binding-linux-riscv64-gnu@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.127.0.tgz#3a3b10d160988df50bbbcd631c6af39de3dd451d" + integrity sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ== + +"@oxc-parser/binding-linux-riscv64-musl@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.127.0.tgz#3787d37e1d0a15ee239f51610298500321b31730" + integrity sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g== + +"@oxc-parser/binding-linux-s390x-gnu@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.127.0.tgz#b71a16cbba115a4696498f9149bc54cc4e1df9cd" + integrity sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q== + +"@oxc-parser/binding-linux-x64-gnu@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.127.0.tgz#71527dd0284ba727d35a93c841c91192af3ebdec" + integrity sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ== + +"@oxc-parser/binding-linux-x64-musl@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.127.0.tgz#16830afa4b001f349cebb93e12b278e72601cb3f" + integrity sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg== + +"@oxc-parser/binding-openharmony-arm64@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.127.0.tgz#a41c71d249cb597dc357038eb1cbe3ce732453f8" + integrity sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ== + +"@oxc-parser/binding-wasm32-wasi@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.127.0.tgz#b1efcdb433b30ed4a3ad912fa03da3834bd4845d" + integrity sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ== + dependencies: + "@emnapi/core" "1.9.2" + "@emnapi/runtime" "1.9.2" + "@napi-rs/wasm-runtime" "^1.1.4" + +"@oxc-parser/binding-win32-arm64-msvc@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.127.0.tgz#b62b5e328126323d41ae1ee7adc95537c4c4423a" + integrity sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw== + +"@oxc-parser/binding-win32-ia32-msvc@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.127.0.tgz#dac30de6971dbe63aa5722be9a4cc070fd3c650e" + integrity sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw== + +"@oxc-parser/binding-win32-x64-msvc@0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.127.0.tgz#a2df879b0803f72b350a7567365cee5b8978edf0" + integrity sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w== + +"@oxc-project/types@^0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.127.0.tgz#8374fcdfb4a641861218daa5700c447c00b66663" + integrity sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ== + +"@oxc-resolver/binding-android-arm-eabi@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.20.0.tgz#6040f744ee2350f1eee744ea7b16092653c6e221" + integrity sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg== + +"@oxc-resolver/binding-android-arm64@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.20.0.tgz#6bf3c61f1245c84a13602f18d6326f42306e69b0" + integrity sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q== + +"@oxc-resolver/binding-darwin-arm64@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.20.0.tgz#b6fdb43ac0b67bf89c09e2865b848759bc1bcbb6" + integrity sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ== + +"@oxc-resolver/binding-darwin-x64@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.20.0.tgz#fc065954554eca454fa50ce3e7643f33ebd14977" + integrity sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg== + +"@oxc-resolver/binding-freebsd-x64@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.20.0.tgz#79d9c0262d959fb8eff09dfb2079184132607482" + integrity sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ== + +"@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.20.0.tgz#67d47c22f31f98d42597b258b9daf6acab441bf3" + integrity sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg== + +"@oxc-resolver/binding-linux-arm-musleabihf@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.20.0.tgz#da835d77174c74c49a371a0409c8a6b802bf814e" + integrity sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg== + +"@oxc-resolver/binding-linux-arm64-gnu@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.20.0.tgz#06123d54159307a9731e70f898e9b751c9670560" + integrity sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg== + +"@oxc-resolver/binding-linux-arm64-musl@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.20.0.tgz#a9df55e94243526e92a1c646f11e844d243aa2a3" + integrity sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw== + +"@oxc-resolver/binding-linux-ppc64-gnu@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.20.0.tgz#45559c56a6c8cb9ea8cafffca03c79d2a04097e1" + integrity sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ== + +"@oxc-resolver/binding-linux-riscv64-gnu@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.20.0.tgz#8687b6e6cf3592e3cd76a316580d7fcd146e3f1d" + integrity sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw== + +"@oxc-resolver/binding-linux-riscv64-musl@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.20.0.tgz#90ee1c47c2e0cf2c7ed6f542544e4c528d82f978" + integrity sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg== + +"@oxc-resolver/binding-linux-s390x-gnu@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.20.0.tgz#3fe4d600f79978e766a746405bfa54fe05f27a5c" + integrity sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g== + +"@oxc-resolver/binding-linux-x64-gnu@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.20.0.tgz#bda339de35865aa54f28862e55829607c8d4e923" + integrity sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g== + +"@oxc-resolver/binding-linux-x64-musl@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.20.0.tgz#c0d5c660faee13a916162f8d83be50015cb72bb5" + integrity sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ== + +"@oxc-resolver/binding-openharmony-arm64@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.20.0.tgz#f4fe37c4c5cf0804be3af07a53684780d5f9d582" + integrity sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ== + +"@oxc-resolver/binding-wasm32-wasi@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.20.0.tgz#3fbbaf065df34b44d45d8391250aeabf220acf5e" + integrity sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg== + dependencies: + "@emnapi/core" "1.10.0" + "@emnapi/runtime" "1.10.0" + "@napi-rs/wasm-runtime" "^1.1.4" + +"@oxc-resolver/binding-win32-arm64-msvc@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.20.0.tgz#00e204aac6dddd1fa909d80aa53be6bee10e6b91" + integrity sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA== + +"@oxc-resolver/binding-win32-x64-msvc@11.20.0": + version "11.20.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.20.0.tgz#56d62047fa2ae7c2695bc901ff16303c8b6429c7" + integrity sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw== + "@peculiar/asn1-cms@^2.6.0", "@peculiar/asn1-cms@^2.7.0": version "2.7.0" resolved "https://registry.yarnpkg.com/@peculiar/asn1-cms/-/asn1-cms-2.7.0.tgz#8e0eb656f4fc85f7c621dd442fa2d298faa84984" @@ -2921,6 +3279,96 @@ dependencies: "@sinonjs/commons" "^3.0.1" +"@storybook/builder-webpack5@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-10.4.1.tgz#e5705713f21b73b186d5a10d42d016f40407ed91" + integrity sha512-3Ah4jUjg8nEms/5JV6odtQj9+pQ1DT/04s/V6dZKThGdl85YTrYUZV5OTgbNxYbmQn/TwpWWjQlcW8ulpo2WBw== + dependencies: + "@storybook/core-webpack" "10.4.1" + case-sensitive-paths-webpack-plugin "^2.4.0" + cjs-module-lexer "^1.2.3" + css-loader "^7.1.2" + es-module-lexer "^1.5.0" + fork-ts-checker-webpack-plugin "^9.1.0" + html-webpack-plugin "^5.5.0" + magic-string "^0.30.5" + style-loader "^4.0.0" + terser-webpack-plugin "^5.3.17" + ts-dedent "^2.0.0" + webpack "5" + webpack-dev-middleware "^6.1.2" + webpack-hot-middleware "^2.25.1" + webpack-virtual-modules "^0.6.0" + +"@storybook/core-webpack@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-10.4.1.tgz#9ddef26b8afacf96cb36deee4012632c9bd78956" + integrity sha512-Wert/4ou5WRl8WYWWS8bBW7Lxa/ASMEuQ3EVuG3SITAtPNvKDKqTFBjZLx9eJSefkX6fJ3yG85FFUOPsv6GemQ== + dependencies: + ts-dedent "^2.0.0" + +"@storybook/global@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@storybook/global/-/global-5.0.0.tgz#b793d34b94f572c1d7d9e0f44fac4e0dbc9572ed" + integrity sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ== + +"@storybook/icons@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-2.0.2.tgz#765b49b902a2bdf6f3a5a1aa8dc0cdcd3c55c5b5" + integrity sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw== + +"@storybook/preset-react-webpack@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-10.4.1.tgz#ec3e3b1216d06e53b375ce7a9b5594f6c870184a" + integrity sha512-uAR/C/oDZYhReaYpD4Rd5S4VWcXP2XO8+BwXwanKt4UHbYfOw7AQgBTeZ/6Wns/0xIXhOoA1rxO5TA2wDLUjLA== + dependencies: + "@storybook/core-webpack" "10.4.1" + "@storybook/react-docgen-typescript-plugin" "1.0.6--canary.9.0c3f3b7.0" + "@types/semver" "^7.7.1" + magic-string "^0.30.5" + react-docgen "^7.1.1" + resolve "^1.22.8" + semver "^7.7.3" + tsconfig-paths "^4.2.0" + webpack "5" + +"@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0": + version "1.0.6--canary.9.0c3f3b7.0" + resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.6--canary.9.0c3f3b7.0.tgz#7f10f3c641f32e4513a8b6ffb5036933e7059534" + integrity sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q== + dependencies: + debug "^4.1.1" + endent "^2.0.1" + find-cache-dir "^3.3.1" + flat-cache "^3.0.4" + micromatch "^4.0.2" + react-docgen-typescript "^2.2.2" + tslib "^2.0.0" + +"@storybook/react-dom-shim@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.4.1.tgz#18836217a1ba5fc4040878a6e33bc0fe4dad5e4a" + integrity sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ== + +"@storybook/react-webpack5@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@storybook/react-webpack5/-/react-webpack5-10.4.1.tgz#32631dfa70d508ff7d6da98b8d5ec173252f91a9" + integrity sha512-2jF231DrEk70I8+wVakCnKtpweGFNfxdaov883Rve0TFvhxZs42Y9PpKzSf4rusvSrWc9jdWuJ2k7ERbS50MLg== + dependencies: + "@storybook/builder-webpack5" "10.4.1" + "@storybook/preset-react-webpack" "10.4.1" + "@storybook/react" "10.4.1" + +"@storybook/react@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-10.4.1.tgz#078926c18d29a7af51a16588170953c8beea0c45" + integrity sha512-WuYz4NaUk4gmFAMliSpCbV8w6jP5OY9juBfw1huwzu2S/k5FhnVXwmrUaL0fmf3Bq/7NgkzmBBbZr6I6LuHayQ== + dependencies: + "@storybook/global" "^5.0.0" + "@storybook/react-dom-shim" "10.4.1" + react-docgen "^8.0.2" + react-docgen-typescript "^2.2.2" + "@testing-library/dom@10.4.1": version "10.4.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95" @@ -2935,6 +3383,18 @@ picocolors "1.1.1" pretty-format "^27.0.2" +"@testing-library/jest-dom@^6.9.1": + version "6.9.1" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz#7613a04e146dd2976d24ddf019730d57a89d56c2" + integrity sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + picocolors "^1.1.1" + redent "^3.0.0" + "@testing-library/react@16.3.2": version "16.3.2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.2.tgz#672883b7acb8e775fc0492d9e9d25e06e89786d0" @@ -2942,6 +3402,11 @@ dependencies: "@babel/runtime" "^7.12.5" +"@testing-library/user-event@^14.6.1": + version "14.6.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" + integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== + "@tybys/wasm-util@^0.10.1": version "0.10.2" resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz#12b3a1b33db1f9cad4ddff1f604ab7dd00bf464e" @@ -2954,7 +3419,7 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== -"@types/babel__core@^7.20.5": +"@types/babel__core@^7.18.0", "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== @@ -2980,7 +3445,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*": +"@types/babel__traverse@*", "@types/babel__traverse@^7.18.0", "@types/babel__traverse@^7.20.7": version "7.28.0" resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== @@ -3002,6 +3467,14 @@ dependencies: "@types/node" "*" +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + "@types/connect-history-api-fallback@^1.5.4": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" @@ -3017,6 +3490,16 @@ dependencies: "@types/node" "*" +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + +"@types/doctrine@^0.0.9": + version "0.0.9" + resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.9.tgz#d86a5f452a15e3e3113b99e39616a9baa0f9863f" + integrity sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA== + "@types/emscripten@^1.41.5": version "1.41.5" resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.41.5.tgz#5670e4b52b098691cb844b84ee48c9176699b68d" @@ -3194,11 +3677,21 @@ dependencies: csstype "^3.2.2" +"@types/resolve@^1.20.2": + version "1.20.6" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.6.tgz#e6e60dad29c2c8c206c026e6dd8d6d1bdda850b8" + integrity sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ== + "@types/retry@0.12.2": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/semver@^7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + "@types/send@*": version "1.2.1" resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" @@ -3594,6 +4087,40 @@ resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz#72da0da48d72b1e87831b9c0308931d3f4669027" integrity sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA== +"@vitest/expect@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" + integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + tinyrainbow "^2.0.0" + +"@vitest/pretty-format@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4" + integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/spy@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599" + integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== + dependencies: + tinyspy "^4.0.3" + +"@vitest/utils@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea" + integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== + dependencies: + "@vitest/pretty-format" "3.2.4" + loupe "^3.1.4" + tinyrainbow "^2.0.0" + "@vscode/sudo-prompt@^9.0.0": version "9.3.2" resolved "https://registry.yarnpkg.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz#692ba38df40bd3502ccc4e9f099fbbaedbd5f04e" @@ -3720,6 +4247,11 @@ "@webassemblyjs/ast" "1.14.1" "@xtuc/long" "4.2.2" +"@webcontainer/env@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@webcontainer/env/-/env-1.1.1.tgz#23021b2bb24befeeef53dba8996d1886b7016515" + integrity sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng== + "@xmldom/xmldom@^0.8.8": version "0.8.13" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.13.tgz#00d1dd940b218dff2e49309d410d8bb212159225" @@ -3853,7 +4385,7 @@ ansi-fragments@^0.2.1: slice-ansi "^2.0.0" strip-ansi "^5.0.0" -ansi-html-community@^0.0.8: +ansi-html-community@0.0.8, ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== @@ -3934,6 +4466,11 @@ aria-query@5.3.0: dependencies: dequal "^2.0.3" +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -4036,6 +4573,18 @@ asn1js@^3.0.6: pvutils "^1.1.5" tslib "^2.8.1" +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +ast-types@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.16.1.tgz#7a9da1617c9081bc121faafe91711b4c8bb81da2" + integrity sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg== + dependencies: + tslib "^2.0.1" + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -4506,6 +5055,22 @@ caniuse-lite@^1.0.30001782: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz#238887ddf5fcfc8c36d872394d0a78a517312a72" integrity sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA== +case-sensitive-paths-webpack-plugin@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== + +chai@^5.2.0: + version "5.3.3" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@^2.0.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4528,6 +5093,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +check-error@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5" + integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== + chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -4543,6 +5113,13 @@ chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" +chokidar@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chrome-launcher@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz#4e6404e32200095fdce7f6a1e1004f9bd36fa5da" @@ -4584,6 +5161,11 @@ ci-info@^4.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== +cjs-module-lexer@^1.2.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + cjs-module-lexer@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca" @@ -4731,6 +5313,11 @@ commander@^9.4.0, commander@^9.4.1: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -4820,6 +5407,16 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cosmiconfig@^8.2.0: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + cosmiconfig@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.1.tgz#df110631a8547b5d1a98915271986f06e3011379" @@ -4861,7 +5458,7 @@ css-in-js-utils@^3.1.0: dependencies: hyphenate-style-name "^1.0.3" -css-loader@7.1.4: +css-loader@7.1.4, css-loader@^7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.4.tgz#8f6bf9f8fc8cbef7d2ef6e80acc6545eaefa90b1" integrity sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw== @@ -4891,6 +5488,11 @@ css-what@^6.0.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea" integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -4990,17 +5592,27 @@ decode-uri-component@^0.4.1: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + dedent@^1.6.0: version "1.7.2" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9" integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA== +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.3.0, deepmerge@^4.3.1: +deepmerge@^4.2.2, deepmerge@^4.3.0, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -5109,11 +5721,23 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dom-accessibility-api@^0.5.9: version "0.5.16" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -5244,6 +5868,15 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +endent@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/endent/-/endent-2.1.0.tgz#5aaba698fb569e5e18e69e1ff7a28ff35373cd88" + integrity sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w== + dependencies: + dedent "^0.7.0" + fast-json-parse "^1.0.3" + objectorarray "^1.0.5" + enhanced-resolve@^5.22.0: version "5.22.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz#43c5caad657c6fce58fc6142e5ca6fa8528ed460" @@ -5396,6 +6029,11 @@ es-iterator-helpers@^1.2.1: iterator.prototype "^1.1.5" math-intrinsics "^1.1.0" +es-module-lexer@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + es-module-lexer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz#1dfcbb5ea3bbfb63f28e1fc3676c3676d1c9624c" @@ -5434,6 +6072,38 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" +"esbuild@^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0": + version "0.27.7" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.7.tgz#bcadce22b2f3fd76f257e3a64f83a64986fea11f" + integrity sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.7" + "@esbuild/android-arm" "0.27.7" + "@esbuild/android-arm64" "0.27.7" + "@esbuild/android-x64" "0.27.7" + "@esbuild/darwin-arm64" "0.27.7" + "@esbuild/darwin-x64" "0.27.7" + "@esbuild/freebsd-arm64" "0.27.7" + "@esbuild/freebsd-x64" "0.27.7" + "@esbuild/linux-arm" "0.27.7" + "@esbuild/linux-arm64" "0.27.7" + "@esbuild/linux-ia32" "0.27.7" + "@esbuild/linux-loong64" "0.27.7" + "@esbuild/linux-mips64el" "0.27.7" + "@esbuild/linux-ppc64" "0.27.7" + "@esbuild/linux-riscv64" "0.27.7" + "@esbuild/linux-s390x" "0.27.7" + "@esbuild/linux-x64" "0.27.7" + "@esbuild/netbsd-arm64" "0.27.7" + "@esbuild/netbsd-x64" "0.27.7" + "@esbuild/openbsd-arm64" "0.27.7" + "@esbuild/openbsd-x64" "0.27.7" + "@esbuild/openharmony-arm64" "0.27.7" + "@esbuild/sunos-x64" "0.27.7" + "@esbuild/win32-arm64" "0.27.7" + "@esbuild/win32-ia32" "0.27.7" + "@esbuild/win32-x64" "0.27.7" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -5645,7 +6315,7 @@ espree@^11.2.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^5.0.1" -esprima@^4.0.0: +esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -5987,6 +6657,11 @@ fast-glob@^3.2.9, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.8" +fast-json-parse@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" + integrity sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw== + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -6146,6 +6821,15 @@ find-babel-config@^2.1.1: dependencies: json5 "^2.2.3" +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -6176,6 +6860,15 @@ find-yarn-workspace-root@^2.0.0: dependencies: micromatch "^4.0.2" +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + flat-cache@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" @@ -6231,6 +6924,24 @@ foreground-child@^3.1.0: cross-spawn "^7.0.6" signal-exit "^4.0.1" +fork-ts-checker-webpack-plugin@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz#433481c1c228c56af111172fcad7df79318c915a" + integrity sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q== + dependencies: + "@babel/code-frame" "^7.16.7" + chalk "^4.1.2" + chokidar "^4.0.1" + cosmiconfig "^8.2.0" + deepmerge "^4.2.2" + fs-extra "^10.0.0" + memfs "^3.4.1" + minimatch "^3.0.4" + node-abort-controller "^3.0.1" + schema-utils "^3.1.1" + semver "^7.3.5" + tapable "^2.2.1" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -6259,6 +6970,11 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-monkey@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.1.0.tgz#632aa15a20e71828ed56b24303363fb1414e5997" + integrity sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -6419,7 +7135,7 @@ glob@^13.0.0, glob@^13.0.2: minipass "^7.1.3" path-scurry "^2.0.2" -glob@^7.1.4: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -6622,7 +7338,7 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" -html-webpack-plugin@5.6.7: +html-webpack-plugin@5.6.7, html-webpack-plugin@^5.5.0: version "5.6.7" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz#429bab4e12abf3c07e1c608886608e2df2c06b11" integrity sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw== @@ -6800,6 +7516,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -7743,7 +8464,7 @@ json-stable-stringify@^1.0.2: jsonify "^0.0.1" object-keys "^1.1.1" -json5@^2.1.2, json5@^2.2.3: +json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -7784,7 +8505,7 @@ junk@^4.0.1: resolved "https://registry.yarnpkg.com/junk/-/junk-4.0.1.tgz#7ee31f876388c05177fe36529ee714b07b50fbed" integrity sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ== -keyv@^4.5.4: +keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -8013,6 +8734,11 @@ lottie-web@5.13.0: resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.13.0.tgz#441d3df217cc8ba302338c3f168e1a3af0f221d3" integrity sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ== +loupe@^3.1.0, loupe@^3.1.4: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -8042,6 +8768,20 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +magic-string@^0.30.5: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -8076,6 +8816,13 @@ media-typer@^1.1.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== +memfs@^3.4.1, memfs@^3.4.12: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + memfs@^4.43.1: version "4.57.2" resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.57.2.tgz#5f74e977c9a14681ea10d427b3ce5d7db5f817e7" @@ -8343,6 +9090,13 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== +mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@^3.0.0, mime-types@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" @@ -8350,13 +9104,6 @@ mime-types@^3.0.0, mime-types@^3.0.1: dependencies: mime-db "^1.54.0" -mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -8377,6 +9124,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -8527,6 +9279,11 @@ nocache@^3.0.1: resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.4.tgz#5b37a56ec6e09fc7d401dceaed2eab40c8bfdf79" integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + node-exports-info@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/node-exports-info/-/node-exports-info-1.6.0.tgz#1aedafb01a966059c9a5e791a94a94d93f5c2a13" @@ -8682,6 +9439,11 @@ object.values@^1.1.6, object.values@^1.2.1: define-properties "^1.2.1" es-object-atoms "^1.0.0" +objectorarray@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5" + integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg== + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -8727,7 +9489,7 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^10.0.3: +open@^10.0.3, open@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/open/-/open-10.2.0.tgz#b9d855be007620e80b6fb05fac98141fe62db73c" integrity sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA== @@ -8800,6 +9562,59 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" +oxc-parser@^0.127.0: + version "0.127.0" + resolved "https://registry.yarnpkg.com/oxc-parser/-/oxc-parser-0.127.0.tgz#bb14600f5c59fb6b1fbac0ab6ff2cd3495a6df1d" + integrity sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA== + dependencies: + "@oxc-project/types" "^0.127.0" + optionalDependencies: + "@oxc-parser/binding-android-arm-eabi" "0.127.0" + "@oxc-parser/binding-android-arm64" "0.127.0" + "@oxc-parser/binding-darwin-arm64" "0.127.0" + "@oxc-parser/binding-darwin-x64" "0.127.0" + "@oxc-parser/binding-freebsd-x64" "0.127.0" + "@oxc-parser/binding-linux-arm-gnueabihf" "0.127.0" + "@oxc-parser/binding-linux-arm-musleabihf" "0.127.0" + "@oxc-parser/binding-linux-arm64-gnu" "0.127.0" + "@oxc-parser/binding-linux-arm64-musl" "0.127.0" + "@oxc-parser/binding-linux-ppc64-gnu" "0.127.0" + "@oxc-parser/binding-linux-riscv64-gnu" "0.127.0" + "@oxc-parser/binding-linux-riscv64-musl" "0.127.0" + "@oxc-parser/binding-linux-s390x-gnu" "0.127.0" + "@oxc-parser/binding-linux-x64-gnu" "0.127.0" + "@oxc-parser/binding-linux-x64-musl" "0.127.0" + "@oxc-parser/binding-openharmony-arm64" "0.127.0" + "@oxc-parser/binding-wasm32-wasi" "0.127.0" + "@oxc-parser/binding-win32-arm64-msvc" "0.127.0" + "@oxc-parser/binding-win32-ia32-msvc" "0.127.0" + "@oxc-parser/binding-win32-x64-msvc" "0.127.0" + +oxc-resolver@^11.19.1: + version "11.20.0" + resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-11.20.0.tgz#2b2a7a76de753b34be1d6031da1604c8cb580b08" + integrity sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g== + optionalDependencies: + "@oxc-resolver/binding-android-arm-eabi" "11.20.0" + "@oxc-resolver/binding-android-arm64" "11.20.0" + "@oxc-resolver/binding-darwin-arm64" "11.20.0" + "@oxc-resolver/binding-darwin-x64" "11.20.0" + "@oxc-resolver/binding-freebsd-x64" "11.20.0" + "@oxc-resolver/binding-linux-arm-gnueabihf" "11.20.0" + "@oxc-resolver/binding-linux-arm-musleabihf" "11.20.0" + "@oxc-resolver/binding-linux-arm64-gnu" "11.20.0" + "@oxc-resolver/binding-linux-arm64-musl" "11.20.0" + "@oxc-resolver/binding-linux-ppc64-gnu" "11.20.0" + "@oxc-resolver/binding-linux-riscv64-gnu" "11.20.0" + "@oxc-resolver/binding-linux-riscv64-musl" "11.20.0" + "@oxc-resolver/binding-linux-s390x-gnu" "11.20.0" + "@oxc-resolver/binding-linux-x64-gnu" "11.20.0" + "@oxc-resolver/binding-linux-x64-musl" "11.20.0" + "@oxc-resolver/binding-openharmony-arm64" "11.20.0" + "@oxc-resolver/binding-wasm32-wasi" "11.20.0" + "@oxc-resolver/binding-win32-arm64-msvc" "11.20.0" + "@oxc-resolver/binding-win32-x64-msvc" "11.20.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -8989,6 +9804,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + pe-library@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pe-library/-/pe-library-1.0.1.tgz#02735430885a622576a53cd8827658b7d2fada0e" @@ -9019,7 +9839,7 @@ pirates@^4.0.7: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== -pkg-dir@^4.2.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -9329,6 +10149,43 @@ react-devtools-core@^6.1.5: shell-quote "^1.6.1" ws "^7" +react-docgen-typescript@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.4.0.tgz#033428b4a6a639d050ac8baf2a5195c596521713" + integrity sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg== + +react-docgen@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-7.1.1.tgz#a7a8e6b923a945acf0b7325a889ddd74fec74a63" + integrity sha512-hlSJDQ2synMPKFZOsKo9Hi8WWZTC7POR8EmWvTSjow+VDgKzkmjQvFm2fk0tmRw+f0vTOIYKlarR0iL4996pdg== + dependencies: + "@babel/core" "^7.18.9" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + "@types/babel__core" "^7.18.0" + "@types/babel__traverse" "^7.18.0" + "@types/doctrine" "^0.0.9" + "@types/resolve" "^1.20.2" + doctrine "^3.0.0" + resolve "^1.22.1" + strip-indent "^4.0.0" + +react-docgen@^8.0.2: + version "8.0.3" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-8.0.3.tgz#164e5b29f8115f23d69b09966ec835d92bcdc075" + integrity sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w== + dependencies: + "@babel/core" "^7.28.0" + "@babel/traverse" "^7.28.0" + "@babel/types" "^7.28.2" + "@types/babel__core" "^7.20.5" + "@types/babel__traverse" "^7.20.7" + "@types/doctrine" "^0.0.9" + "@types/resolve" "^1.20.2" + doctrine "^3.0.0" + resolve "^1.22.1" + strip-indent "^4.0.0" + react-dom@19.2.3: version "19.2.3" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.3.tgz#f0b61d7e5c4a86773889fcc1853af3ed5f215b17" @@ -9529,6 +10386,11 @@ readable-stream@^3.0.6, readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -9536,6 +10398,17 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +recast@^0.23.5: + version "0.23.11" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.11.tgz#8885570bb28cf773ba1dc600da7f502f7883f73f" + integrity sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA== + dependencies: + ast-types "^0.16.1" + esprima "~4.0.0" + source-map "~0.6.1" + tiny-invariant "^1.3.3" + tslib "^2.0.1" + rechoir@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" @@ -9543,6 +10416,14 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + reflect-metadata@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" @@ -9685,7 +10566,7 @@ resolve-workspace-root@^2.0.0: resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz#9cbbf8321ebccaaf0e4ffea5274aa26b611ccd62" integrity sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w== -resolve@^1.20.0, resolve@^1.22.11, resolve@^1.22.8: +resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.11, resolve@^1.22.8: version "1.22.12" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== @@ -9738,6 +10619,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rrweb-cssom@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz#3021d1b4352fbf3b614aaeed0bc0d5739abe0bc2" @@ -9820,7 +10708,7 @@ scheduler@0.27.0, scheduler@^0.27.0: resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== -schema-utils@^3.0.0: +schema-utils@^3.0.0, schema-utils@^3.1.1: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -9852,7 +10740,7 @@ selfsigned@^5.5.0: "@peculiar/x509" "^1.14.2" pkijs "^3.3.3" -semver@^6.3.1: +semver@^6.0.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -10107,7 +10995,7 @@ source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.6.0, source-map@~0.6.0: +source-map@^0.6.0, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -10187,6 +11075,27 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" +storybook@10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.4.1.tgz#5cd7d11fd16bb7c0ae61b883d65cebe909290820" + integrity sha512-V1Zd2e+gBFufqAQVZ1JR8KLqALsEZ3JYSBnWwQbKa6zCfWWanR6AFMyuOkLt2gZOgGp3h2Riuz88pGNVTQSG0A== + dependencies: + "@storybook/global" "^5.0.0" + "@storybook/icons" "^2.0.2" + "@testing-library/jest-dom" "^6.9.1" + "@testing-library/user-event" "^14.6.1" + "@vitest/expect" "3.2.4" + "@vitest/spy" "3.2.4" + "@webcontainer/env" "^1.1.1" + esbuild "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0" + open "^10.2.0" + oxc-parser "^0.127.0" + oxc-resolver "^11.19.1" + recast "^0.23.5" + semver "^7.7.3" + use-sync-external-store "^1.5.0" + ws "^8.18.0" + stream-buffers@2.2.x: version "2.2.0" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" @@ -10338,6 +11247,11 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.2.2" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -10348,6 +11262,18 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-indent@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.1.1.tgz#aba13de189d4ad9a17f6050e76554ac27585c7af" + integrity sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -10363,7 +11289,7 @@ structured-headers@^0.4.1: resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1" integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg== -style-loader@4.0.0: +style-loader@4.0.0, style-loader@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5" integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA== @@ -10431,7 +11357,7 @@ tagged-tag@^1.0.0: resolved "https://registry.yarnpkg.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6" integrity sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== -tapable@^2.0.0, tapable@^2.3.0, tapable@^2.3.3: +tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0, tapable@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== @@ -10444,7 +11370,7 @@ terminal-link@^2.1.1: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@5.6.1: +terser-webpack-plugin@5.6.1, terser-webpack-plugin@^5.3.17: version "5.6.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz#47bc41bd8b8fab8383b62ec763b7394829097e7b" integrity sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ== @@ -10498,6 +11424,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tinyglobby@^0.2.15: version "0.2.16" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" @@ -10506,6 +11437,16 @@ tinyglobby@^0.2.15: fdir "^6.5.0" picomatch "^4.0.4" +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + +tinyspy@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-4.0.4.tgz#d77a002fb53a88aa1429b419c1c92492e0c81f78" + integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q== + tldts-core@^6.1.86: version "6.1.86" resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" @@ -10579,12 +11520,26 @@ ts-api-utils@^2.5.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== +ts-dedent@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" + integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== + +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.8.1: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -10944,6 +11899,17 @@ webpack-cli@7.0.3: rechoir "^0.8.0" webpack-merge "^6.0.1" +webpack-dev-middleware@^6.1.2: + version "6.1.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-6.1.3.tgz#79f4103f8c898564c9e96c3a9c2422de50f249bc" + integrity sha512-A4ChP0Qj8oGociTs6UdlRUGANIGrCDL3y+pmQMc+dSsraXHCatFpmMey4mYELA+juqwUqwQsUgJJISXl1KWmiw== + dependencies: + colorette "^2.0.10" + memfs "^3.4.12" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + webpack-dev-middleware@^7.4.2: version "7.4.5" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz#d4e8720aa29cb03bc158084a94edb4594e3b7ac0" @@ -10990,6 +11956,15 @@ webpack-dev-server@5.2.4: webpack-dev-middleware "^7.4.2" ws "^8.18.0" +webpack-hot-middleware@^2.25.1: + version "2.26.1" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.26.1.tgz#87214f1e3f9f3acab9271fef9e6ed7b637d719c0" + integrity sha512-khZGfAeJx6I8K9zKohEWWYN6KDlVw2DHownoe+6Vtwj1LP9WFgegXnVMSkZ/dBEBtXFwrkkydsaPFlB7f8wU2A== + dependencies: + ansi-html-community "0.0.8" + html-entities "^2.1.0" + strip-ansi "^6.0.0" + webpack-merge@6.0.1, webpack-merge@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-6.0.1.tgz#50c776868e080574725abc5869bd6e4ef0a16c6a" @@ -11004,7 +11979,12 @@ webpack-sources@^3.5.0: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.5.0.tgz#87bf7f5801a4e985b1f1c92b64b9620a02f76d08" integrity sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ== -webpack@5.107.2: +webpack-virtual-modules@^0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" + integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== + +webpack@5, webpack@5.107.2: version "5.107.2" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.107.2.tgz#dea14dcb177b46b29de15f952f7303691ee2b596" integrity sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==