Compare commits

...

20 Commits

Author SHA1 Message Date
b163fdaa62 update styles, v0.1.17
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2026-02-24 10:37:36 +01:00
ec63a10027 update ci
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2026-02-24 10:14:08 +01:00
5ada69773c update ci, datepicker, v0.1.16
Some checks failed
continuous-integration/drone/push Build is failing
2026-02-24 10:06:41 +01:00
370d6e7e0a v0.1.15
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2026-02-23 23:53:32 +01:00
44dd5d5deb v0.1.14
Some checks failed
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/tag Build encountered an error
2026-02-23 23:51:42 +01:00
8d3ca5a281 fix ci
Some checks failed
continuous-integration/drone/push Build encountered an error
2026-02-23 23:51:16 +01:00
1d5113d209 fix datepicker css, v0.1.13
Some checks failed
continuous-integration/drone/tag Build is failing
continuous-integration/drone/push Build is passing
2026-02-23 23:48:58 +01:00
f71e773a3a update drone
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 23:34:04 +01:00
4921afe296 fix date picker highlighting, v0.1.12
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2026-02-23 23:29:06 +01:00
5cc3e3646c make accent overridable, v0.1.11
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2026-02-23 22:59:57 +01:00
29a4e8c2ee add width, v0.1.10
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2026-02-23 19:57:19 +01:00
3ddd108186 add datepicker, v0.1.9
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2026-02-23 19:21:38 +01:00
836d24943e update eslint
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 15:18:13 +01:00
a312141c21 v0.1.8
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2026-02-23 14:50:55 +01:00
457962ede2 update css
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 14:50:12 +01:00
f1c7e245aa update prettier
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 14:23:37 +01:00
c2e370f0a8 add eslint / prettier
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 14:19:16 +01:00
01b00b5717 update package.json
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 14:01:34 +01:00
f9a9c89e4f v0.1.7
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2026-02-23 13:57:36 +01:00
6ba98fa6b6 fix build
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-23 13:56:52 +01:00
42 changed files with 3317 additions and 2077 deletions

View File

@@ -13,17 +13,18 @@ trigger:
steps: steps:
- name: install - name: install
image: node:22 image: node:25
commands: commands:
- corepack enable
- corepack prepare yarn@1.22.22 --activate
- yarn install --frozen-lockfile - yarn install --frozen-lockfile
- name: build - name: lint
image: node:22 image: node:25
commands:
- yarn lint
- name: build
image: node:25
commands: commands:
- corepack enable
- corepack prepare yarn@1.22.22 --activate
- yarn build - yarn build
--- ---
@@ -32,22 +33,18 @@ type: docker
name: web-ui-publish name: web-ui-publish
trigger: trigger:
branch:
- main
event: event:
- promote - tag
target: ref:
- production - refs/tags/v*
steps: steps:
- name: publish-npm - name: publish-npm
image: node:22 image: node:25
environment: environment:
NEXUS_NPM_TOKEN: NEXUS_NPM_TOKEN:
from_secret: nexus_npm_token from_secret: nexus_npm_token
commands: commands:
- corepack enable
- corepack prepare yarn@1.22.22 --activate
- yarn install --frozen-lockfile - yarn install --frozen-lockfile
- npm config set //nexus.beatrice.wtf/repository/npm-hosted/:_authToken "$NEXUS_NPM_TOKEN" - npm config set //nexus.beatrice.wtf/repository/npm-hosted/:_authToken "$NEXUS_NPM_TOKEN"
- yarn publish:nexus - yarn publish:nexus

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
dist
coverage
storybook-static
node_modules
yarn.lock

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"singleQuote": true,
"semi": true,
"printWidth": 100,
"tabWidth": 4
}

View File

@@ -2,18 +2,14 @@ import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = { const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx|mdx)'], stories: ['../src/**/*.stories.@(ts|tsx|mdx)'],
addons: [ addons: ['@storybook/addon-docs', '@storybook/addon-a11y', '@storybook/addon-themes'],
'@storybook/addon-docs',
'@storybook/addon-a11y',
'@storybook/addon-themes'
],
framework: { framework: {
name: '@storybook/react-vite', name: '@storybook/react-vite',
options: {} options: {},
}, },
docs: { docs: {
autodocs: 'tag' autodocs: 'tag',
} },
}; };
export default config; export default config;

View File

@@ -11,17 +11,17 @@ const preview: Preview = {
<MemoryRouter> <MemoryRouter>
<Story /> <Story />
</MemoryRouter> </MemoryRouter>
) ),
], ],
parameters: { parameters: {
controls: { controls: {
matchers: { matchers: {
color: /(background|color)$/i, color: /(background|color)$/i,
date: /Date$/i date: /Date$/i,
} },
},
layout: 'centered',
}, },
layout: 'centered'
}
}; };
export default preview; export default preview;

48
eslint.config.mjs Normal file
View File

@@ -0,0 +1,48 @@
import js from '@eslint/js';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: [
'dist',
'coverage',
'storybook-static',
'node_modules',
'*.config.cjs',
'vite.config.js',
'vite.config.d.ts',
'tailwind-preset.cjs',
],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
'no-empty': ['error', { allowEmptyCatch: true }],
},
},
{
files: ['src/**/*.{ts,tsx}'],
ignores: ['src/**/*.stories.ts', 'src/**/*.stories.tsx'],
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
);

View File

@@ -1,5 +1,4 @@
GNU Affero General Public License # GNU Affero General Public License
=================================
_Version 3, 19 November 2007_ _Version 3, 19 November 2007_
_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_ _Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_
@@ -201,20 +200,20 @@ You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions: terms of section 4, provided that you also meet all of these conditions:
* **a)** The work must carry prominent notices stating that you modified - **a)** The work must carry prominent notices stating that you modified
it, and giving a relevant date. it, and giving a relevant date.
* **b)** The work must carry prominent notices stating that it is - **b)** The work must carry prominent notices stating that it is
released under this License and any conditions added under section 7. released under this License and any conditions added under section 7.
This requirement modifies the requirement in section 4 to This requirement modifies the requirement in section 4 to
“keep intact all notices”. “keep intact all notices”.
* **c)** You must license the entire work, as a whole, under this - **c)** You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7 License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts, additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it. invalidate such permission if you have separately received it.
* **d)** If the work has interactive user interfaces, each must display - **d)** If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your interfaces that do not display Appropriate Legal Notices, your
work need not make them do so. work need not make them do so.
@@ -236,11 +235,11 @@ of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License, machine-readable Corresponding Source under the terms of this License,
in one of these ways: in one of these ways:
* **a)** Convey the object code in, or embodied in, a physical product - **a)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the (including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium Corresponding Source fixed on a durable physical medium
customarily used for software interchange. customarily used for software interchange.
* **b)** Convey the object code in, or embodied in, a physical product - **b)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a (including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product long as you offer spare parts or customer support for that product
@@ -251,12 +250,12 @@ in one of these ways:
more than your reasonable cost of physically performing this more than your reasonable cost of physically performing this
conveying of source, or **(2)** access to copy the conveying of source, or **(2)** access to copy the
Corresponding Source from a network server at no charge. Corresponding Source from a network server at no charge.
* **c)** Convey individual copies of the object code with a copy of the - **c)** Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord only if you received the object code with such an offer, in accord
with subsection 6b. with subsection 6b.
* **d)** Convey the object code by offering access from a designated - **d)** Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the further charge. You need not require recipients to copy the
@@ -268,7 +267,7 @@ in one of these ways:
Corresponding Source. Regardless of what server hosts the Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements. available for as long as needed to satisfy these requirements.
* **e)** Convey the object code using peer-to-peer transmission, provided - **e)** Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no Source of the work are being offered to the general public at no
charge under subsection 6d. charge under subsection 6d.
@@ -345,19 +344,19 @@ Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms: that material) supplement the terms of this License with terms:
* **a)** Disclaiming warranty or limiting liability differently from the - **a)** Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or terms of sections 15 and 16 of this License; or
* **b)** Requiring preservation of specified reasonable legal notices or - **b)** Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or Notices displayed by works containing it; or
* **c)** Prohibiting misrepresentation of the origin of that material, or - **c)** Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or reasonable ways as different from the original version; or
* **d)** Limiting the use for publicity purposes of names of licensors or - **d)** Limiting the use for publicity purposes of names of licensors or
authors of the material; or authors of the material; or
* **e)** Declining to grant rights under trademark law for use of some - **e)** Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or trade names, trademarks, or service marks; or
* **f)** Requiring indemnification of licensors and authors of that - **f)** Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on any liability that these contractual assumptions directly impose on

View File

@@ -1,6 +1,6 @@
{ {
"name": "@panic/web-ui", "name": "@panic/web-ui",
"version": "0.1.6", "version": "0.1.17",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"description": "Core components for panic.haus web applications", "description": "Core components for panic.haus web applications",
"type": "module", "type": "module",
@@ -27,6 +27,10 @@
"scripts": { "scripts": {
"clean": "rm -rf dist", "clean": "rm -rf dist",
"build": "yarn clean && vite build && tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/base.css dist/styles/base.css && tailwindcss -c tailwind.build.config.cjs -i src/styles/components.css -o dist/styles/components.css --minify && tailwindcss -c tailwind.build.config.cjs -i src/styles/utilities.css -o dist/styles/utilities.css --minify && cp tailwind-preset.cjs dist/tailwind-preset.cjs", "build": "yarn clean && vite build && tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/base.css dist/styles/base.css && tailwindcss -c tailwind.build.config.cjs -i src/styles/components.css -o dist/styles/components.css --minify && tailwindcss -c tailwind.build.config.cjs -i src/styles/utilities.css -o dist/styles/utilities.css --minify && cp tailwind-preset.cjs dist/tailwind-preset.cjs",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"build-storybook": "storybook build", "build-storybook": "storybook build",
"prepublishOnly": "yarn build", "prepublishOnly": "yarn build",
@@ -49,26 +53,33 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@codemirror/language": "^6.11.3",
"@eslint/js": "^10",
"@heroicons/react": "^2.2.0",
"@lezer/highlight": "^1.2.1",
"@mdxeditor/editor": "^3.52.4",
"@storybook/addon-a11y": "^10.2.10", "@storybook/addon-a11y": "^10.2.10",
"@storybook/addon-docs": "^10.2.10", "@storybook/addon-docs": "^10.2.10",
"@storybook/addon-themes": "^10.2.10", "@storybook/addon-themes": "^10.2.10",
"@storybook/react": "^10.2.10", "@storybook/react": "^10.2.10",
"@storybook/react-vite": "^10.2.10", "@storybook/react-vite": "^10.2.10",
"@codemirror/language": "^6.11.3",
"@heroicons/react": "^2.2.0",
"@lezer/highlight": "^1.2.1",
"@mdxeditor/editor": "^3.52.4",
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
"@types/react": "^19.0.0", "@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0", "@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"eslint": "^10",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.1",
"globals": "^17.3.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"prettier": "^3.8.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-router-dom": "^7.0.0", "react-router-dom": "^7.0.0",
"storybook": "^10.2.10", "storybook": "^10.2.10",
"tailwindcss": "^3.4.16", "tailwindcss": "^3.4.16",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"typescript-eslint": "^8.56.0",
"vite": "^7.0.0", "vite": "^7.0.0",
"yjs": "^13.6.24" "yjs": "^13.6.24"
} }

View File

@@ -1,7 +1,7 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: { tailwindcss: {
config: './tailwind.storybook.config.cjs' config: './tailwind.storybook.config.cjs',
} },
} },
}; };

View File

@@ -9,77 +9,86 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Polymorphic button component: renders a native `<button>` or a React Router `<Link>` when `to` is provided.' component:
} 'Polymorphic button component: renders a native `<button>` or a React Router `<Link>` when `to` is provided.',
} },
},
}, },
argTypes: { argTypes: {
label: { label: {
description: 'Visible button label text.', description: 'Visible button label text.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
type: { type: {
description: 'Base visual style.', description: 'Base visual style.',
options: ['solid', 'outlined', 'noborder'], options: ['solid', 'outlined', 'noborder'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'solid' | 'outlined' | 'noborder'" } } table: { type: { summary: "'solid' | 'outlined' | 'noborder'" } },
}, },
variant: { variant: {
description: 'Color variant. If omitted: `primary` for `solid`, `secondary` for the other types.', description:
'Color variant. If omitted: `primary` for `solid`, `secondary` for the other types.',
options: ['primary', 'secondary', 'important'], options: ['primary', 'secondary', 'important'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'primary' | 'secondary' | 'important'" } } table: { type: { summary: "'primary' | 'secondary' | 'important'" } },
}, },
size: { size: {
description: 'Button size.', description: 'Button size.',
options: ['sm', 'md', 'lg', 'full'], options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } } table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
},
width: {
description: 'Button width behavior.',
options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
}, },
to: { to: {
description: 'Navigation path. When set, the component renders a `<Link>`.', description: 'Navigation path. When set, the component renders a `<Link>`.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
htmlType: { htmlType: {
description: 'HTML button type used when rendering a native `<button>`.', description: 'HTML button type used when rendering a native `<button>`.',
options: ['button', 'submit', 'reset'], options: ['button', 'submit', 'reset'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'button' | 'submit' | 'reset'" } } table: { type: { summary: "'button' | 'submit' | 'reset'" } },
}, },
disabled: { disabled: {
description: 'Disables interaction and hover/click states.', description: 'Disables interaction and hover/click states.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
icon: { icon: {
description: 'Optional icon component (for example Heroicons).', description: 'Optional icon component (for example Heroicons).',
control: false, control: false,
table: { type: { summary: 'ElementType' } } table: { type: { summary: 'ElementType' } },
}, },
ariaLabel: { ariaLabel: {
description: 'Accessible label. Falls back to `label` when not provided.', description: 'Accessible label. Falls back to `label` when not provided.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
className: { className: {
description: 'Extra CSS classes for the root element.', description: 'Extra CSS classes for the root element.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
onClick: { onClick: {
description: 'Click handler callback.', description: 'Click handler callback.',
action: 'clicked', action: 'clicked',
table: { type: { summary: 'MouseEventHandler<HTMLElement>' } } table: { type: { summary: 'MouseEventHandler<HTMLElement>' } },
} },
}, },
args: { args: {
type: 'solid', type: 'solid',
variant: 'primary', variant: 'primary',
size: 'md', size: 'md',
label: 'Save' width: 'md',
} label: 'Save',
},
} satisfies Meta<typeof Button>; } satisfies Meta<typeof Button>;
export default meta; export default meta;
@@ -91,37 +100,37 @@ export const SolidImportant: Story = {
args: { args: {
type: 'solid', type: 'solid',
variant: 'important', variant: 'important',
label: 'Delete' label: 'Delete',
} },
}; };
export const OutlinedSecondary: Story = { export const OutlinedSecondary: Story = {
args: { args: {
type: 'outlined', type: 'outlined',
variant: 'secondary', variant: 'secondary',
label: 'Cancel' label: 'Cancel',
} },
}; };
export const IconOnly: Story = { export const IconOnly: Story = {
args: { args: {
icon: PlusIcon, icon: PlusIcon,
label: undefined, label: undefined,
ariaLabel: 'Add item' ariaLabel: 'Add item',
} },
}; };
export const LinkButton: Story = { export const LinkButton: Story = {
args: { args: {
to: '/demo', to: '/demo',
label: 'Go to demo' label: 'Go to demo',
} },
}; };
export const Disabled: Story = { export const Disabled: Story = {
args: { args: {
disabled: true disabled: true,
} },
}; };
export const SizeMatrix: Story = { export const SizeMatrix: Story = {
@@ -130,7 +139,7 @@ export const SizeMatrix: Story = {
<Button {...args} size="sm" label="Small" /> <Button {...args} size="sm" label="Small" />
<Button {...args} size="md" label="Medium" /> <Button {...args} size="md" label="Medium" />
<Button {...args} size="lg" label="Large" /> <Button {...args} size="lg" label="Large" />
<Button {...args} size="full" label="Full width" /> <Button {...args} size="md" width="full" label="Full width" />
</div> </div>
) ),
}; };

View File

@@ -11,6 +11,7 @@ type ButtonProps = {
type: ButtonType; type: ButtonType;
variant?: ButtonVariant; variant?: ButtonVariant;
size?: ComponentSize; size?: ComponentSize;
width?: ComponentSize;
to?: string; to?: string;
htmlType?: NativeButtonType; htmlType?: NativeButtonType;
onClick?: MouseEventHandler<HTMLElement>; onClick?: MouseEventHandler<HTMLElement>;
@@ -24,40 +25,47 @@ const SIZE_CLASS: Record<ComponentSize, string> = {
sm: 'h-8 px-3 text-xs', sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 text-sm', md: 'h-10 px-4 text-sm',
lg: 'h-12 px-5 text-base', lg: 'h-12 px-5 text-base',
full: 'h-10 w-full px-4 text-sm' full: 'h-10 px-4 text-sm',
}; };
const ICON_ONLY_SIZE_CLASS: Record<ComponentSize, string> = { const ICON_ONLY_SIZE_CLASS: Record<ComponentSize, string> = {
sm: 'h-8 w-8 !p-0', sm: 'h-8 w-8 !p-0',
md: 'h-10 w-10 !p-0', md: 'h-10 w-10 !p-0',
lg: 'h-12 w-12 !p-0', lg: 'h-12 w-12 !p-0',
full: 'h-10 w-full !p-0' full: 'h-10 w-10 !p-0',
}; };
const ICON_CLASS: Record<ComponentSize, string> = { const ICON_CLASS: Record<ComponentSize, string> = {
sm: 'h-4 w-4', sm: 'h-4 w-4',
md: 'h-4 w-4', md: 'h-4 w-4',
lg: 'h-5 w-5', lg: 'h-5 w-5',
full: 'h-4 w-4' full: 'h-4 w-4',
}; };
const ICON_ONLY_CLASS: Record<ComponentSize, string> = { const ICON_ONLY_CLASS: Record<ComponentSize, string> = {
sm: 'h-4 w-4', sm: 'h-4 w-4',
md: 'h-5 w-5', md: 'h-5 w-5',
lg: 'h-6 w-6', lg: 'h-6 w-6',
full: 'h-5 w-5' full: 'h-5 w-5',
};
const WIDTH_CLASS: Record<ComponentSize, string> = {
sm: 'max-w-xs',
md: '',
lg: 'max-w-md',
full: 'w-full max-w-none',
}; };
const TYPE_CLASS: Record<ButtonType, string> = { const TYPE_CLASS: Record<ButtonType, string> = {
solid: 'btn-solid', solid: 'btn-solid',
outlined: 'btn-outlined', outlined: 'btn-outlined',
noborder: 'btn-noborder' noborder: 'btn-noborder',
}; };
const VARIANT_CLASS: Record<ButtonVariant, string> = { const VARIANT_CLASS: Record<ButtonVariant, string> = {
primary: 'btn-primary', primary: 'btn-primary',
secondary: 'btn-secondary', secondary: 'btn-secondary',
important: 'btn-important' important: 'btn-important',
}; };
function resolveVariant(type: ButtonType, variant?: ButtonVariant): ButtonVariant { function resolveVariant(type: ButtonType, variant?: ButtonVariant): ButtonVariant {
@@ -73,13 +81,14 @@ export function Button({
type, type,
variant, variant,
size = 'md', size = 'md',
width = 'md',
to, to,
htmlType = 'button', htmlType = 'button',
onClick, onClick,
disabled = false, disabled = false,
icon: Icon, icon: Icon,
ariaLabel, ariaLabel,
className = '' className = '',
}: Readonly<ButtonProps>) { }: Readonly<ButtonProps>) {
const isIconOnly = Icon != null && !label; const isIconOnly = Icon != null && !label;
const resolvedVariant = resolveVariant(type, variant); const resolvedVariant = resolveVariant(type, variant);
@@ -87,10 +96,13 @@ export function Button({
TYPE_CLASS[type], TYPE_CLASS[type],
VARIANT_CLASS[resolvedVariant], VARIANT_CLASS[resolvedVariant],
isIconOnly ? ICON_ONLY_SIZE_CLASS[size] : SIZE_CLASS[size], isIconOnly ? ICON_ONLY_SIZE_CLASS[size] : SIZE_CLASS[size],
WIDTH_CLASS[width],
Icon && label ? 'gap-1.5' : '', Icon && label ? 'gap-1.5' : '',
disabled ? 'pointer-events-none cursor-not-allowed opacity-45 saturate-50' : '', disabled ? 'pointer-events-none cursor-not-allowed opacity-45 saturate-50' : '',
className className,
].join(' ').trim(); ]
.join(' ')
.trim();
const computedAriaLabel = ariaLabel ?? label; const computedAriaLabel = ariaLabel ?? label;
const iconClass = `${isIconOnly ? ICON_ONLY_CLASS[size] : ICON_CLASS[size]} shrink-0`; const iconClass = `${isIconOnly ? ICON_ONLY_CLASS[size] : ICON_CLASS[size]} shrink-0`;
const content = ( const content = (

View File

@@ -8,42 +8,45 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Compact badge/chip component with solid or outlined style. `tone` accepts a Tailwind color token (for example `cyan-700`) and resolves it to the correct border/background/text color at runtime.' component:
} 'Compact badge/chip component with solid or outlined style. `tone` accepts a Tailwind color token (for example `cyan-700`) and resolves it to the correct border/background/text color at runtime.',
} },
},
}, },
argTypes: { argTypes: {
variant: { variant: {
description: 'Chip visual style.', description: 'Chip visual style.',
options: ['solid', 'outlined'], options: ['solid', 'outlined'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'solid' | 'outlined'" } } table: { type: { summary: "'solid' | 'outlined'" } },
}, },
tone: { tone: {
description: 'Tailwind color token (format: `<color>-<shade>`, for example `cyan-700`, `indigo-600`, `rose-500`).', description:
'Tailwind color token (format: `<color>-<shade>`, for example `cyan-700`, `indigo-600`, `rose-500`).',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
as: { as: {
description: "Root tag or component to render (for example `'span'`, `'a'`, `'button'`).", description:
"Root tag or component to render (for example `'span'`, `'a'`, `'button'`).",
control: false, control: false,
table: { type: { summary: 'ElementType' } } table: { type: { summary: 'ElementType' } },
}, },
className: { className: {
description: 'Extra CSS classes for the root element.', description: 'Extra CSS classes for the root element.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
children: { children: {
description: 'Text or React node rendered inside the chip.', description: 'Text or React node rendered inside the chip.',
control: 'text', control: 'text',
table: { type: { summary: 'ReactNode' } } table: { type: { summary: 'ReactNode' } },
} },
}, },
args: { args: {
children: 'Published', children: 'Published',
variant: 'solid' variant: 'solid',
} },
} satisfies Meta<typeof Chip>; } satisfies Meta<typeof Chip>;
export default meta; export default meta;
@@ -55,29 +58,41 @@ export const OutlinedIndigo: Story = {
args: { args: {
variant: 'outlined', variant: 'outlined',
tone: 'indigo-700', tone: 'indigo-700',
children: 'Draft' children: 'Draft',
} },
}; };
export const OutlinedCyan: Story = { export const OutlinedCyan: Story = {
args: { args: {
variant: 'outlined', variant: 'outlined',
tone: 'cyan-700', tone: 'cyan-700',
children: 'Archived' children: 'Archived',
} },
}; };
export const ToneMatrix: Story = { export const ToneMatrix: Story = {
render: () => ( render: () => (
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Chip variant="solid">Default</Chip> <Chip variant="solid">Default</Chip>
<Chip variant="solid" tone="indigo-700">Indigo</Chip> <Chip variant="solid" tone="indigo-700">
<Chip variant="solid" tone="cyan-700">Cyan</Chip> Indigo
<Chip variant="solid" tone="rose-600">Rose</Chip> </Chip>
<Chip variant="solid" tone="cyan-700">
Cyan
</Chip>
<Chip variant="solid" tone="rose-600">
Rose
</Chip>
<Chip variant="outlined">Default</Chip> <Chip variant="outlined">Default</Chip>
<Chip variant="outlined" tone="indigo-700">Indigo</Chip> <Chip variant="outlined" tone="indigo-700">
<Chip variant="outlined" tone="cyan-700">Cyan</Chip> Indigo
<Chip variant="outlined" tone="rose-600">Rose</Chip> </Chip>
<Chip variant="outlined" tone="cyan-700">
Cyan
</Chip>
<Chip variant="outlined" tone="rose-600">
Rose
</Chip>
</div> </div>
) ),
}; };

View File

@@ -14,7 +14,7 @@ type ChipProps<T extends ElementType> = {
const variantClassMap: Record<ChipVariant, string> = { const variantClassMap: Record<ChipVariant, string> = {
solid: 'chip-solid', solid: 'chip-solid',
outlined: 'chip-outlined' outlined: 'chip-outlined',
}; };
type TailwindPalette = Record<string, string>; type TailwindPalette = Record<string, string>;
@@ -25,7 +25,7 @@ function resolveTailwindToneColor(tone: string | undefined): string | null {
return null; return null;
} }
const colorSource = tailwindColors as Record<string, unknown>; const colorSource = tailwindColors as unknown as Record<string, unknown>;
const lastDashIndex = normalizedTone.lastIndexOf('-'); const lastDashIndex = normalizedTone.lastIndexOf('-');
if (lastDashIndex === -1) { if (lastDashIndex === -1) {
@@ -50,16 +50,21 @@ export function Chip<T extends ElementType = 'span'>({
tone, tone,
as, as,
className = '', className = '',
children children,
}: Readonly<ChipProps<T>>) { }: Readonly<ChipProps<T>>) {
const Component = as ?? 'span' as ElementType; const Component = as ?? ('span' as ElementType);
const toneColor = resolveTailwindToneColor(tone); const toneColor = resolveTailwindToneColor(tone);
const toneStyle: CSSProperties | undefined = toneColor == null const toneStyle: CSSProperties | undefined =
toneColor == null
? undefined ? undefined
: variant === 'solid' : variant === 'solid'
? { borderColor: toneColor, backgroundColor: toneColor, color: '#ffffff' } ? { borderColor: toneColor, backgroundColor: toneColor, color: '#ffffff' }
: { borderColor: toneColor, color: toneColor }; : { borderColor: toneColor, color: toneColor };
const classes = `chip-root ${variantClassMap[variant]} ${className}`.trim(); const classes = `chip-root ${variantClassMap[variant]} ${className}`.trim();
return <Component className={classes} style={toneStyle}>{children}</Component>; return (
<Component className={classes} style={toneStyle}>
{children}
</Component>
);
} }

View File

@@ -0,0 +1,240 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { CalendarDaysIcon } from '@heroicons/react/24/solid';
import { DatePicker } from './DatePicker';
const meta = {
title: 'Components/DatePicker',
component: DatePicker,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Date selection field with InputField-compatible API, supporting date/time/datetime-local values, size/layout variants, and validation state.',
},
},
},
argTypes: {
label: {
description: 'Label text shown above (stacked) or on the left (inline).',
control: 'text',
table: { type: { summary: 'string' } },
},
placeholder: {
description: 'Input placeholder text.',
control: 'text',
table: { type: { summary: 'string' } },
},
type: {
description: 'Native date input type.',
options: ['date', 'datetime-local', 'time'],
control: 'inline-radio',
table: { type: { summary: "'date' | 'datetime-local' | 'time'" } },
},
size: {
description: 'Input size.',
options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
},
width: {
description: 'Input width constraint.',
options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
},
layout: {
description: 'Label/input layout mode.',
options: ['stacked', 'inline'],
control: 'inline-radio',
table: { type: { summary: "'stacked' | 'inline'" } },
},
value: {
description: 'Controlled input value.',
control: 'text',
table: { type: { summary: 'string' } },
},
name: {
description: 'Native input `name` attribute.',
control: 'text',
table: { type: { summary: 'string' } },
},
disabled: {
description: 'Disables the input.',
control: 'boolean',
table: { type: { summary: 'boolean' } },
},
required: {
description: 'Sets the native HTML `required` attribute.',
control: 'boolean',
table: { type: { summary: 'boolean' } },
},
error: {
description: 'Validation message shown below the field.',
control: 'text',
table: { type: { summary: 'string' } },
},
rightIcon: {
description: 'Optional trailing icon node.',
control: false,
table: { type: { summary: 'ReactNode' } },
},
className: {
description: 'Extra CSS classes for the outer wrapper.',
control: 'text',
table: { type: { summary: 'string' } },
},
inputClassName: {
description: 'Extra CSS classes for the `<input>` element.',
control: 'text',
table: { type: { summary: 'string' } },
},
onChange: {
description: 'Change handler callback.',
action: 'changed',
table: { type: { summary: 'ChangeEventHandler<HTMLInputElement>' } },
},
onBlur: {
description: 'Blur handler callback.',
control: false,
table: { type: { summary: 'FocusEventHandler<HTMLInputElement>' } },
},
inputRef: {
description: 'Ref forwarded to the native `<input>` element.',
control: false,
table: { type: { summary: 'Ref<HTMLInputElement>' } },
},
},
args: {
label: 'Schedule at',
type: 'datetime-local',
value: '',
size: 'md',
width: 'md',
layout: 'stacked',
},
} satisfies Meta<typeof DatePicker>;
export default meta;
type Story = StoryObj<typeof meta>;
export const DateOnly: Story = {
args: {
type: 'date',
label: 'Publish date',
},
render: (args) => {
const [value, setValue] = useState('2031-05-20');
return (
<DatePicker
{...args}
value={value}
onChange={(event) => {
setValue(event.target.value);
args.onChange?.(event);
}}
/>
);
},
};
export const DateTime: Story = {
args: {
type: 'datetime-local',
label: 'Schedule at',
},
render: (args) => {
const [value, setValue] = useState('2031-05-20T14:30');
return (
<DatePicker
{...args}
value={value}
onChange={(event) => {
setValue(event.target.value);
args.onChange?.(event);
}}
/>
);
},
};
export const TimeOnlyInline: Story = {
args: {
type: 'time',
label: 'Start time',
layout: 'inline',
size: 'sm',
rightIcon: <CalendarDaysIcon className="h-4 w-4 ui-body-secondary" />,
},
render: (args) => {
const [value, setValue] = useState('09:00');
return (
<DatePicker
{...args}
value={value}
onChange={(event) => {
setValue(event.target.value);
args.onChange?.(event);
}}
/>
);
},
};
export const Error: Story = {
args: {
type: 'datetime-local',
label: 'Schedule at',
value: '',
error: 'Pick a valid future date and time',
},
};
export const Disabled: Story = {
args: {
type: 'datetime-local',
label: 'Published at',
value: '2031-05-20T14:30',
disabled: true,
},
};
export const SizeMatrix: Story = {
args: {
type: 'datetime-local',
label: 'Schedule at',
},
render: (args) => {
const [value, setValue] = useState('2031-05-20T14:30');
return (
<div className="grid grid-cols-1 gap-3">
<DatePicker
{...args}
value={value}
size="sm"
onChange={(event) => setValue(event.target.value)}
/>
<DatePicker
{...args}
value={value}
size="md"
onChange={(event) => setValue(event.target.value)}
/>
<DatePicker
{...args}
value={value}
size="lg"
onChange={(event) => setValue(event.target.value)}
/>
<DatePicker
{...args}
value={value}
size="full"
width="full"
onChange={(event) => setValue(event.target.value)}
/>
</div>
);
},
};

View File

@@ -0,0 +1,97 @@
import type { ChangeEventHandler, FocusEventHandler, ReactNode, Ref } from 'react';
import type { ComponentSize } from './types';
type DatePickerKind = 'date' | 'datetime-local' | 'time';
type Layout = 'stacked' | 'inline';
export type DatePickerProps = {
label?: string;
placeholder?: string;
type: DatePickerKind;
size?: ComponentSize;
width?: ComponentSize;
layout?: Layout;
value: string;
name?: string;
onChange?: ChangeEventHandler<HTMLInputElement>;
onBlur?: FocusEventHandler<HTMLInputElement>;
inputRef?: Ref<HTMLInputElement>;
disabled?: boolean;
required?: boolean;
error?: string;
rightIcon?: ReactNode;
className?: string;
inputClassName?: string;
};
export function DatePicker({
label,
placeholder = '',
type,
size = 'md',
width = 'md',
layout = 'stacked',
value,
name,
onChange,
onBlur,
inputRef,
disabled = false,
required = false,
error,
rightIcon,
className = '',
inputClassName = '',
}: Readonly<DatePickerProps>) {
const containerWidthClass = {
sm: 'max-w-xs',
md: 'max-w-sm',
lg: 'max-w-md',
full: 'max-w-none',
}[width];
const inputSizeClass = {
sm: 'h-8 !text-xs',
md: 'h-10 text-sm',
lg: 'h-12 text-sm',
full: 'h-10 text-sm',
}[size];
const wrapperClass =
layout === 'inline' ? 'inline-flex w-auto items-center gap-2' : 'block w-full gap-1';
const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : '';
const hasTrailingIcon = Boolean(rightIcon);
const inputWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1';
return (
<label
className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerWidthClass} ${className}`.trim()}
>
{label ? <span className={labelClass}>{label}</span> : null}
<div className={inputWrapperClass}>
<input
type={type}
value={value}
name={name}
onChange={onChange}
onBlur={onBlur}
ref={inputRef}
placeholder={placeholder}
disabled={disabled}
required={required}
className={`field w-full ${hasTrailingIcon ? 'pr-10' : ''} ${inputSizeClass} ${error ? 'border-red-400/70 focus:border-red-400 focus:ring-red-400/30' : ''} ${inputClassName}`.trim()}
/>
{rightIcon ? (
<span className="pointer-events-none absolute inset-y-0 right-2 inline-flex items-center justify-center px-1">
{rightIcon}
</span>
) : null}
</div>
{error ? (
<span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>
{error}
</span>
) : null}
</label>
);
}

View File

@@ -5,7 +5,7 @@ import { Dropdown } from './Dropdown';
const choices = [ const choices = [
{ id: 'draft', label: 'Draft' }, { id: 'draft', label: 'Draft' },
{ id: 'review', label: 'In review' }, { id: 'review', label: 'In review' },
{ id: 'published', label: 'Published' } { id: 'published', label: 'Published' },
]; ];
const meta = { const meta = {
@@ -15,76 +15,84 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Styled select component with label, error state, stacked/inline layout, and multiple sizes.' component:
} 'Styled select component with label, error state, stacked/inline layout, and multiple sizes.',
} },
},
}, },
argTypes: { argTypes: {
label: { label: {
description: 'Label text shown above (stacked) or on the left (inline).', description: 'Label text shown above (stacked) or on the left (inline).',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
value: { value: {
description: 'Current selected value (must match one `choices[].id`).', description: 'Current selected value (must match one `choices[].id`).',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
choices: { choices: {
description: "Options list in `{ id: string; label: string }` format.", description: 'Options list in `{ id: string; label: string }` format.',
control: 'object', control: 'object',
table: { type: { summary: 'Array<{ id: string; label: string }>' } } table: { type: { summary: 'Array<{ id: string; label: string }>' } },
}, },
size: { size: {
description: 'Control size.', description: 'Control size.',
options: ['sm', 'md', 'lg', 'full'], options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } } table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
},
width: {
description: 'Control width constraint.',
options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
}, },
layout: { layout: {
description: 'Label/control layout mode.', description: 'Label/control layout mode.',
options: ['stacked', 'inline'], options: ['stacked', 'inline'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'stacked' | 'inline'" } } table: { type: { summary: "'stacked' | 'inline'" } },
}, },
disabled: { disabled: {
description: 'Disables the field.', description: 'Disables the field.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
required: { required: {
description: 'Sets the native HTML `required` attribute.', description: 'Sets the native HTML `required` attribute.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
error: { error: {
description: 'Error message shown below the field.', description: 'Error message shown below the field.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
className: { className: {
description: 'Extra CSS classes for the wrapper.', description: 'Extra CSS classes for the wrapper.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
selectClassName: { selectClassName: {
description: 'Extra CSS classes for the `<select>` element.', description: 'Extra CSS classes for the `<select>` element.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
onChange: { onChange: {
description: 'Callback fired with the newly selected value.', description: 'Callback fired with the newly selected value.',
action: 'changed', action: 'changed',
table: { type: { summary: '(value: string) => void' } } table: { type: { summary: '(value: string) => void' } },
} },
}, },
args: { args: {
label: 'Status', label: 'Status',
value: 'draft', value: 'draft',
choices, choices,
size: 'md', size: 'md',
layout: 'stacked' width: 'md',
} layout: 'stacked',
},
} satisfies Meta<typeof Dropdown>; } satisfies Meta<typeof Dropdown>;
export default meta; export default meta;
@@ -103,13 +111,13 @@ export const Stacked: Story = {
}} }}
/> />
); );
} },
}; };
export const Inline: Story = { export const Inline: Story = {
args: { args: {
layout: 'inline', layout: 'inline',
size: 'sm' size: 'sm',
}, },
render: (args) => { render: (args) => {
const [value, setValue] = useState(args.value); const [value, setValue] = useState(args.value);
@@ -123,19 +131,19 @@ export const Inline: Story = {
}} }}
/> />
); );
} },
}; };
export const Disabled: Story = { export const Disabled: Story = {
args: { args: {
disabled: true disabled: true,
} },
}; };
export const WithError: Story = { export const WithError: Story = {
args: { args: {
error: 'Please choose a valid status' error: 'Please choose a valid status',
} },
}; };
export const SizeMatrix: Story = { export const SizeMatrix: Story = {
@@ -146,8 +154,15 @@ export const SizeMatrix: Story = {
<Dropdown {...args} value={value} size="sm" label="Small" onChange={setValue} /> <Dropdown {...args} value={value} size="sm" label="Small" onChange={setValue} />
<Dropdown {...args} value={value} size="md" label="Medium" onChange={setValue} /> <Dropdown {...args} value={value} size="md" label="Medium" onChange={setValue} />
<Dropdown {...args} value={value} size="lg" label="Large" onChange={setValue} /> <Dropdown {...args} value={value} size="lg" label="Large" onChange={setValue} />
<Dropdown {...args} value={value} size="full" label="Full" onChange={setValue} /> <Dropdown
{...args}
value={value}
size="full"
width="full"
label="Full"
onChange={setValue}
/>
</div> </div>
); );
} },
}; };

View File

@@ -14,6 +14,7 @@ type DropdownProps = {
value: string; value: string;
choices: DropdownChoice[]; choices: DropdownChoice[];
size?: ComponentSize; size?: ComponentSize;
width?: ComponentSize;
layout?: DropdownLayout; layout?: DropdownLayout;
disabled?: boolean; disabled?: boolean;
required?: boolean; required?: boolean;
@@ -28,41 +29,43 @@ export function Dropdown({
value, value,
choices, choices,
size = 'md', size = 'md',
width = 'md',
layout = 'stacked', layout = 'stacked',
disabled = false, disabled = false,
required = false, required = false,
onChange, onChange,
error, error,
className = '', className = '',
selectClassName = '' selectClassName = '',
}: Readonly<DropdownProps>) { }: Readonly<DropdownProps>) {
const containerSizeClass = { const containerWidthClass = {
sm: 'max-w-xs', sm: 'max-w-xs',
md: 'max-w-sm', md: 'max-w-sm',
lg: 'max-w-md', lg: 'max-w-md',
full: 'max-w-none' full: 'max-w-none',
}[size]; }[width];
const selectSizeClass = { const selectSizeClass = {
sm: 'h-8 !text-xs', sm: 'h-8 !text-xs',
md: 'h-10 text-sm', md: 'h-10 text-sm',
lg: 'h-12 text-sm', lg: 'h-12 text-sm',
full: 'h-10 text-sm' full: 'h-10 text-sm',
}[size]; }[size];
const handleChange: ChangeEventHandler<HTMLSelectElement> = (event) => { const handleChange: ChangeEventHandler<HTMLSelectElement> = (event) => {
onChange?.(event.target.value); onChange?.(event.target.value);
}; };
const wrapperClass = layout === 'inline' const wrapperClass =
? 'inline-flex w-auto items-center gap-2' layout === 'inline' ? 'inline-flex w-auto items-center gap-2' : 'block w-full gap-1';
: 'block w-full gap-1';
const selectWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1'; const selectWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1';
const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : ''; const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : '';
return ( return (
<label className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerSizeClass} ${className}`.trim()}> <label
className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerWidthClass} ${className}`.trim()}
>
{label ? <span className={labelClass}>{label}</span> : null} {label ? <span className={labelClass}>{label}</span> : null}
<div className={selectWrapperClass}> <div className={selectWrapperClass}>
<select <select
@@ -78,11 +81,17 @@ export function Dropdown({
</option> </option>
))} ))}
</select> </select>
<span className={`pointer-events-none absolute inset-y-0 right-3 flex items-center ${disabled ? 'ui-label-disabled' : 'ui-body-secondary'}`}> <span
className={`pointer-events-none absolute inset-y-0 right-3 flex items-center ${disabled ? 'ui-label-disabled' : 'ui-body-secondary'}`}
>
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" /> <ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
</span> </span>
</div> </div>
{error ? <span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>{error}</span> : null} {error ? (
<span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>
{error}
</span>
) : null}
</label> </label>
); );
} }

View File

@@ -12,35 +12,36 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Surface container with a title bar and a responsive content grid, intended for CMS forms.' component:
} 'Surface container with a title bar and a responsive content grid, intended for CMS forms.',
} },
},
}, },
argTypes: { argTypes: {
title: { title: {
description: 'Form title displayed in the header bar.', description: 'Form title displayed in the header bar.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
titleBarRight: { titleBarRight: {
description: 'Optional node rendered on the right side of the title bar.', description: 'Optional node rendered on the right side of the title bar.',
control: false, control: false,
table: { type: { summary: 'ReactNode' } } table: { type: { summary: 'ReactNode' } },
}, },
children: { children: {
description: 'Form content rendered inside the responsive grid.', description: 'Form content rendered inside the responsive grid.',
control: false, control: false,
table: { type: { summary: 'ReactNode' } } table: { type: { summary: 'ReactNode' } },
}, },
className: { className: {
description: 'Extra CSS classes for the root container.', description: 'Extra CSS classes for the root container.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
} },
}, },
args: { args: {
title: 'Post details' title: 'Post details',
} },
} satisfies Meta<typeof Form>; } satisfies Meta<typeof Form>;
export default meta; export default meta;
@@ -56,13 +57,13 @@ export const Basic: Story = {
value="draft" value="draft"
choices={[ choices={[
{ id: 'draft', label: 'Draft' }, { id: 'draft', label: 'Draft' },
{ id: 'published', label: 'Published' } { id: 'published', label: 'Published' },
]} ]}
/> />
<InputField label="Slug" type="text" value="a-short-post-title" /> <InputField label="Slug" type="text" value="a-short-post-title" />
</> </>
) ),
} },
}; };
export const WithActions: Story = { export const WithActions: Story = {
@@ -71,10 +72,7 @@ export const WithActions: Story = {
const [status, setStatus] = useState('draft'); const [status, setStatus] = useState('draft');
return ( return (
<Form <Form {...args} titleBarRight={<Button type="solid" size="sm" label="Save" />}>
{...args}
titleBarRight={<Button type="solid" size="sm" label="Save" />}
>
<div className="col-span-2"> <div className="col-span-2">
<InputField <InputField
label="Title" label="Title"
@@ -82,6 +80,7 @@ export const WithActions: Story = {
value={title} value={title}
onChange={(event) => setTitle(event.target.value)} onChange={(event) => setTitle(event.target.value)}
size="full" size="full"
width="full"
/> />
</div> </div>
<Dropdown <Dropdown
@@ -91,11 +90,11 @@ export const WithActions: Story = {
choices={[ choices={[
{ id: 'draft', label: 'Draft' }, { id: 'draft', label: 'Draft' },
{ id: 'review', label: 'In review' }, { id: 'review', label: 'In review' },
{ id: 'published', label: 'Published' } { id: 'published', label: 'Published' },
]} ]}
/> />
<InputField label="Slug" type="text" value="storybook-powered-cms" /> <InputField label="Slug" type="text" value="storybook-powered-cms" />
</Form> </Form>
); );
} },
}; };

View File

@@ -11,14 +11,15 @@ type FormProps = {
export function Form({ title, titleBarRight, children, className = '' }: Readonly<FormProps>) { export function Form({ title, titleBarRight, children, className = '' }: Readonly<FormProps>) {
return ( return (
<div className={`surface overflow-hidden rounded-xl ${className}`.trim()}> <div className={`surface overflow-hidden rounded-xl ${className}`.trim()}>
<div className="flex items-center justify-between border-b px-4 py-3 sm:px-5" style={{ borderColor: 'var(--surface-divider)' }}> <div
className="flex items-center justify-between border-b px-4 py-3 sm:px-5"
style={{ borderColor: 'var(--surface-divider)' }}
>
<Label variant="h4">{title}</Label> <Label variant="h4">{title}</Label>
{titleBarRight ? <div>{titleBarRight}</div> : null} {titleBarRight ? <div>{titleBarRight}</div> : null}
</div> </div>
<div className="grid grid-cols-1 gap-4 p-4 sm:p-5 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-4 p-4 sm:p-5 lg:grid-cols-3">{children}</div>
{children}
</div>
</div> </div>
); );
} }

View File

@@ -10,94 +10,102 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Text input field with optional label, validation state, size/layout variants, and password visibility toggle.' component:
} 'Text input field with optional label, validation state, size/layout variants, and password visibility toggle.',
} },
},
}, },
argTypes: { argTypes: {
label: { label: {
description: 'Label text shown above (stacked) or on the left (inline).', description: 'Label text shown above (stacked) or on the left (inline).',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
placeholder: { placeholder: {
description: 'Input placeholder text.', description: 'Input placeholder text.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
type: { type: {
description: 'Native input type.', description: 'Native input type.',
options: ['text', 'password', 'email'], options: ['text', 'password', 'email'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'text' | 'password' | 'email'" } } table: { type: { summary: "'text' | 'password' | 'email'" } },
}, },
size: { size: {
description: 'Input size.', description: 'Input size.',
options: ['sm', 'md', 'lg', 'full'], options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } } table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
},
width: {
description: 'Input width constraint.',
options: ['sm', 'md', 'lg', 'full'],
control: 'inline-radio',
table: { type: { summary: "'sm' | 'md' | 'lg' | 'full'" } },
}, },
layout: { layout: {
description: 'Label/input layout mode.', description: 'Label/input layout mode.',
options: ['stacked', 'inline'], options: ['stacked', 'inline'],
control: 'inline-radio', control: 'inline-radio',
table: { type: { summary: "'stacked' | 'inline'" } } table: { type: { summary: "'stacked' | 'inline'" } },
}, },
value: { value: {
description: 'Controlled input value.', description: 'Controlled input value.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
name: { name: {
description: 'Native input `name` attribute.', description: 'Native input `name` attribute.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
disabled: { disabled: {
description: 'Disables the input.', description: 'Disables the input.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
required: { required: {
description: 'Sets the native HTML `required` attribute.', description: 'Sets the native HTML `required` attribute.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
error: { error: {
description: 'Validation message shown below the field.', description: 'Validation message shown below the field.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
rightIcon: { rightIcon: {
description: 'Optional trailing icon node (ignored for password type because toggle icon is used).', description:
'Optional trailing icon node (ignored for password type because toggle icon is used).',
control: false, control: false,
table: { type: { summary: 'ReactNode' } } table: { type: { summary: 'ReactNode' } },
}, },
className: { className: {
description: 'Extra CSS classes for the outer wrapper.', description: 'Extra CSS classes for the outer wrapper.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
inputClassName: { inputClassName: {
description: 'Extra CSS classes for the `<input>` element.', description: 'Extra CSS classes for the `<input>` element.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
onChange: { onChange: {
description: 'Change handler callback.', description: 'Change handler callback.',
action: 'changed', action: 'changed',
table: { type: { summary: 'ChangeEventHandler<HTMLInputElement>' } } table: { type: { summary: 'ChangeEventHandler<HTMLInputElement>' } },
}, },
onBlur: { onBlur: {
description: 'Blur handler callback.', description: 'Blur handler callback.',
control: false, control: false,
table: { type: { summary: 'FocusEventHandler<HTMLInputElement>' } } table: { type: { summary: 'FocusEventHandler<HTMLInputElement>' } },
}, },
inputRef: { inputRef: {
description: 'Ref forwarded to the native `<input>` element.', description: 'Ref forwarded to the native `<input>` element.',
control: false, control: false,
table: { type: { summary: 'Ref<HTMLInputElement>' } } table: { type: { summary: 'Ref<HTMLInputElement>' } },
} },
}, },
args: { args: {
label: 'Email', label: 'Email',
@@ -105,8 +113,9 @@ const meta = {
placeholder: 'name@example.com', placeholder: 'name@example.com',
value: '', value: '',
size: 'md', size: 'md',
layout: 'stacked' width: 'md',
} layout: 'stacked',
},
} satisfies Meta<typeof InputField>; } satisfies Meta<typeof InputField>;
export default meta; export default meta;
@@ -116,7 +125,7 @@ export const Text: Story = {
args: { args: {
type: 'text', type: 'text',
label: 'Title', label: 'Title',
placeholder: 'Write a title' placeholder: 'Write a title',
}, },
render: (args) => { render: (args) => {
const [value, setValue] = useState('Storybook integration'); const [value, setValue] = useState('Storybook integration');
@@ -130,14 +139,14 @@ export const Text: Story = {
}} }}
/> />
); );
} },
}; };
export const PasswordWithToggle: Story = { export const PasswordWithToggle: Story = {
args: { args: {
type: 'password', type: 'password',
label: 'Password', label: 'Password',
placeholder: 'Type a strong password' placeholder: 'Type a strong password',
}, },
render: (args) => { render: (args) => {
const [value, setValue] = useState('pa55word'); const [value, setValue] = useState('pa55word');
@@ -151,7 +160,7 @@ export const PasswordWithToggle: Story = {
}} }}
/> />
); );
} },
}; };
export const InlineWithIcon: Story = { export const InlineWithIcon: Story = {
@@ -160,7 +169,7 @@ export const InlineWithIcon: Story = {
label: 'Search', label: 'Search',
layout: 'inline', layout: 'inline',
size: 'sm', size: 'sm',
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" /> rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />,
}, },
render: (args) => { render: (args) => {
const [value, setValue] = useState('posts'); const [value, setValue] = useState('posts');
@@ -174,7 +183,7 @@ export const InlineWithIcon: Story = {
}} }}
/> />
); );
} },
}; };
export const Error: Story = { export const Error: Story = {
@@ -182,8 +191,8 @@ export const Error: Story = {
type: 'email', type: 'email',
label: 'Email', label: 'Email',
value: 'invalid.mail', value: 'invalid.mail',
error: 'Enter a valid email address' error: 'Enter a valid email address',
} },
}; };
export const Disabled: Story = { export const Disabled: Story = {
@@ -191,25 +200,46 @@ export const Disabled: Story = {
type: 'text', type: 'text',
label: 'Read only field', label: 'Read only field',
value: 'Locked content', value: 'Locked content',
disabled: true disabled: true,
} },
}; };
export const SizeMatrix: Story = { export const SizeMatrix: Story = {
args: { args: {
type: 'text', type: 'text',
label: 'Name', label: 'Name',
placeholder: 'Enter value' placeholder: 'Enter value',
}, },
render: (args) => { render: (args) => {
const [value, setValue] = useState('Beatrice'); const [value, setValue] = useState('Beatrice');
return ( return (
<div className="grid grid-cols-1 gap-3"> <div className="grid grid-cols-1 gap-3">
<InputField {...args} value={value} size="sm" onChange={(event) => setValue(event.target.value)} /> <InputField
<InputField {...args} value={value} size="md" onChange={(event) => setValue(event.target.value)} /> {...args}
<InputField {...args} value={value} size="lg" onChange={(event) => setValue(event.target.value)} /> value={value}
<InputField {...args} value={value} size="full" onChange={(event) => setValue(event.target.value)} /> size="sm"
onChange={(event) => setValue(event.target.value)}
/>
<InputField
{...args}
value={value}
size="md"
onChange={(event) => setValue(event.target.value)}
/>
<InputField
{...args}
value={value}
size="lg"
onChange={(event) => setValue(event.target.value)}
/>
<InputField
{...args}
value={value}
size="full"
width="full"
onChange={(event) => setValue(event.target.value)}
/>
</div> </div>
); );
} },
}; };

View File

@@ -12,6 +12,7 @@ type InputFieldProps = {
placeholder?: string; placeholder?: string;
type: InputKind; type: InputKind;
size?: ComponentSize; size?: ComponentSize;
width?: ComponentSize;
layout?: Layout; layout?: Layout;
value: string; value: string;
name?: string; name?: string;
@@ -31,6 +32,7 @@ export function InputField({
placeholder = '', placeholder = '',
type, type,
size = 'md', size = 'md',
width = 'md',
layout = 'stacked', layout = 'stacked',
value, value,
name, name,
@@ -42,26 +44,25 @@ export function InputField({
error, error,
rightIcon, rightIcon,
className = '', className = '',
inputClassName = '' inputClassName = '',
}: Readonly<InputFieldProps>) { }: Readonly<InputFieldProps>) {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const containerSizeClass = { const containerWidthClass = {
sm: 'max-w-xs', sm: 'max-w-xs',
md: 'max-w-sm', md: 'max-w-sm',
lg: 'max-w-md', lg: 'max-w-md',
full: 'max-w-none' full: 'max-w-none',
}[size]; }[width];
const inputSizeClass = { const inputSizeClass = {
sm: 'h-8 !text-xs', sm: 'h-8 !text-xs',
md: 'h-10 text-sm', md: 'h-10 text-sm',
lg: 'h-12 text-sm', lg: 'h-12 text-sm',
full: 'h-10 text-sm' full: 'h-10 text-sm',
}[size]; }[size];
const wrapperClass = layout === 'inline' const wrapperClass =
? 'inline-flex w-auto items-center gap-2' layout === 'inline' ? 'inline-flex w-auto items-center gap-2' : 'block w-full gap-1';
: 'block w-full gap-1';
const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : ''; const labelClass = layout === 'inline' ? 'text-xs ui-body-secondary' : '';
const isPasswordType = type === 'password'; const isPasswordType = type === 'password';
const resolvedType: InputKind = isPasswordType && showPassword ? 'text' : type; const resolvedType: InputKind = isPasswordType && showPassword ? 'text' : type;
@@ -69,7 +70,9 @@ export function InputField({
const inputWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1'; const inputWrapperClass = layout === 'inline' ? 'relative' : 'relative mt-1';
return ( return (
<label className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerSizeClass} ${className}`.trim()}> <label
className={`${wrapperClass} text-sm font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'} ${containerWidthClass} ${className}`.trim()}
>
{label ? <span className={labelClass}>{label}</span> : null} {label ? <span className={labelClass}>{label}</span> : null}
<div className={inputWrapperClass}> <div className={inputWrapperClass}>
<input <input
@@ -100,7 +103,11 @@ export function InputField({
</span> </span>
) : null} ) : null}
</div> </div>
{error ? <span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>{error}</span> : null} {error ? (
<span className="mt-1 block text-xs" style={{ color: 'var(--error-text)' }}>
{error}
</span>
) : null}
</label> </label>
); );
} }

View File

@@ -8,37 +8,44 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Typography helper component for headings, body text, caption, error text, and inline code styles.' component:
} 'Typography helper component for headings, body text, caption, error text, and inline code styles.',
} },
},
}, },
argTypes: { argTypes: {
variant: { variant: {
description: 'Typography style preset.', description: 'Typography style preset.',
options: ['h1', 'h2', 'h3', 'h4', 'body', 'body2', 'caption', 'error', 'code'], options: ['h1', 'h2', 'h3', 'h4', 'body', 'body2', 'caption', 'error', 'code'],
control: 'select', control: 'select',
table: { type: { summary: "'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'body2' | 'caption' | 'error' | 'code'" } } table: {
type: {
summary:
"'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'body2' | 'caption' | 'error' | 'code'",
},
},
}, },
as: { as: {
description: "Override rendered HTML tag or component (for example `'p'`, `'span'`, `'h2'`).", description:
"Override rendered HTML tag or component (for example `'p'`, `'span'`, `'h2'`).",
control: false, control: false,
table: { type: { summary: 'ElementType' } } table: { type: { summary: 'ElementType' } },
}, },
className: { className: {
description: 'Extra CSS classes.', description: 'Extra CSS classes.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
children: { children: {
description: 'Label content.', description: 'Label content.',
control: 'text', control: 'text',
table: { type: { summary: 'ReactNode' } } table: { type: { summary: 'ReactNode' } },
} },
}, },
args: { args: {
variant: 'body', variant: 'body',
children: 'Label text' children: 'Label text',
} },
} satisfies Meta<typeof Label>; } satisfies Meta<typeof Label>;
export default meta; export default meta;
@@ -49,15 +56,15 @@ export const Body: Story = {};
export const Error: Story = { export const Error: Story = {
args: { args: {
variant: 'error', variant: 'error',
children: 'This field is required' children: 'This field is required',
} },
}; };
export const Code: Story = { export const Code: Story = {
args: { args: {
variant: 'code', variant: 'code',
children: 'const isPublished = true;' children: 'const isPublished = true;',
} },
}; };
export const VariantScale: Story = { export const VariantScale: Story = {
@@ -73,5 +80,5 @@ export const VariantScale: Story = {
<Label variant="error">Error copy</Label> <Label variant="error">Error copy</Label>
<Label variant="code">npm run build</Label> <Label variant="code">npm run build</Label>
</div> </div>
) ),
}; };

View File

@@ -1,15 +1,6 @@
import type { ElementType, ReactNode } from 'react'; import type { ElementType, ReactNode } from 'react';
type LabelVariant = type LabelVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'body2' | 'caption' | 'error' | 'code';
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'body'
| 'body2'
| 'caption'
| 'error'
| 'code';
type LabelProps<T extends ElementType> = { type LabelProps<T extends ElementType> = {
variant?: LabelVariant; variant?: LabelVariant;
@@ -27,7 +18,7 @@ const variantClassMap: Record<LabelVariant, string> = {
body2: 'ui-body-secondary text-sm', body2: 'ui-body-secondary text-sm',
caption: 'ui-kicker text-xs font-semibold uppercase tracking-[0.12em]', caption: 'ui-kicker text-xs font-semibold uppercase tracking-[0.12em]',
error: 'ui-error text-sm', error: 'ui-error text-sm',
code: 'ui-code text-sm font-mono' code: 'ui-code text-sm font-mono',
}; };
const variantTagMap: Record<LabelVariant, ElementType> = { const variantTagMap: Record<LabelVariant, ElementType> = {
@@ -39,14 +30,14 @@ const variantTagMap: Record<LabelVariant, ElementType> = {
body2: 'p', body2: 'p',
caption: 'p', caption: 'p',
error: 'p', error: 'p',
code: 'code' code: 'code',
}; };
export function Label<T extends ElementType = 'p'>({ export function Label<T extends ElementType = 'p'>({
variant = 'body', variant = 'body',
as, as,
className = '', className = '',
children children,
}: Readonly<LabelProps<T>>) { }: Readonly<LabelProps<T>>) {
const Component = as ?? variantTagMap[variant]; const Component = as ?? variantTagMap[variant];
const classes = `${variantClassMap[variant]} ${className}`.trim(); const classes = `${variantClassMap[variant]} ${className}`.trim();

View File

@@ -1,14 +1,14 @@
import { useState } from 'react'; import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { headingsPlugin, listsPlugin, markdownShortcutPlugin, quotePlugin } from '@mdxeditor/editor'; import {
headingsPlugin,
listsPlugin,
markdownShortcutPlugin,
quotePlugin,
} from '@mdxeditor/editor';
import { MDXEditorField } from './MDXEditorField'; import { MDXEditorField } from './MDXEditorField';
const basePlugins = [ const basePlugins = [headingsPlugin(), listsPlugin(), quotePlugin(), markdownShortcutPlugin()];
headingsPlugin(),
listsPlugin(),
quotePlugin(),
markdownShortcutPlugin()
];
const sampleMarkdown = `# Hello from MDXEditor const sampleMarkdown = `# Hello from MDXEditor
@@ -25,88 +25,90 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'MDX editor wrapper with label, editable/read-only/disabled modes, theme class support, and error rendering.' component:
} 'MDX editor wrapper with label, editable/read-only/disabled modes, theme class support, and error rendering.',
} },
},
}, },
argTypes: { argTypes: {
label: { label: {
description: 'Field label shown above the editor.', description: 'Field label shown above the editor.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
markdown: { markdown: {
description: 'Controlled markdown content value.', description: 'Controlled markdown content value.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
readOnly: { readOnly: {
description: 'Enables read-only mode.', description: 'Enables read-only mode.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
disabled: { disabled: {
description: 'Disables editing and applies disabled visuals.', description: 'Disables editing and applies disabled visuals.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
themeClassName: { themeClassName: {
description: 'Theme class applied to MDXEditor (for example `light-theme` or `dark-theme`).', description:
'Theme class applied to MDXEditor (for example `light-theme` or `dark-theme`).',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
plugins: { plugins: {
description: 'MDXEditor plugins array.', description: 'MDXEditor plugins array.',
control: false, control: false,
table: { type: { summary: 'MDXEditorProps["plugins"]' } } table: { type: { summary: 'MDXEditorProps["plugins"]' } },
}, },
contentEditableClassName: { contentEditableClassName: {
description: 'CSS class used on the content editable area.', description: 'CSS class used on the content editable area.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
className: { className: {
description: 'Extra CSS classes for the outer wrapper.', description: 'Extra CSS classes for the outer wrapper.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
editorWrapperClassName: { editorWrapperClassName: {
description: 'Extra CSS classes for the editor shell element.', description: 'Extra CSS classes for the editor shell element.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
editorWrapperStyle: { editorWrapperStyle: {
description: 'Inline style object for the editor shell.', description: 'Inline style object for the editor shell.',
control: 'object', control: 'object',
table: { type: { summary: 'CSSProperties' } } table: { type: { summary: 'CSSProperties' } },
}, },
editorClassName: { editorClassName: {
description: 'Extra CSS classes for the MDXEditor instance.', description: 'Extra CSS classes for the MDXEditor instance.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
error: { error: {
description: 'Error message shown below the editor.', description: 'Error message shown below the editor.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
onChange: { onChange: {
description: 'Callback fired when markdown changes in editable mode.', description: 'Callback fired when markdown changes in editable mode.',
action: 'changed', action: 'changed',
table: { type: { summary: '(markdown: string) => void' } } table: { type: { summary: '(markdown: string) => void' } },
}, },
editorRef: { editorRef: {
description: 'Ref to MDXEditor methods.', description: 'Ref to MDXEditor methods.',
control: false, control: false,
table: { type: { summary: 'Ref<MDXEditorMethods | null>' } } table: { type: { summary: 'Ref<MDXEditorMethods | null>' } },
} },
}, },
args: { args: {
label: 'Content', label: 'Content',
markdown: sampleMarkdown, markdown: sampleMarkdown,
plugins: basePlugins, plugins: basePlugins,
themeClassName: '' themeClassName: '',
} },
} satisfies Meta<typeof MDXEditorField>; } satisfies Meta<typeof MDXEditorField>;
export default meta; export default meta;
@@ -128,12 +130,12 @@ export const Editable: Story = {
/> />
</div> </div>
); );
} },
}; };
export const ReadOnly: Story = { export const ReadOnly: Story = {
args: { args: {
readOnly: true readOnly: true,
}, },
render: (args) => ( render: (args) => (
<div className="w-full max-w-2xl"> <div className="w-full max-w-2xl">
@@ -142,13 +144,13 @@ export const ReadOnly: Story = {
editorWrapperClassName="mt-2 overflow-hidden rounded-xl border" editorWrapperClassName="mt-2 overflow-hidden rounded-xl border"
/> />
</div> </div>
) ),
}; };
export const DisabledWithError: Story = { export const DisabledWithError: Story = {
args: { args: {
disabled: true, disabled: true,
error: 'Editor is currently disabled' error: 'Editor is currently disabled',
}, },
render: (args) => ( render: (args) => (
<div className="w-full max-w-2xl"> <div className="w-full max-w-2xl">
@@ -157,5 +159,5 @@ export const DisabledWithError: Story = {
editorWrapperClassName="mt-2 overflow-hidden rounded-xl border" editorWrapperClassName="mt-2 overflow-hidden rounded-xl border"
/> />
</div> </div>
) ),
}; };

View File

@@ -33,20 +33,28 @@ export function MDXEditorField({
editorWrapperClassName = 'post-mdx-editor mt-2 overflow-hidden rounded-xl border', editorWrapperClassName = 'post-mdx-editor mt-2 overflow-hidden rounded-xl border',
editorWrapperStyle, editorWrapperStyle,
editorClassName = '', editorClassName = '',
error error,
}: Readonly<MDXEditorFieldProps>) { }: Readonly<MDXEditorFieldProps>) {
const resolvedEditorClassName = `${themeClassName} ${editorClassName}`.trim(); const resolvedEditorClassName = `${themeClassName} ${editorClassName}`.trim();
const editorModeKey = disabled || readOnly ? 'read-only' : 'editable'; const editorModeKey = disabled || readOnly ? 'read-only' : 'editable';
const resolvedEditorWrapperClassName = `${editorWrapperClassName} ${disabled ? 'post-mdx-editor--disabled' : 'post-mdx-editor--enabled'}`.trim(); const resolvedEditorWrapperClassName =
`${editorWrapperClassName} ${disabled ? 'post-mdx-editor--disabled' : 'post-mdx-editor--enabled'}`.trim();
const resolvedEditorWrapperStyle: CSSProperties = { const resolvedEditorWrapperStyle: CSSProperties = {
backgroundColor: disabled ? 'var(--field-disabled-bg)' : 'var(--field-bg)', backgroundColor: disabled ? 'var(--field-disabled-bg)' : 'var(--field-bg)',
borderColor: disabled ? 'var(--field-disabled-border)' : 'var(--field-border)', borderColor: disabled ? 'var(--field-disabled-border)' : 'var(--field-border)',
...editorWrapperStyle ...editorWrapperStyle,
}; };
return ( return (
<div className={className}> <div className={className}>
{label ? <Label variant="body" className={`font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'}`}>{label}</Label> : null} {label ? (
<Label
variant="body"
className={`font-medium ${disabled ? 'ui-label-disabled' : 'ui-label'}`}
>
{label}
</Label>
) : null}
<div className={resolvedEditorWrapperClassName} style={resolvedEditorWrapperStyle}> <div className={resolvedEditorWrapperClassName} style={resolvedEditorWrapperStyle}>
<MDXEditor <MDXEditor
key={editorModeKey} key={editorModeKey}
@@ -59,7 +67,11 @@ export function MDXEditorField({
plugins={plugins} plugins={plugins}
/> />
</div> </div>
{error ? <Label variant="error" className="mt-2 ui-error">{error}</Label> : null} {error ? (
<Label variant="error" className="mt-2 ui-error">
{error}
</Label>
) : null}
</div> </div>
); );
} }

View File

@@ -10,43 +10,44 @@ const meta = {
layout: 'padded', layout: 'padded',
docs: { docs: {
description: { description: {
component: 'Sidebar navigation link with active state styling and collapsed/expanded rendering mode.' component:
} 'Sidebar navigation link with active state styling and collapsed/expanded rendering mode.',
} },
},
}, },
argTypes: { argTypes: {
to: { to: {
description: 'Destination route path.', description: 'Destination route path.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
label: { label: {
description: 'Navigation item label.', description: 'Navigation item label.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
icon: { icon: {
description: 'Icon component rendered before the label.', description: 'Icon component rendered before the label.',
control: false, control: false,
table: { type: { summary: 'ComponentType<SVGProps<SVGSVGElement>>' } } table: { type: { summary: 'ComponentType<SVGProps<SVGSVGElement>>' } },
}, },
collapsed: { collapsed: {
description: 'Collapsed state. When true, desktop view shows icon-only rail style.', description: 'Collapsed state. When true, desktop view shows icon-only rail style.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
onClick: { onClick: {
description: 'Optional click callback (for example to close mobile drawer).', description: 'Optional click callback (for example to close mobile drawer).',
action: 'clicked', action: 'clicked',
table: { type: { summary: '() => void' } } table: { type: { summary: '() => void' } },
} },
}, },
args: { args: {
to: '/', to: '/',
label: 'Dashboard', label: 'Dashboard',
icon: HomeIcon, icon: HomeIcon,
collapsed: false collapsed: false,
} },
} satisfies Meta<typeof SidebarNavItem>; } satisfies Meta<typeof SidebarNavItem>;
export default meta; export default meta;
@@ -57,14 +58,19 @@ export const Expanded: Story = {
<nav className="flex w-56 flex-col gap-1"> <nav className="flex w-56 flex-col gap-1">
<SidebarNavItem {...args} /> <SidebarNavItem {...args} />
<SidebarNavItem to="/users" label="Users" icon={UsersIcon} collapsed={args.collapsed} /> <SidebarNavItem to="/users" label="Users" icon={UsersIcon} collapsed={args.collapsed} />
<SidebarNavItem to="/profile" label="Profile" icon={UserCircleIcon} collapsed={args.collapsed} /> <SidebarNavItem
to="/profile"
label="Profile"
icon={UserCircleIcon}
collapsed={args.collapsed}
/>
</nav> </nav>
) ),
}; };
export const Collapsed: Story = { export const Collapsed: Story = {
args: { args: {
collapsed: true collapsed: true,
}, },
render: (args) => ( render: (args) => (
<nav className="flex w-14 flex-col gap-1"> <nav className="flex w-14 flex-col gap-1">
@@ -72,5 +78,5 @@ export const Collapsed: Story = {
<SidebarNavItem to="/users" label="Users" icon={UsersIcon} collapsed /> <SidebarNavItem to="/users" label="Users" icon={UsersIcon} collapsed />
<SidebarNavItem to="/profile" label="Profile" icon={UserCircleIcon} collapsed /> <SidebarNavItem to="/profile" label="Profile" icon={UserCircleIcon} collapsed />
</nav> </nav>
) ),
}; };

View File

@@ -11,7 +11,13 @@ type SidebarNavItemProps = {
onClick?: () => void; onClick?: () => void;
}; };
export function SidebarNavItem({ to, label, icon: Icon, collapsed, onClick }: Readonly<SidebarNavItemProps>) { export function SidebarNavItem({
to,
label,
icon: Icon,
collapsed,
onClick,
}: Readonly<SidebarNavItemProps>) {
const layoutClass = collapsed const layoutClass = collapsed
? 'mx-auto w-8 justify-center px-0' ? 'mx-auto w-8 justify-center px-0'
: 'px-2 lg:w-full lg:justify-start'; : 'px-2 lg:w-full lg:justify-start';
@@ -20,14 +26,18 @@ export function SidebarNavItem({ to, label, icon: Icon, collapsed, onClick }: Re
<NavLink <NavLink
to={to} to={to}
onClick={onClick} onClick={onClick}
className={({ isActive }) => ( className={({ isActive }) =>
`inline-flex h-8 items-center rounded-lg text-sm font-medium transition ${layoutClass} ${ `inline-flex h-8 items-center rounded-lg text-sm font-medium transition ${layoutClass} ${
isActive ? 'bg-accent-500 text-white' : 'ui-body-secondary hover:bg-zinc-500/15' isActive ? 'ui-accent-active' : 'ui-body-secondary hover:bg-zinc-500/15'
}` }`
)} }
> >
<Icon className="h-4 w-4 shrink-0" /> <Icon className="h-4 w-4 shrink-0" />
{!collapsed ? <span className="ml-2 truncate leading-none">{label}</span> : <span className="ml-2 lg:hidden">{label}</span>} {!collapsed ? (
<span className="ml-2 truncate leading-none">{label}</span>
) : (
<span className="ml-2 lg:hidden">{label}</span>
)}
</NavLink> </NavLink>
); );
} }

View File

@@ -20,7 +20,7 @@ const rows: UserRow[] = [
{ id: '5', name: 'Andrea Pini', role: 'EDITOR', status: 'Pending', posts: 9 }, { id: '5', name: 'Andrea Pini', role: 'EDITOR', status: 'Pending', posts: 9 },
{ id: '6', name: 'Sofia Denti', role: 'AUTHOR', status: 'Active', posts: 7 }, { id: '6', name: 'Sofia Denti', role: 'AUTHOR', status: 'Active', posts: 7 },
{ id: '7', name: 'Marco Serra', role: 'AUTHOR', status: 'Active', posts: 18 }, { id: '7', name: 'Marco Serra', role: 'AUTHOR', status: 'Active', posts: 18 },
{ id: '8', name: 'Elena Neri', role: 'EDITOR', status: 'Active', posts: 31 } { id: '8', name: 'Elena Neri', role: 'EDITOR', status: 'Active', posts: 31 },
]; ];
const headers: TableHeader<UserRow>[] = [ const headers: TableHeader<UserRow>[] = [
@@ -30,14 +30,14 @@ const headers: TableHeader<UserRow>[] = [
value: (row) => row.name, value: (row) => row.name,
sortable: true, sortable: true,
sortField: 'name', sortField: 'name',
cellClassName: 'table-cell-primary' cellClassName: 'table-cell-primary',
}, },
{ {
id: 'role', id: 'role',
label: 'Role', label: 'Role',
value: (row) => row.role, value: (row) => row.role,
sortable: true, sortable: true,
sortField: 'role' sortField: 'role',
}, },
{ {
id: 'status', id: 'status',
@@ -46,15 +46,15 @@ const headers: TableHeader<UserRow>[] = [
<Chip variant="outlined" tone={row.status === 'Active' ? 'indigo-700' : 'cyan-700'}> <Chip variant="outlined" tone={row.status === 'Active' ? 'indigo-700' : 'cyan-700'}>
{row.status} {row.status}
</Chip> </Chip>
) ),
}, },
{ {
id: 'posts', id: 'posts',
label: 'Posts', label: 'Posts',
value: (row) => row.posts, value: (row) => row.posts,
sortable: true, sortable: true,
sortField: 'posts' sortField: 'posts',
} },
]; ];
type UsersTableProps = { type UsersTableProps = {
@@ -117,49 +117,51 @@ const meta = {
parameters: { parameters: {
docs: { docs: {
description: { description: {
component: 'Generic data table with loading/empty states, optional sorting controls, and optional pagination footer.' component:
} 'Generic data table with loading/empty states, optional sorting controls, and optional pagination footer.',
} },
},
}, },
argTypes: { argTypes: {
data: { data: {
description: 'Rows rendered in the table body.', description: 'Rows rendered in the table body.',
control: 'object', control: 'object',
table: { type: { summary: 'UserRow[]' } } table: { type: { summary: 'UserRow[]' } },
}, },
isLoading: { isLoading: {
description: 'When true, shows the loading indicator row.', description: 'When true, shows the loading indicator row.',
control: 'boolean', control: 'boolean',
table: { type: { summary: 'boolean' } } table: { type: { summary: 'boolean' } },
}, },
emptyMessage: { emptyMessage: {
description: 'Message shown when `data` is empty and `isLoading` is false.', description: 'Message shown when `data` is empty and `isLoading` is false.',
control: 'text', control: 'text',
table: { type: { summary: 'string' } } table: { type: { summary: 'string' } },
}, },
sorting: { sorting: {
description: "Current sort state object. Use `null` for no active sorting.", description: 'Current sort state object. Use `null` for no active sorting.',
control: 'object', control: 'object',
table: { type: { summary: "{ field: string; direction: 'asc' | 'desc' } | null" } } table: { type: { summary: "{ field: string; direction: 'asc' | 'desc' } | null" } },
}, },
onSortChange: { onSortChange: {
description: 'Callback fired when a sortable header is clicked.', description: 'Callback fired when a sortable header is clicked.',
action: 'sort changed', action: 'sort changed',
table: { type: { summary: '(field: string) => void' } } table: { type: { summary: '(field: string) => void' } },
}, },
pagination: { pagination: {
description: 'Pagination config object. When omitted, pagination footer is hidden.', description: 'Pagination config object. When omitted, pagination footer is hidden.',
control: 'object', control: 'object',
table: { table: {
type: { type: {
summary: '{ page; pageSize; total; totalPages; onPageChange; onPageSizeChange? }' summary:
} '{ page; pageSize; total; totalPages; onPageChange; onPageSizeChange? }',
} },
} },
},
}, },
args: { args: {
data: rows data: rows,
} },
} satisfies Meta<typeof UsersTable>; } satisfies Meta<typeof UsersTable>;
export default meta; export default meta;
@@ -169,22 +171,22 @@ export const WithRows: Story = {};
export const Loading: Story = { export const Loading: Story = {
args: { args: {
isLoading: true isLoading: true,
} },
}; };
export const Empty: Story = { export const Empty: Story = {
args: { args: {
data: [], data: [],
emptyMessage: 'No users found' emptyMessage: 'No users found',
} },
}; };
export const InteractiveSortingAndPagination: Story = { export const InteractiveSortingAndPagination: Story = {
render: () => { render: () => {
const [sorting, setSorting] = useState<SortState | null>({ const [sorting, setSorting] = useState<SortState | null>({
field: 'name', field: 'name',
direction: 'asc' direction: 'asc',
}); });
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(5); const [pageSize, setPageSize] = useState(5);
@@ -220,9 +222,9 @@ export const InteractiveSortingAndPagination: Story = {
onPageSizeChange: (next) => { onPageSizeChange: (next) => {
setPage(1); setPage(1);
setPageSize(next); setPageSize(next);
} },
}} }}
/> />
); );
} },
}; };

View File

@@ -1,5 +1,11 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { ArrowPathIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon } from '@heroicons/react/24/solid'; import {
ArrowPathIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUpIcon,
} from '@heroicons/react/24/solid';
import { ArrowsUpDownIcon } from '@heroicons/react/24/outline'; import { ArrowsUpDownIcon } from '@heroicons/react/24/outline';
import { Button } from './Button'; import { Button } from './Button';
import { Dropdown } from './Dropdown'; import { Dropdown } from './Dropdown';
@@ -46,7 +52,7 @@ export function Table<T>({
className = '', className = '',
sorting = null, sorting = null,
onSortChange, onSortChange,
pagination pagination,
}: Readonly<TableProps<T>>) { }: Readonly<TableProps<T>>) {
const canGoPrev = pagination != null && pagination.page > 1; const canGoPrev = pagination != null && pagination.page > 1;
const canGoNext = pagination != null && pagination.page < pagination.totalPages; const canGoNext = pagination != null && pagination.page < pagination.totalPages;
@@ -58,24 +64,34 @@ export function Table<T>({
<thead className="table-head"> <thead className="table-head">
<tr> <tr>
{headers.map((header) => { {headers.map((header) => {
const canSort = header.sortable === true const canSort =
&& typeof onSortChange === 'function' header.sortable === true &&
&& typeof header.sortField === 'string' typeof onSortChange === 'function' &&
&& header.sortField.length > 0; typeof header.sortField === 'string' &&
header.sortField.length > 0;
const isActiveSort = canSort && sorting?.field === header.sortField; const isActiveSort = canSort && sorting?.field === header.sortField;
const sortDirection = isActiveSort ? sorting?.direction : null; const sortDirection = isActiveSort ? sorting?.direction : null;
return ( return (
<th key={header.id} className={`table-head-cell ${header.headerClassName ?? ''}`.trim()}> <th
key={header.id}
className={`table-head-cell ${header.headerClassName ?? ''}`.trim()}
>
{canSort ? ( {canSort ? (
<button <button
type="button" type="button"
className="table-sort-button" className="table-sort-button"
onClick={() => onSortChange(header.sortField as string)} onClick={() =>
onSortChange(header.sortField as string)
}
aria-label={`Sort by ${header.label}`} aria-label={`Sort by ${header.label}`}
> >
<span>{header.label}</span> <span>{header.label}</span>
<span className="table-sort-icon" aria-hidden="true" data-sort-state={sortDirection ?? 'none'}> <span
className="table-sort-icon"
aria-hidden="true"
data-sort-state={sortDirection ?? 'none'}
>
{sortDirection === 'asc' ? ( {sortDirection === 'asc' ? (
<ChevronUpIcon className="h-4 w-4" /> <ChevronUpIcon className="h-4 w-4" />
) : null} ) : null}
@@ -99,8 +115,15 @@ export function Table<T>({
{isLoading ? ( {isLoading ? (
<tr className="table-body-row"> <tr className="table-body-row">
<td colSpan={headers.length} className="px-4 py-6 text-center"> <td colSpan={headers.length} className="px-4 py-6 text-center">
<Label as="span" variant="body2" className="inline-flex items-center justify-center ui-loading"> <Label
<ArrowPathIcon className="h-5 w-5 animate-spin" aria-hidden="true" /> as="span"
variant="body2"
className="inline-flex items-center justify-center ui-loading"
>
<ArrowPathIcon
className="h-5 w-5 animate-spin"
aria-hidden="true"
/>
</Label> </Label>
</td> </td>
</tr> </tr>
@@ -114,14 +137,19 @@ export function Table<T>({
</td> </td>
</tr> </tr>
) : null} ) : null}
{!isLoading && data.map((row, index) => ( {!isLoading &&
data.map((row, index) => (
<tr key={rowKey(row, index)} className="table-body-row"> <tr key={rowKey(row, index)} className="table-body-row">
{headers.map((header) => { {headers.map((header) => {
const content = typeof header.value === 'function' const content =
typeof header.value === 'function'
? (header.value as (item: T) => ReactNode)(row) ? (header.value as (item: T) => ReactNode)(row)
: header.value; : header.value;
return ( return (
<td key={`${header.id}-${index}`} className={`table-cell-secondary ${header.cellClassName ?? ''}`.trim()}> <td
key={`${header.id}-${index}`}
className={`table-cell-secondary ${header.cellClassName ?? ''}`.trim()}
>
{content} {content}
</td> </td>
); );
@@ -133,9 +161,7 @@ export function Table<T>({
</div> </div>
{pagination ? ( {pagination ? (
<div className="flex flex-col gap-3 border-t border-zinc-500/20 px-4 py-3 sm:flex-row sm:items-center sm:justify-between"> <div className="flex flex-col gap-3 border-t border-zinc-500/20 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
<Label variant="body2"> <Label variant="body2">{pagination.total} results</Label>
{pagination.total} results
</Label>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{pagination.onPageSizeChange ? ( {pagination.onPageSizeChange ? (
<Dropdown <Dropdown
@@ -143,7 +169,7 @@ export function Table<T>({
value={String(pagination.pageSize)} value={String(pagination.pageSize)}
choices={[5, 10, 20, 50, 100].map((size) => ({ choices={[5, 10, 20, 50, 100].map((size) => ({
id: String(size), id: String(size),
label: String(size) label: String(size),
}))} }))}
size="sm" size="sm"
layout="inline" layout="inline"

View File

@@ -1,5 +1,6 @@
export { Button } from './components/Button'; export { Button } from './components/Button';
export { Chip } from './components/Chip'; export { Chip } from './components/Chip';
export { DatePicker } from './components/DatePicker';
export { Dropdown } from './components/Dropdown'; export { Dropdown } from './components/Dropdown';
export { Form } from './components/Form'; export { Form } from './components/Form';
export { InputField } from './components/InputField'; export { InputField } from './components/InputField';
@@ -8,5 +9,6 @@ export { SidebarNavItem } from './components/SidebarNavItem';
export { Table } from './components/Table'; export { Table } from './components/Table';
export type { TableHeader } from './components/Table'; export type { TableHeader } from './components/Table';
export type { DatePickerProps } from './components/DatePicker';
export type { ComponentSize } from './components/types'; export type { ComponentSize } from './components/types';
export type { SortDirection, SortState } from './types/sort'; export type { SortDirection, SortState } from './types/sort';

View File

@@ -1,6 +1,12 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root { :root {
/* Consumer projects can override these accent tokens after importing @panic/web-ui styles. */
--accent-300: 168 155 191;
--accent-400: 149 135 173;
--accent-500: 125 111 152;
--accent-600: 106 93 132;
--accent-contrast: 255 255 255;
--bg-page: #16121a; --bg-page: #16121a;
--surface-bg: rgba(24, 24, 27, 0.45); --surface-bg: rgba(24, 24, 27, 0.45);
--surface-bg-strong: rgba(24, 24, 27, 0.62); --surface-bg-strong: rgba(24, 24, 27, 0.62);
@@ -16,6 +22,8 @@
--field-disabled-border: #3f3f46; --field-disabled-border: #3f3f46;
--field-disabled-text: #bbb6c3; --field-disabled-text: #bbb6c3;
--field-disabled-placeholder: #71717a; --field-disabled-placeholder: #71717a;
--field-selection-bg: rgb(var(--accent-500) / 0.42);
--field-selection-text: var(--text-primary);
--ghost-bg: rgba(24, 24, 27, 0.5); --ghost-bg: rgba(24, 24, 27, 0.5);
--ghost-border: #3f3f46; --ghost-border: #3f3f46;
--ghost-hover: rgba(39, 39, 42, 0.7); --ghost-hover: rgba(39, 39, 42, 0.7);
@@ -33,8 +41,8 @@
--error-border: rgba(252, 165, 165, 0.3); --error-border: rgba(252, 165, 165, 0.3);
--error-bg: rgba(239, 68, 68, 0.1); --error-bg: rgba(239, 68, 68, 0.1);
--error-text: #fecaca; --error-text: #fecaca;
--mdx-link: #7d6f98; --mdx-link: rgb(var(--accent-500));
--mdx-link-hover: #9587ad; --mdx-link-hover: rgb(var(--accent-400));
--mdx-inline-code-bg: #27272a; --mdx-inline-code-bg: #27272a;
--mdx-inline-code-border: #3f3f46; --mdx-inline-code-border: #3f3f46;
--mdx-codeblock-bg: #18181b; --mdx-codeblock-bg: #18181b;
@@ -42,8 +50,8 @@
--mdx-codeblock-text: #e4e4e7; --mdx-codeblock-text: #e4e4e7;
--mdx-codeblock-gutter: #a1a1aa; --mdx-codeblock-gutter: #a1a1aa;
--mdx-codeblock-active: #27272a; --mdx-codeblock-active: #27272a;
--mdx-codeblock-selection: rgba(125, 111, 152, 0.35); --mdx-codeblock-selection: rgb(var(--accent-500) / 0.35);
--mdx-codeblock-bracket: rgba(125, 111, 152, 0.45); --mdx-codeblock-bracket: rgb(var(--accent-500) / 0.45);
--shadow-glow: 0 0 0 1px rgba(63, 63, 70, 0.65), 0 18px 44px rgba(0, 0, 0, 0.45); --shadow-glow: 0 0 0 1px rgba(63, 63, 70, 0.65), 0 18px 44px rgba(0, 0, 0, 0.45);
} }
@@ -63,6 +71,8 @@
--field-disabled-border: #d7d7d7; --field-disabled-border: #d7d7d7;
--field-disabled-text: #71717a; --field-disabled-text: #71717a;
--field-disabled-placeholder: #a1a1aa; --field-disabled-placeholder: #a1a1aa;
--field-selection-bg: rgb(var(--accent-500) / 0.24);
--field-selection-text: var(--text-primary);
--ghost-bg: rgba(255, 255, 255, 0.88); --ghost-bg: rgba(255, 255, 255, 0.88);
--ghost-border: #d4d4d8; --ghost-border: #d4d4d8;
--ghost-hover: #f4f4f5; --ghost-hover: #f4f4f5;
@@ -78,8 +88,8 @@
--error-border: rgba(248, 113, 113, 0.35); --error-border: rgba(248, 113, 113, 0.35);
--error-bg: rgba(254, 226, 226, 0.8); --error-bg: rgba(254, 226, 226, 0.8);
--error-text: #991b1b; --error-text: #991b1b;
--mdx-link: #7d6f98; --mdx-link: rgb(var(--accent-500));
--mdx-link-hover: #6a5d84; --mdx-link-hover: rgb(var(--accent-600));
--mdx-inline-code-bg: #f4f4f5; --mdx-inline-code-bg: #f4f4f5;
--mdx-inline-code-border: #d4d4d8; --mdx-inline-code-border: #d4d4d8;
--mdx-codeblock-bg: #ffffff; --mdx-codeblock-bg: #ffffff;
@@ -87,8 +97,7 @@
--mdx-codeblock-text: #18181b; --mdx-codeblock-text: #18181b;
--mdx-codeblock-gutter: #71717a; --mdx-codeblock-gutter: #71717a;
--mdx-codeblock-active: #f4f4f5; --mdx-codeblock-active: #f4f4f5;
--mdx-codeblock-selection: rgba(125, 111, 152, 0.22); --mdx-codeblock-selection: rgb(var(--accent-500) / 0.22);
--mdx-codeblock-bracket: rgba(125, 111, 152, 0.32); --mdx-codeblock-bracket: rgb(var(--accent-500) / 0.32);
--shadow-glow: 0 0 0 1px rgba(212, 212, 216, 0.9), 0 18px 36px rgba(15, 23, 42, 0.08); --shadow-glow: 0 0 0 1px rgba(212, 212, 216, 0.9), 0 18px 36px rgba(15, 23, 42, 0.08);
} }

View File

@@ -9,23 +9,118 @@
border: 1px solid var(--field-border); border: 1px solid var(--field-border);
background-color: var(--field-bg); background-color: var(--field-bg);
color: var(--text-primary); color: var(--text-primary);
@apply w-full rounded-xl px-3 py-2 text-sm outline-none transition focus:border-accent-400 focus:ring-2 focus:ring-accent-400/30; appearance: none;
-webkit-appearance: none;
-webkit-text-fill-color: var(--text-primary);
@apply w-full rounded-xl px-3 py-2 text-sm outline-none transition;
}
.field:focus {
border-color: rgb(var(--accent-400));
box-shadow: 0 0 0 2px rgb(var(--accent-400) / 0.3);
} }
.field::placeholder { .field::placeholder {
color: var(--text-soft); color: var(--text-soft);
} }
.field::selection {
background-color: var(--field-selection-bg);
color: var(--field-selection-text);
-webkit-text-fill-color: var(--field-selection-text);
}
.field[type='date']::-webkit-datetime-edit-day-field,
.field[type='date']::-webkit-datetime-edit-month-field,
.field[type='date']::-webkit-datetime-edit-year-field,
.field[type='time']::-webkit-datetime-edit-hour-field,
.field[type='time']::-webkit-datetime-edit-minute-field,
.field[type='datetime-local']::-webkit-datetime-edit-day-field,
.field[type='datetime-local']::-webkit-datetime-edit-month-field,
.field[type='datetime-local']::-webkit-datetime-edit-year-field,
.field[type='datetime-local']::-webkit-datetime-edit-hour-field,
.field[type='datetime-local']::-webkit-datetime-edit-minute-field {
border-radius: 0.375rem;
}
.field[type='date']::-webkit-datetime-edit-day-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='date']::-webkit-datetime-edit-month-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='date']::-webkit-datetime-edit-year-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='time']::-webkit-datetime-edit-hour-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='time']::-webkit-datetime-edit-minute-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='datetime-local']::-webkit-datetime-edit-day-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='datetime-local']::-webkit-datetime-edit-month-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='datetime-local']::-webkit-datetime-edit-year-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='datetime-local']::-webkit-datetime-edit-hour-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field[type='datetime-local']::-webkit-datetime-edit-minute-field:focus {
border-radius: 0.375rem;
background-color: var(--field-selection-bg) !important;
color: var(--field-selection-text) !important;
-webkit-text-fill-color: var(--field-selection-text) !important;
}
.field:disabled { .field:disabled {
border-color: var(--field-disabled-border); border-color: var(--field-disabled-border);
background-color: var(--field-disabled-bg); background-color: var(--field-disabled-bg);
color: var(--field-disabled-text); color: var(--field-disabled-text);
-webkit-text-fill-color: var(--field-disabled-text);
} }
.field:disabled::placeholder { .field:disabled::placeholder {
color: var(--field-disabled-placeholder); color: var(--field-disabled-placeholder);
} }
.btn-solid:disabled,
.btn-outlined:disabled,
.btn-noborder:disabled {
opacity: 1;
}
.btn-solid, .btn-solid,
.btn-outlined, .btn-outlined,
.btn-noborder { .btn-noborder {
@@ -45,7 +140,13 @@
} }
.btn-solid.btn-primary { .btn-solid.btn-primary {
@apply bg-accent-500 text-white hover:bg-accent-400 disabled:opacity-100; background-color: rgb(var(--accent-500));
color: rgb(var(--accent-contrast));
@apply disabled:opacity-100;
}
.btn-solid.btn-primary:hover {
background-color: rgb(var(--accent-400));
} }
.btn-solid.btn-primary:disabled { .btn-solid.btn-primary:disabled {
@@ -97,16 +198,19 @@
} }
.btn-outlined.btn-primary { .btn-outlined.btn-primary {
@apply border-accent-500 text-accent-300; border-color: rgb(var(--accent-500));
color: rgb(var(--accent-300));
background-color: transparent; background-color: transparent;
} }
.btn-outlined.btn-primary:hover { .btn-outlined.btn-primary:hover {
@apply bg-accent-500/15 text-accent-300; background-color: rgb(var(--accent-500) / 0.15);
color: rgb(var(--accent-300));
} }
.btn-outlined.btn-primary:disabled { .btn-outlined.btn-primary:disabled {
@apply border-accent-500/40 text-accent-300/60; border-color: rgb(var(--accent-500) / 0.4);
color: rgb(var(--accent-300) / 0.6);
background-color: transparent; background-color: transparent;
} }
@@ -139,16 +243,17 @@
} }
.btn-noborder.btn-primary { .btn-noborder.btn-primary {
@apply text-accent-300; color: rgb(var(--accent-300));
background-color: transparent; background-color: transparent;
} }
.btn-noborder.btn-primary:hover { .btn-noborder.btn-primary:hover {
@apply bg-accent-500/15 text-accent-300; background-color: rgb(var(--accent-500) / 0.15);
color: rgb(var(--accent-300));
} }
.btn-noborder.btn-primary:disabled { .btn-noborder.btn-primary:disabled {
@apply text-accent-300/60; color: rgb(var(--accent-300) / 0.6);
background-color: transparent; background-color: transparent;
} }
@@ -218,6 +323,15 @@
color: var(--error-text); color: var(--error-text);
} }
.ui-accent-active {
background-color: rgb(var(--accent-500));
color: rgb(var(--accent-contrast));
}
.ui-accent-active:hover {
background-color: rgb(var(--accent-400));
}
.chip-root { .chip-root {
@apply inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-semibold leading-none; @apply inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-semibold leading-none;
} }

View File

@@ -7,15 +7,15 @@ module.exports = {
300: '#a89bbf', 300: '#a89bbf',
400: '#9587ad', 400: '#9587ad',
500: '#7d6f98', 500: '#7d6f98',
600: '#6a5d84' 600: '#6a5d84',
} },
}, },
fontFamily: { fontFamily: {
sans: ['Inter', 'ui-sans-serif', 'system-ui', 'sans-serif'] sans: ['Inter', 'ui-sans-serif', 'system-ui', 'sans-serif'],
}, },
boxShadow: { boxShadow: {
glow: '0 0 0 1px rgba(63,63,70,0.65), 0 18px 44px rgba(0,0,0,0.45)' glow: '0 0 0 1px rgba(63,63,70,0.65), 0 18px 44px rgba(0,0,0,0.45)',
} },
} },
} },
}; };

View File

@@ -3,14 +3,12 @@ const webUiPreset = require('./tailwind-preset.cjs');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
presets: [webUiPreset], presets: [webUiPreset],
content: [ content: ['./src/**/*.{ts,tsx,js,jsx}'],
'./src/**/*.{ts,tsx,js,jsx}'
],
corePlugins: { corePlugins: {
preflight: false preflight: false,
}, },
theme: { theme: {
extend: {} extend: {},
}, },
plugins: [] plugins: [],
}; };

View File

@@ -3,12 +3,9 @@ const webUiPreset = require('./tailwind-preset.cjs');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
presets: [webUiPreset], presets: [webUiPreset],
content: [ content: ['./src/**/*.{ts,tsx,js,jsx,mdx}', './.storybook/**/*.{ts,tsx,js,jsx,mdx}'],
'./src/**/*.{ts,tsx,js,jsx,mdx}',
'./.storybook/**/*.{ts,tsx,js,jsx,mdx}'
],
theme: { theme: {
extend: {} extend: {},
}, },
plugins: [] plugins: [],
}; };

View File

@@ -8,11 +8,14 @@ export default defineConfig({
lib: { lib: {
entry: { entry: {
index: resolve(__dirname, 'src/index.ts'), index: resolve(__dirname, 'src/index.ts'),
'components/MDXEditorField': resolve(__dirname, 'src/components/MDXEditorField.tsx') 'components/MDXEditorField': resolve(
__dirname,
'src/components/MDXEditorField.tsx',
),
}, },
name: 'PanicWebUi', name: 'PanicWebUi',
formats: ['es'], formats: ['es'],
fileName: (_format, entryName) => `${entryName}.js` fileName: (_format, entryName) => `${entryName}.js`,
}, },
rollupOptions: { rollupOptions: {
external: [ external: [
@@ -20,8 +23,8 @@ export default defineConfig({
'react-dom', 'react-dom',
'react-router-dom', 'react-router-dom',
'@heroicons/react', '@heroicons/react',
'@mdxeditor/editor' '@mdxeditor/editor',
] ],
} },
} },
}); });

575
yarn.lock
View File

@@ -26,7 +26,7 @@
resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d"
integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==
"@babel/core@^7.28.0", "@babel/core@^7.29.0": "@babel/core@^7.24.4", "@babel/core@^7.28.0", "@babel/core@^7.29.0":
version "7.29.0" version "7.29.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322"
integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==
@@ -119,7 +119,7 @@
"@babel/template" "^7.28.6" "@babel/template" "^7.28.6"
"@babel/types" "^7.28.6" "@babel/types" "^7.28.6"
"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": "@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.4", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0":
version "7.29.0" version "7.29.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6"
integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==
@@ -699,6 +699,59 @@
resolved "https://nexus.beatrice.wtf/repository/npm-group/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" resolved "https://nexus.beatrice.wtf/repository/npm-group/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17"
integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==
"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1":
version "4.9.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.12.2":
version "4.12.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
"@eslint/config-array@^0.23.2":
version "0.23.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/config-array/-/config-array-0.23.2.tgz#db85beeff7facc685a5775caacb1c845669b9470"
integrity sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==
dependencies:
"@eslint/object-schema" "^3.0.2"
debug "^4.3.1"
minimatch "^10.2.1"
"@eslint/config-helpers@^0.5.2":
version "0.5.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d"
integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==
dependencies:
"@eslint/core" "^1.1.0"
"@eslint/core@^1.1.0":
version "1.1.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d"
integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==
dependencies:
"@types/json-schema" "^7.0.15"
"@eslint/js@^10":
version "10.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583"
integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==
"@eslint/object-schema@^3.0.2":
version "3.0.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/object-schema/-/object-schema-3.0.2.tgz#c59c6a94aa4b428ed7f1615b6a4495c0a21f7a22"
integrity sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==
"@eslint/plugin-kit@^0.6.0":
version "0.6.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz#e0cb12ec66719cb2211ad36499fb516f2a63899d"
integrity sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==
dependencies:
"@eslint/core" "^1.1.0"
levn "^0.4.1"
"@floating-ui/core@^1.7.4": "@floating-ui/core@^1.7.4":
version "1.7.4" version "1.7.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4" resolved "https://nexus.beatrice.wtf/repository/npm-group/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4"
@@ -740,6 +793,29 @@
resolved "https://nexus.beatrice.wtf/repository/npm-group/@heroicons/react/-/react-2.2.0.tgz#0c05124af50434a800773abec8d3af6a297d904b" resolved "https://nexus.beatrice.wtf/repository/npm-group/@heroicons/react/-/react-2.2.0.tgz#0c05124af50434a800773abec8d3af6a297d904b"
integrity sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ== integrity sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==
"@humanfs/core@^0.19.1":
version "0.19.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==
"@humanfs/node@^0.16.6":
version "0.16.7"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26"
integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==
dependencies:
"@humanfs/core" "^0.19.1"
"@humanwhocodes/retry" "^0.4.0"
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2":
version "0.4.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba"
integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
"@joshwooding/vite-plugin-react-docgen-typescript@^0.6.4": "@joshwooding/vite-plugin-react-docgen-typescript@^0.6.4":
version "0.6.4" version "0.6.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.4.tgz#9cfa58703ae8122329c52a5989244818ee4cdcbe" resolved "https://nexus.beatrice.wtf/repository/npm-group/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.4.tgz#9cfa58703ae8122329c52a5989244818ee4cdcbe"
@@ -1902,6 +1978,11 @@
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/doctrine/-/doctrine-0.0.9.tgz#d86a5f452a15e3e3113b99e39616a9baa0f9863f" resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/doctrine/-/doctrine-0.0.9.tgz#d86a5f452a15e3e3113b99e39616a9baa0f9863f"
integrity sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA== integrity sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==
"@types/esrecurse@^4.3.1":
version "4.3.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec"
integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==
"@types/estree-jsx@^1.0.0": "@types/estree-jsx@^1.0.0":
version "1.0.5" version "1.0.5"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
@@ -1909,7 +1990,7 @@
dependencies: dependencies:
"@types/estree" "*" "@types/estree" "*"
"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0": "@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8":
version "1.0.8" version "1.0.8"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
@@ -1921,6 +2002,11 @@
dependencies: dependencies:
"@types/unist" "*" "@types/unist" "*"
"@types/json-schema@^7.0.15":
version "7.0.15"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/mdast@^4.0.0": "@types/mdast@^4.0.0":
version "4.0.4" version "4.0.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
@@ -1965,6 +2051,102 @@
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
"@typescript-eslint/eslint-plugin@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz#5aec3db807a6b8437ea5d5ebf7bd16b4119aba8d"
integrity sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.56.0"
"@typescript-eslint/type-utils" "8.56.0"
"@typescript-eslint/utils" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.4.0"
"@typescript-eslint/parser@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/parser/-/parser-8.56.0.tgz#8ecff1678b8b1a742d29c446ccf5eeea7f971d72"
integrity sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==
dependencies:
"@typescript-eslint/scope-manager" "8.56.0"
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/typescript-estree" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
debug "^4.4.3"
"@typescript-eslint/project-service@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/project-service/-/project-service-8.56.0.tgz#bb8562fecd8f7922e676fc6a1189c20dd7991d73"
integrity sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.56.0"
"@typescript-eslint/types" "^8.56.0"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz#604030a4c6433df3728effdd441d47f45a86edb4"
integrity sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==
dependencies:
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
"@typescript-eslint/tsconfig-utils@8.56.0", "@typescript-eslint/tsconfig-utils@^8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz#2538ce83cbc376e685487960cbb24b65fe2abc4e"
integrity sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==
"@typescript-eslint/type-utils@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz#72b4edc1fc73988998f1632b3ec99c2a66eaac6e"
integrity sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==
dependencies:
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/typescript-estree" "8.56.0"
"@typescript-eslint/utils" "8.56.0"
debug "^4.4.3"
ts-api-utils "^2.4.0"
"@typescript-eslint/types@8.56.0", "@typescript-eslint/types@^8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/types/-/types-8.56.0.tgz#a2444011b9a98ca13d70411d2cbfed5443b3526a"
integrity sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==
"@typescript-eslint/typescript-estree@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz#fadbc74c14c5bac947db04980ff58bb178701c2e"
integrity sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==
dependencies:
"@typescript-eslint/project-service" "8.56.0"
"@typescript-eslint/tsconfig-utils" "8.56.0"
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
debug "^4.4.3"
minimatch "^9.0.5"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.4.0"
"@typescript-eslint/utils@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/utils/-/utils-8.56.0.tgz#063ce6f702ec603de1b83ee795ed5e877d6f7841"
integrity sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.56.0"
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/typescript-estree" "8.56.0"
"@typescript-eslint/visitor-keys@8.56.0":
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz#7d6592ab001827d3ce052155edf7ecad19688d7d"
integrity sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==
dependencies:
"@typescript-eslint/types" "8.56.0"
eslint-visitor-keys "^5.0.0"
"@vitejs/plugin-react@^5.0.0": "@vitejs/plugin-react@^5.0.0":
version "5.1.4" version "5.1.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz#5b477e060bf612a7394c4febacc5de33a219b0e4" resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz#5b477e060bf612a7394c4febacc5de33a219b0e4"
@@ -2011,16 +2193,26 @@
loupe "^3.1.4" loupe "^3.1.4"
tinyrainbow "^2.0.0" tinyrainbow "^2.0.0"
acorn-jsx@^5.0.0: acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
version "5.3.2" version "5.3.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.0.0, acorn@^8.15.0: acorn@^8.0.0, acorn@^8.15.0, acorn@^8.16.0:
version "8.16.0" version "8.16.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
ajv@^6.12.4:
version "6.14.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
anser@^2.1.1: anser@^2.1.1:
version "2.3.5" version "2.3.5"
resolved "https://nexus.beatrice.wtf/repository/npm-group/anser/-/anser-2.3.5.tgz#3435896b68b93e5e744842499d0ce3e6f6d013ee" resolved "https://nexus.beatrice.wtf/repository/npm-group/anser/-/anser-2.3.5.tgz#3435896b68b93e5e744842499d0ce3e6f6d013ee"
@@ -2274,6 +2466,15 @@ crelt@^1.0.5, crelt@^1.0.6:
resolved "https://nexus.beatrice.wtf/repository/npm-group/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" resolved "https://nexus.beatrice.wtf/repository/npm-group/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://nexus.beatrice.wtf/repository/npm-group/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
css.escape@^1.5.1: css.escape@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" resolved "https://nexus.beatrice.wtf/repository/npm-group/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
@@ -2297,7 +2498,7 @@ d@1, d@^1.0.1, d@^1.0.2:
es5-ext "^0.10.64" es5-ext "^0.10.64"
type "^2.7.2" type "^2.7.2"
debug@^4.0.0, debug@^4.1.0, debug@^4.3.1: debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.4.3:
version "4.4.3" version "4.4.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" resolved "https://nexus.beatrice.wtf/repository/npm-group/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
@@ -2316,6 +2517,11 @@ deep-eql@^5.0.1:
resolved "https://nexus.beatrice.wtf/repository/npm-group/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" resolved "https://nexus.beatrice.wtf/repository/npm-group/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341"
integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==
deep-is@^0.1.3:
version "0.1.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
default-browser-id@^5.0.0: default-browser-id@^5.0.0:
version "5.0.1" version "5.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/default-browser-id/-/default-browser-id-5.0.1.tgz#f7a7ccb8f5104bf8e0f71ba3b1ccfa5eafdb21e8" resolved "https://nexus.beatrice.wtf/repository/npm-group/default-browser-id/-/default-browser-id-5.0.1.tgz#f7a7ccb8f5104bf8e0f71ba3b1ccfa5eafdb21e8"
@@ -2478,11 +2684,88 @@ escape-carriage@^1.3.1:
resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-carriage/-/escape-carriage-1.3.1.tgz#842658e5422497b1232585e517dc813fc6a86170" resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-carriage/-/escape-carriage-1.3.1.tgz#842658e5422497b1232585e517dc813fc6a86170"
integrity sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw== integrity sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
escape-string-regexp@^5.0.0: escape-string-regexp@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
eslint-plugin-react-hooks@^7.0.1:
version "7.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz#66e258db58ece50723ef20cc159f8aa908219169"
integrity sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==
dependencies:
"@babel/core" "^7.24.4"
"@babel/parser" "^7.24.4"
hermes-parser "^0.25.1"
zod "^3.25.0 || ^4.0.0"
zod-validation-error "^3.5.0 || ^4.0.0"
eslint-plugin-react-refresh@^0.5.1:
version "0.5.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.1.tgz#d13f1cfea4718a108060a41219d1849287278adc"
integrity sha512-Y5sJsreCUdGcF4mLD70iJNa47Z6CX4MsqJoJBARDC/fBhmacSby7k73UuValr0F9M7GfWKpEqS4NMsniWkVxQw==
eslint-scope@^9.1.1:
version "9.1.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-scope/-/eslint-scope-9.1.1.tgz#f6a209486e38bd28356b5feb07d445cc99c89967"
integrity sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==
dependencies:
"@types/esrecurse" "^4.3.1"
"@types/estree" "^1.0.8"
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1:
version "5.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
eslint@^10:
version "10.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint/-/eslint-10.0.1.tgz#b5c5f7706782a21590ba6451e7a30d2947273c2d"
integrity sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
"@eslint/config-array" "^0.23.2"
"@eslint/config-helpers" "^0.5.2"
"@eslint/core" "^1.1.0"
"@eslint/plugin-kit" "^0.6.0"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
"@types/estree" "^1.0.6"
ajv "^6.12.4"
cross-spawn "^7.0.6"
debug "^4.3.2"
escape-string-regexp "^4.0.0"
eslint-scope "^9.1.1"
eslint-visitor-keys "^5.0.1"
espree "^11.1.1"
esquery "^1.7.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^8.0.0"
find-up "^5.0.0"
glob-parent "^6.0.2"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
json-stable-stringify-without-jsonify "^1.0.1"
minimatch "^10.2.1"
natural-compare "^1.4.0"
optionator "^0.9.3"
esniff@^2.0.1: esniff@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" resolved "https://nexus.beatrice.wtf/repository/npm-group/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
@@ -2493,11 +2776,39 @@ esniff@^2.0.1:
event-emitter "^0.3.5" event-emitter "^0.3.5"
type "^2.7.2" type "^2.7.2"
espree@^11.1.1:
version "11.1.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/espree/-/espree-11.1.1.tgz#866f6bc9ccccd6f28876b7a6463abb281b9cb847"
integrity sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==
dependencies:
acorn "^8.16.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^5.0.1"
esprima@~4.0.0: esprima@~4.0.0:
version "4.0.1" version "4.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" resolved "https://nexus.beatrice.wtf/repository/npm-group/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esquery@^1.7.0:
version "1.7.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==
dependencies:
estraverse "^5.1.0"
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
dependencies:
estraverse "^5.2.0"
estraverse@^5.1.0, estraverse@^5.2.0:
version "5.3.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
estree-util-is-identifier-name@^3.0.0: estree-util-is-identifier-name@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" resolved "https://nexus.beatrice.wtf/repository/npm-group/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd"
@@ -2536,6 +2847,11 @@ ext@^1.7.0:
dependencies: dependencies:
type "^2.7.2" type "^2.7.2"
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.3.2: fast-glob@^3.3.2:
version "3.3.3" version "3.3.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
@@ -2547,6 +2863,16 @@ fast-glob@^3.3.2:
merge2 "^1.3.0" merge2 "^1.3.0"
micromatch "^4.0.8" micromatch "^4.0.8"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@^2.0.6:
version "2.0.6"
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fastq@^1.6.0: fastq@^1.6.0:
version "1.20.1" version "1.20.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" resolved "https://nexus.beatrice.wtf/repository/npm-group/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675"
@@ -2566,6 +2892,13 @@ fdir@^6.5.0:
resolved "https://nexus.beatrice.wtf/repository/npm-group/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" resolved "https://nexus.beatrice.wtf/repository/npm-group/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
file-entry-cache@^8.0.0:
version "8.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
dependencies:
flat-cache "^4.0.0"
fill-range@^7.1.1: fill-range@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" resolved "https://nexus.beatrice.wtf/repository/npm-group/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -2573,6 +2906,27 @@ fill-range@^7.1.1:
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
find-up@^5.0.0:
version "5.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
flat-cache@^4.0.0:
version "4.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
dependencies:
flatted "^3.2.9"
keyv "^4.5.4"
flatted@^3.2.9:
version "3.3.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
format@^0.2.0: format@^0.2.0:
version "0.2.2" version "0.2.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" resolved "https://nexus.beatrice.wtf/repository/npm-group/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
@@ -2621,6 +2975,11 @@ glob@^13.0.1:
minipass "^7.1.3" minipass "^7.1.3"
path-scurry "^2.0.2" path-scurry "^2.0.2"
globals@^17.3.0:
version "17.3.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/globals/-/globals-17.3.0.tgz#8b96544c2fa91afada02747cc9731c002a96f3b9"
integrity sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==
hasown@^2.0.2: hasown@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" resolved "https://nexus.beatrice.wtf/repository/npm-group/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
@@ -2628,11 +2987,38 @@ hasown@^2.0.2:
dependencies: dependencies:
function-bind "^1.1.2" function-bind "^1.1.2"
hermes-estree@0.25.1:
version "0.25.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480"
integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==
hermes-parser@^0.25.1:
version "0.25.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1"
integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==
dependencies:
hermes-estree "0.25.1"
ieee754@^1.2.1: ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" resolved "https://nexus.beatrice.wtf/repository/npm-group/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0:
version "5.3.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
ignore@^7.0.5:
version "7.0.5"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9"
integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
indent-string@^4.0.0: indent-string@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" resolved "https://nexus.beatrice.wtf/repository/npm-group/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
@@ -2685,7 +3071,7 @@ is-extglob@^2.1.1:
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://nexus.beatrice.wtf/repository/npm-group/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3" version "4.0.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" resolved "https://nexus.beatrice.wtf/repository/npm-group/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@@ -2716,6 +3102,11 @@ is-wsl@^3.1.0:
dependencies: dependencies:
is-inside-container "^1.0.0" is-inside-container "^1.0.0"
isexe@^2.0.0:
version "2.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
isomorphic.js@^0.2.4: isomorphic.js@^0.2.4:
version "0.2.5" version "0.2.5"
resolved "https://nexus.beatrice.wtf/repository/npm-group/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88" resolved "https://nexus.beatrice.wtf/repository/npm-group/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88"
@@ -2743,16 +3134,46 @@ jsesc@^3.0.2:
resolved "https://nexus.beatrice.wtf/repository/npm-group/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" resolved "https://nexus.beatrice.wtf/repository/npm-group/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
json-buffer@3.0.1:
version "3.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json5@^2.2.2, json5@^2.2.3: json5@^2.2.2, json5@^2.2.3:
version "2.2.3" version "2.2.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" resolved "https://nexus.beatrice.wtf/repository/npm-group/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
keyv@^4.5.4:
version "4.5.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
dependencies:
json-buffer "3.0.1"
kleur@^4.0.3: kleur@^4.0.3:
version "4.1.5" version "4.1.5"
resolved "https://nexus.beatrice.wtf/repository/npm-group/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" resolved "https://nexus.beatrice.wtf/repository/npm-group/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
levn@^0.4.1:
version "0.4.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
dependencies:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lexical@0.35.0, lexical@^0.35.0: lexical@0.35.0, lexical@^0.35.0:
version "0.35.0" version "0.35.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/lexical/-/lexical-0.35.0.tgz#6e451aff97b88bc8cb5ba1312f225b62d01d2986" resolved "https://nexus.beatrice.wtf/repository/npm-group/lexical/-/lexical-0.35.0.tgz#6e451aff97b88bc8cb5ba1312f225b62d01d2986"
@@ -2775,6 +3196,13 @@ lines-and-columns@^1.1.6:
resolved "https://nexus.beatrice.wtf/repository/npm-group/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" resolved "https://nexus.beatrice.wtf/repository/npm-group/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
locate-path@^6.0.0:
version "6.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
longest-streak@^3.0.0: longest-streak@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" resolved "https://nexus.beatrice.wtf/repository/npm-group/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
@@ -3366,13 +3794,20 @@ min-indent@^1.0.0:
resolved "https://nexus.beatrice.wtf/repository/npm-group/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" resolved "https://nexus.beatrice.wtf/repository/npm-group/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
minimatch@^10.2.2: minimatch@^10.2.1, minimatch@^10.2.2:
version "10.2.2" version "10.2.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f" resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f"
integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw== integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==
dependencies: dependencies:
brace-expansion "^5.0.2" brace-expansion "^5.0.2"
minimatch@^9.0.5:
version "9.0.6"
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-9.0.6.tgz#a7e3bccfcb3d78ec1bf8d51c9ba749080237a5c8"
integrity sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==
dependencies:
brace-expansion "^5.0.2"
minimist@^1.2.6: minimist@^1.2.6:
version "1.2.8" version "1.2.8"
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" resolved "https://nexus.beatrice.wtf/repository/npm-group/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
@@ -3407,6 +3842,11 @@ nanoid@^3.3.11:
resolved "https://nexus.beatrice.wtf/repository/npm-group/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" resolved "https://nexus.beatrice.wtf/repository/npm-group/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
next-tick@^1.1.0: next-tick@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" resolved "https://nexus.beatrice.wtf/repository/npm-group/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@@ -3442,6 +3882,18 @@ open@^10.2.0:
is-inside-container "^1.0.0" is-inside-container "^1.0.0"
wsl-utils "^0.1.0" wsl-utils "^0.1.0"
optionator@^0.9.3:
version "0.9.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
dependencies:
deep-is "^0.1.3"
fast-levenshtein "^2.0.6"
levn "^0.4.1"
prelude-ls "^1.2.1"
type-check "^0.4.0"
word-wrap "^1.2.5"
outvariant@1.4.0: outvariant@1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/outvariant/-/outvariant-1.4.0.tgz#e742e4bda77692da3eca698ef5bfac62d9fba06e" resolved "https://nexus.beatrice.wtf/repository/npm-group/outvariant/-/outvariant-1.4.0.tgz#e742e4bda77692da3eca698ef5bfac62d9fba06e"
@@ -3452,6 +3904,20 @@ outvariant@^1.3.0, outvariant@^1.4.0:
resolved "https://nexus.beatrice.wtf/repository/npm-group/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873" resolved "https://nexus.beatrice.wtf/repository/npm-group/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873"
integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==
p-limit@^3.0.2:
version "3.1.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
parse-entities@^4.0.0: parse-entities@^4.0.0:
version "4.0.2" version "4.0.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" resolved "https://nexus.beatrice.wtf/repository/npm-group/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159"
@@ -3465,6 +3931,16 @@ parse-entities@^4.0.0:
is-decimal "^2.0.0" is-decimal "^2.0.0"
is-hexadecimal "^2.0.0" is-hexadecimal "^2.0.0"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-key@^3.1.0:
version "3.1.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.7: path-parse@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://nexus.beatrice.wtf/repository/npm-group/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://nexus.beatrice.wtf/repository/npm-group/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
@@ -3560,6 +4036,16 @@ postcss@^8.4.47, postcss@^8.4.49, postcss@^8.5.6:
picocolors "^1.1.1" picocolors "^1.1.1"
source-map-js "^1.2.1" source-map-js "^1.2.1"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@^3.8.1:
version "3.8.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
pretty-format@^27.0.2: pretty-format@^27.0.2:
version "27.5.1" version "27.5.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" resolved "https://nexus.beatrice.wtf/repository/npm-group/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
@@ -3583,6 +4069,11 @@ prop-types@^15.7.2:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.13.1" react-is "^16.13.1"
punycode@^2.1.0:
version "2.3.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
queue-microtask@^1.2.2: queue-microtask@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://nexus.beatrice.wtf/repository/npm-group/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -3817,6 +4308,18 @@ set-cookie-parser@^2.6.0:
resolved "https://nexus.beatrice.wtf/repository/npm-group/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" resolved "https://nexus.beatrice.wtf/repository/npm-group/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68"
integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
source-map-js@^1.2.1: source-map-js@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" resolved "https://nexus.beatrice.wtf/repository/npm-group/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -3985,6 +4488,11 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
ts-api-utils@^2.4.0:
version "2.4.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8"
integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==
ts-dedent@^2.0.0: ts-dedent@^2.0.0:
version "2.2.0" version "2.2.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
@@ -4009,11 +4517,28 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
resolved "https://nexus.beatrice.wtf/repository/npm-group/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" resolved "https://nexus.beatrice.wtf/repository/npm-group/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
dependencies:
prelude-ls "^1.2.1"
type@^2.7.2: type@^2.7.2:
version "2.7.3" version "2.7.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486" resolved "https://nexus.beatrice.wtf/repository/npm-group/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486"
integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==
typescript-eslint@^8.56.0:
version "8.56.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript-eslint/-/typescript-eslint-8.56.0.tgz#f4686ccaaf2fb86daf0133820da40ca5961a2236"
integrity sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==
dependencies:
"@typescript-eslint/eslint-plugin" "8.56.0"
"@typescript-eslint/parser" "8.56.0"
"@typescript-eslint/typescript-estree" "8.56.0"
"@typescript-eslint/utils" "8.56.0"
typescript@^5.6.2: typescript@^5.6.2:
version "5.9.3" version "5.9.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
@@ -4082,6 +4607,13 @@ update-browserslist-db@^1.2.0:
escalade "^3.2.0" escalade "^3.2.0"
picocolors "^1.1.1" picocolors "^1.1.1"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://nexus.beatrice.wtf/repository/npm-group/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
use-callback-ref@^1.3.3: use-callback-ref@^1.3.3:
version "1.3.3" version "1.3.3"
resolved "https://nexus.beatrice.wtf/repository/npm-group/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" resolved "https://nexus.beatrice.wtf/repository/npm-group/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf"
@@ -4149,6 +4681,18 @@ webpack-virtual-modules@^0.6.2:
resolved "https://nexus.beatrice.wtf/repository/npm-group/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" resolved "https://nexus.beatrice.wtf/repository/npm-group/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
which@^2.0.1:
version "2.0.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
word-wrap@^1.2.5:
version "1.2.5"
resolved "https://nexus.beatrice.wtf/repository/npm-group/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
ws@^8.18.0: ws@^8.18.0:
version "8.19.0" version "8.19.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" resolved "https://nexus.beatrice.wtf/repository/npm-group/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b"
@@ -4173,6 +4717,21 @@ yjs@^13.6.24:
dependencies: dependencies:
lib0 "^0.2.99" lib0 "^0.2.99"
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://nexus.beatrice.wtf/repository/npm-group/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
"zod-validation-error@^3.5.0 || ^4.0.0":
version "4.0.2"
resolved "https://nexus.beatrice.wtf/repository/npm-group/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918"
integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==
"zod@^3.25.0 || ^4.0.0":
version "4.3.6"
resolved "https://nexus.beatrice.wtf/repository/npm-group/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a"
integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==
zwitch@^2.0.0: zwitch@^2.0.0:
version "2.0.4" version "2.0.4"
resolved "https://nexus.beatrice.wtf/repository/npm-group/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" resolved "https://nexus.beatrice.wtf/repository/npm-group/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"