fix: storybook

This commit is contained in:
Eli Bosley
2025-04-16 13:53:53 -04:00
parent 0be56f148d
commit 35a6d14367
20 changed files with 663 additions and 774 deletions

View File

@@ -92,42 +92,9 @@ function translateRCloneOptionToJsonSchema({
return schema;
}
/**
* Generates the UI schema for RClone remote configuration using Categorization for provider options.
*/
export function getRcloneConfigFormSchema({
providerTypes = [],
selectedProvider = '',
providerOptions = {},
}: {
providerTypes?: string[];
selectedProvider?: string;
providerOptions?: Record<string, RCloneProviderOptionResponse[]>;
}): SettingSlice {
const options = providerOptions[selectedProvider] || [];
const basicSlice = getBasicConfigSlice({ providerTypes });
const standardConfigSlice = getProviderConfigSlice({
selectedProvider,
providerOptions: options,
type: 'standard',
});
const advancedConfigSlice = getProviderConfigSlice({
selectedProvider,
providerOptions: options,
type: 'advanced',
});
const mergedProperties = mergeSettingSlices([basicSlice, standardConfigSlice, advancedConfigSlice]);
return {
properties: mergedProperties.properties,
elements: mergedProperties.elements,
};
}
/**
* Step 1: Basic configuration - name and type selection
* Returns a SettingSlice containing properties and a VerticalLayout UI element with options.step = 0.
*/
function getBasicConfigSlice({ providerTypes }: { providerTypes: string[] }): SettingSlice {
// Create UI elements for basic configuration (Step 1)
@@ -178,30 +145,33 @@ function getBasicConfigSlice({ providerTypes }: { providerTypes: string[] }): Se
},
};
// Wrap the basic elements in a VerticalLayout
// Wrap the basic elements in a VerticalLayout marked for step 0
const verticalLayoutElement: UIElement = {
type: 'VerticalLayout',
elements: basicConfigElements,
options: { step: 0 }, // Assign to step 0
};
return {
properties: basicConfigProperties as unknown as DataSlice,
// Return the VerticalLayout as the single element for this slice
elements: [verticalLayoutElement],
elements: [verticalLayoutElement], // Return the VerticalLayout as the single element
};
}
/**
* Step 2/3: Provider-specific configuration based on the selected provider and whether to show advanced options
* Step 2/3: Provider-specific configuration based on the selected provider and type (standard/advanced).
* Returns a SettingSlice containing properties and a VerticalLayout UI element with options.step = stepIndex.
*/
export function getProviderConfigSlice({
function getProviderConfigSlice({
selectedProvider,
providerOptions,
type = 'standard',
stepIndex, // Added stepIndex parameter
}: {
selectedProvider: string;
providerOptions: RCloneProviderOptionResponse[];
type?: 'standard' | 'advanced';
stepIndex: number; // Required step index for the rule
}): SettingSlice {
// Default properties when no provider is selected
let configProperties: DataSlice = {};
@@ -213,7 +183,7 @@ export function getProviderConfigSlice({
};
}
// Filter options based on the showAdvancedOptions flag
// Filter options based on the type (standard/advanced)
const filteredOptions = providerOptions.filter((option) => {
if (type === 'advanced' && option.Advanced === true) {
return true;
@@ -233,8 +203,7 @@ export function getProviderConfigSlice({
return acc;
}, [] as RCloneProviderOptionResponse[]);
// If no options match the filter (e.g., asking for advanced but none exist), return empty
// Use uniqueOptionsByName instead of filteredOptions from here on
// If no options match the filter, return empty
if (uniqueOptionsByName.length === 0) {
return {
properties: configProperties,
@@ -242,7 +211,7 @@ export function getProviderConfigSlice({
};
}
// Create dynamic UI elements based on unique provider options
// Create dynamic UI control elements based on unique provider options
const controlElements = uniqueOptionsByName.map<UIElement>((option) => {
const format = getJsonFormElementForType({
rcloneType: option.Type,
@@ -278,7 +247,7 @@ export function getProviderConfigSlice({
}
// --- Start: Add dynamic visibility rule based on Provider --- //
let rule: { effect: RuleEffect; condition: SchemaBasedCondition } | undefined = undefined;
let providerRule: { effect: RuleEffect; condition: SchemaBasedCondition } | undefined = undefined;
const providerFilter = option.Provider?.trim();
if (providerFilter) {
@@ -293,12 +262,12 @@ export function getProviderConfigSlice({
? { not: { enum: providers } } // Show if type is NOT in the list
: { enum: providers }; // Show if type IS in the list
rule = {
providerRule = {
effect: RuleEffect.SHOW,
condition: {
scope: '#/properties/type',
schema: conditionSchema,
},
} as SchemaBasedCondition,
};
}
}
@@ -309,8 +278,8 @@ export function getProviderConfigSlice({
scope: `#/properties/parameters/properties/${option.Name}`,
label: option.Help || option.Name,
options: controlOptions,
// Add the rule if it was generated
...(rule && { rule }),
// Add the provider-specific rule if it was generated
...(providerRule && { rule: providerRule }),
};
return uiElement;
});
@@ -319,30 +288,36 @@ export function getProviderConfigSlice({
const paramProperties: Record<string, JsonSchema7> = {};
uniqueOptionsByName.forEach((option) => {
if (option) {
// Ensure option exists before translating
paramProperties[option.Name] = translateRCloneOptionToJsonSchema({ option });
console.log('paramProperties', option.Name, paramProperties[option.Name]);
}
});
// Only add parameters object if there are properties
if (Object.keys(paramProperties).length > 0) {
// Initialize parameters object if it doesn't exist
// Ensure parameters object exists and has a properties key
if (!configProperties.parameters) {
configProperties.parameters = {
type: 'object',
properties: {} as Record<string, JsonSchema7>,
} as unknown as DataSlice;
configProperties.parameters = { type: 'object', properties: {} } as any;
} else if (!(configProperties.parameters as any).properties) {
(configProperties.parameters as any).properties = {};
}
// Merge the properties into the existing parameters object
// Merge the new paramProperties into the existing parameters.properties
(configProperties.parameters as any).properties = {
...(configProperties.parameters as any).properties,
...paramProperties,
};
}
// Wrap the control elements in a VerticalLayout marked for the specified stepIndex
const verticalLayoutElement: UIElement = {
type: 'VerticalLayout',
elements: controlElements,
options: { step: stepIndex }, // Assign to the specified stepIndex
};
return {
properties: configProperties,
elements: controlElements,
elements: [verticalLayoutElement], // Return the VerticalLayout as the single element
};
}
@@ -418,90 +393,8 @@ function getJsonFormElementForType({
}
/**
* Returns a combined form schema for the rclone backup configuration UI
*/
export function getRcloneConfigSlice(): SettingSlice {
const elements: UIElement[] = [
{
type: 'Label',
text: 'Configure RClone Backup',
options: {
format: 'title',
description:
'This 3-step process will guide you through setting up your RClone backup configuration.',
},
},
{
type: 'SteppedLayout',
options: {
stepControl: '#/properties/configStep',
steps: [
{ label: 'Set up Remote Config', description: 'Name and provider selection' },
{ label: 'Set up Drive', description: 'Provider-specific configuration' },
{ label: 'Advanced Config', description: 'Optional advanced settings' },
],
},
},
];
// Basic properties for the rclone backup configuration
const properties: Record<string, JsonSchema7> = {
configStep: {
type: 'number',
minimum: 0,
maximum: 2,
default: 0,
},
};
return {
properties: properties as unknown as DataSlice,
elements,
};
}
/**
* Returns the complete form schemas (data and UI) for the RClone configuration
*/
export function getRcloneConfigSchemas(
properties: DataSlice,
elements: UIElement[]
): {
dataSchema: Record<string, any>;
uiSchema: Layout;
} {
return {
dataSchema: {
type: 'object',
properties,
},
uiSchema: {
type: 'SteppedLayout',
options: {
stepControl: '#/properties/configStep',
steps: [
{ label: 'Set up Remote Config', description: 'Name and provider selection' },
{ label: 'Set up Drive', description: 'Provider-specific configuration' },
{ label: 'Advanced Config', description: 'Optional advanced settings' },
],
},
elements: [
{
type: 'Label',
options: {
format: 'title',
description:
'This 3-step process will guide you through setting up your RClone backup configuration.',
},
},
...elements,
],
},
};
}
/**
* Builds the complete settings schema for a given provider
* Builds the complete settings schema for the RClone config UI, returning the final dataSchema and uiSchema.
* Integrates step-specific elements directly into the SteppedLayout's elements array.
*/
export function buildRcloneConfigSchema({
providerTypes = [],
@@ -511,14 +404,105 @@ export function buildRcloneConfigSchema({
providerTypes?: string[];
selectedProvider?: string;
providerOptions?: Record<string, RCloneProviderOptionResponse[]>;
}): SettingSlice {
// Get the base slice and form schema
const baseSlice = getRcloneConfigSlice();
const formSlice = getRcloneConfigFormSchema({
providerTypes,
}): {
dataSchema: Record<string, any>;
uiSchema: Layout;
} {
// --- Schema Definition ---
// Define the step control property - REMOVED as SteppedLayout uses local state
// const stepControlProperty: Record<string, JsonSchema7> = {
// configStep: {
// type: 'number',
// minimum: 0,
// maximum: 2, // 3 steps: 0, 1, 2
// default: 0,
// },
// };
// --- Step Content Generation ---
const optionsForProvider = providerOptions[selectedProvider] || [];
// Step 0: Basic Config
const basicSlice = getBasicConfigSlice({ providerTypes });
// Step 1: Standard Provider Config
const standardConfigSlice = getProviderConfigSlice({
selectedProvider,
providerOptions,
providerOptions: optionsForProvider,
type: 'standard',
stepIndex: 1, // Assign to step 1
});
return mergeSettingSlices([baseSlice, formSlice]);
// Step 2: Advanced Provider Config
const advancedConfigSlice = getProviderConfigSlice({
selectedProvider,
providerOptions: optionsForProvider,
type: 'advanced',
stepIndex: 2, // Assign to step 2
});
// --- UI Schema Definition ---
// Define the SteppedLayout UI element, now containing step content elements
const steppedLayoutElement: UIElement = {
type: 'SteppedLayout',
options: {
steps: [
{ label: 'Set up Remote Config', description: 'Name and provider selection' },
{ label: 'Set up Drive', description: 'Provider-specific configuration' },
{ label: 'Advanced Config', description: 'Optional advanced settings' },
],
},
// Nest the step content elements directly inside the SteppedLayout
elements: [
...(basicSlice.elements || []),
...(standardConfigSlice.elements || []),
...(advancedConfigSlice.elements || []),
],
};
// Define the overall title label
const titleLabel: UIElement = {
type: 'Label',
text: 'Configure RClone Remote',
options: {
format: 'title',
description:
'This 3-step process will guide you through setting up your RClone remote configuration.',
},
};
// --- Merging and Final Output ---
// Merge all properties: basic + standard + advanced
const mergedProperties = mergeSettingSlices([
basicSlice,
standardConfigSlice,
advancedConfigSlice,
]);
// Construct the final dataSchema
const dataSchema = {
type: 'object',
properties: mergedProperties.properties,
// Add required fields if necessary, e.g., ['name', 'type']
// required: ['name', 'type'], // Example: Make name and type required globally
};
// Construct the final uiSchema with Title + SteppedLayout (containing steps)
const uiSchema: Layout = {
type: 'VerticalLayout',
elements: [
titleLabel,
steppedLayoutElement, // The Stepper control, now containing its step elements
// Step content elements are now *inside* steppedLayoutElement.elements
],
};
return { dataSchema, uiSchema };
}
// Add optional console log for debugging
// console.log('Generated RClone Config Schema:', JSON.stringify({ dataSchema, uiSchema }, null, 2));

View File

@@ -4,7 +4,7 @@ import { type Layout } from '@jsonforms/core';
import { RCloneProviderOptionResponse } from '@app/unraid-api/graph/resolvers/rclone/rclone.model.js';
import { buildRcloneConfigSchema, getRcloneConfigSchemas } from './jsonforms/rclone-jsonforms-config.js';
import { buildRcloneConfigSchema } from './jsonforms/rclone-jsonforms-config.js';
import { RCloneApiService } from './rclone-api.service.js';
/**
@@ -51,12 +51,10 @@ export class RCloneFormService {
await this.loadProviderInfo();
}
const { properties, elements } = buildRcloneConfigSchema({
return buildRcloneConfigSchema({
providerTypes: this.providerNames,
selectedProvider,
providerOptions: this.providerOptions,
});
return getRcloneConfigSchemas(properties, elements);
}
}

346
pnpm-lock.yaml generated
View File

@@ -704,20 +704,23 @@ importers:
specifier: ^4.4.1
version: 4.4.1(@vue/compiler-sfc@3.5.13)(prettier@3.5.3)
'@storybook/addon-essentials':
specifier: ^8.5.8
version: 8.6.9(@types/react@19.0.8)(storybook@8.6.9(prettier@3.5.3))
specifier: ^8.6.12
version: 8.6.12(@types/react@19.0.8)(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-interactions':
specifier: ^8.5.8
version: 8.6.9(storybook@8.6.9(prettier@3.5.3))
specifier: ^8.6.12
version: 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-links':
specifier: ^8.5.8
version: 8.6.9(react@19.0.0)(storybook@8.6.9(prettier@3.5.3))
specifier: ^8.6.12
version: 8.6.12(react@19.0.0)(storybook@8.6.12(prettier@3.5.3))
'@storybook/builder-vite':
specifier: ^8.5.8
version: 8.6.9(storybook@8.6.9(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
specifier: ^8.6.12
version: 8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
'@storybook/vue3':
specifier: ^8.6.12
version: 8.6.12(storybook@8.6.12(prettier@3.5.3))(vue@3.5.13(typescript@5.8.3))
'@storybook/vue3-vite':
specifier: ^8.5.8
version: 8.6.9(storybook@8.6.9(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.8.3))
specifier: ^8.6.12
version: 8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.8.3))
'@tailwindcss/typography':
specifier: ^0.5.15
version: 0.5.16(tailwindcss@3.4.17)
@@ -760,6 +763,9 @@ importers:
autoprefixer:
specifier: ^10.4.20
version: 10.4.21(postcss@8.5.3)
concurrently:
specifier: ^9.1.2
version: 9.1.2
eslint:
specifier: ^9.17.0
version: 9.23.0(jiti@2.4.2)
@@ -794,8 +800,8 @@ importers:
specifier: ^6.0.1
version: 6.0.1
storybook:
specifier: ^8.5.8
version: 8.6.9(prettier@3.5.3)
specifier: ^8.6.12
version: 8.6.12(prettier@3.5.3)
tailwind-rem-to-rem:
specifier: github:unraid/tailwind-rem-to-rem
version: '@unraid/tailwind-rem-to-rem@https://codeload.github.com/unraid/tailwind-rem-to-rem/tar.gz/4b907d0cdb3abda88de9813e33c13c3e7b1300c4(tailwindcss@3.4.17)'
@@ -3590,105 +3596,105 @@ packages:
'@speed-highlight/core@1.2.7':
resolution: {integrity: sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==}
'@storybook/addon-actions@8.6.9':
resolution: {integrity: sha512-H2v17sMbSl8jhSulPxcOyChsFbzik9E7mgCWIf4P114KcIUokWLVuALnSOeqHME6lY0pPBZs3DgvVVMVMm7zNw==}
'@storybook/addon-actions@8.6.12':
resolution: {integrity: sha512-B5kfiRvi35oJ0NIo53CGH66H471A3XTzrfaa6SxXEJsgxxSeKScG5YeXcCvLiZfvANRQ7QDsmzPUgg0o3hdMXw==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-backgrounds@8.6.9':
resolution: {integrity: sha512-DiNpKJq4sEqTCGwwGs8fwi1hxBniCQMxsJFfrYlIx0HTyfA7AMROqP9fyv1aCV1JWDiwlL+cwCurkoyhpuZioQ==}
'@storybook/addon-backgrounds@8.6.12':
resolution: {integrity: sha512-lmIAma9BiiCTbJ8YfdZkXjpnAIrOUcgboLkt1f6XJ78vNEMnLNzD9gnh7Tssz1qrqvm34v9daDjIb+ggdiKp3Q==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-controls@8.6.9':
resolution: {integrity: sha512-YXBYsbHqdYhmrbGI+wv9LAr/LlKnPt9f9GL+9rw82lnYadWObYxzUxs+PPLNO5tc14fd2g+FMVHOfovaRdFvrQ==}
'@storybook/addon-controls@8.6.12':
resolution: {integrity: sha512-9VSRPJWQVb9wLp21uvpxDGNctYptyUX0gbvxIWOHMH3R2DslSoq41lsC/oQ4l4zSHVdL+nq8sCTkhBxIsjKqdQ==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-docs@8.6.9':
resolution: {integrity: sha512-yAP59G5Vd+E6O9KLfBR5ALdOFA5yEZ0n1f8Ne9jwF+NGu1U8KNIfWnZmBYaBGe+bpYn0CWV5AfdFvw83bzHYpw==}
'@storybook/addon-docs@8.6.12':
resolution: {integrity: sha512-kEezQjAf/p3SpDzLABgg4fbT48B6dkT2LiZCKTRmCrJVtuReaAr4R9MMM6Jsph6XjbIj/SvOWf3CMeOPXOs9sg==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-essentials@8.6.9':
resolution: {integrity: sha512-n3DSSIjDsVDw7uOatP2remC5SVSIfjwHcLGor85xLd1SQUh98wednM1Iby19qc/QR69UuOL0nB/d5yG1ifh0sA==}
'@storybook/addon-essentials@8.6.12':
resolution: {integrity: sha512-Y/7e8KFlttaNfv7q2zoHMPdX6hPXHdsuQMAjYl5NG9HOAJREu4XBy4KZpbcozRe4ApZ78rYsN/MO1EuA+bNMIA==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-highlight@8.6.9':
resolution: {integrity: sha512-I0gBHgaH74wX6yf5S7zUmdfr25hwPONpSAqPPGBSNYu0Jj9Je+ANr1y4T1I3cOaEvf73QntDhCgHC6/iqY90Fw==}
'@storybook/addon-highlight@8.6.12':
resolution: {integrity: sha512-9FITVxdoycZ+eXuAZL9ElWyML/0fPPn9UgnnAkrU7zkMi+Segq/Tx7y+WWanC5zfWZrXAuG6WTOYEXeWQdm//w==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-interactions@8.6.9':
resolution: {integrity: sha512-KpSVjcDD+5vmGA78MM2blsfy8J/PfuIMb74nJufgjci2xlzUxB8dGEFJACZPfqM5kUuUv/AhHHsAzP1r/wr83Q==}
'@storybook/addon-interactions@8.6.12':
resolution: {integrity: sha512-cTAJlTq6uVZBEbtwdXkXoPQ4jHOAGKQnYSezBT4pfNkdjn/FnEeaQhMBDzf14h2wr5OgBnJa6Lmd8LD9ficz4A==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-links@8.6.9':
resolution: {integrity: sha512-cYYlsaMHvEJzGqJ3BO5BpXaa00AxYtEKjJEFP7q/LDZBxMnrChzDygFUTAAbUTHF4U3mNCrl1KuyoUL3nMQquA==}
'@storybook/addon-links@8.6.12':
resolution: {integrity: sha512-AfKujFHoAxhxq4yu+6NwylltS9lf5MPs1eLLXvOlwo3l7Y/c68OdxJ7j68vLQhs9H173WVYjKyjbjFxJWf/YYg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.6.9
storybook: ^8.6.12
peerDependenciesMeta:
react:
optional: true
'@storybook/addon-measure@8.6.9':
resolution: {integrity: sha512-2GrHtaYZgM7qeil5/XfNJrdnan7hoLLUyU7w7fph0EVl7tiwmhtp4He0PX9hrT/Abk2HxeCP4WU2fAGwIuTkYg==}
'@storybook/addon-measure@8.6.12':
resolution: {integrity: sha512-tACmwqqOvutaQSduw8SMb62wICaT1rWaHtMN3vtWXuxgDPSdJQxLP+wdVyRYMAgpxhLyIO7YRf++Hfha9RHgFg==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-outline@8.6.9':
resolution: {integrity: sha512-YXfiSmjdpXGNYns9NZfdiEbwRfOW/Naym0dIH7s1LAlZZPJvtEYe2hNUOjBfAEm8ZhC1fA1+pZFnspOQHPENlA==}
'@storybook/addon-outline@8.6.12':
resolution: {integrity: sha512-1ylwm+n1s40S91No0v9T4tCjZORu3GbnjINlyjYTDLLhQHyBQd3nWR1Y1eewU4xH4cW9SnSLcMQFS/82xHqU6A==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-toolbars@8.6.9':
resolution: {integrity: sha512-WOO3CHyzqEql9xnNzi7BUkPRPGHGMCtAR+szGeWqmuj3GZLqXwDOb8HDa3aVMIhVEKhk5jN2zGQmxH53vReBNQ==}
'@storybook/addon-toolbars@8.6.12':
resolution: {integrity: sha512-HEcSzo1DyFtIu5/ikVOmh5h85C1IvK9iFKSzBR6ice33zBOaehVJK+Z5f487MOXxPsZ63uvWUytwPyViGInj+g==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/addon-viewport@8.6.9':
resolution: {integrity: sha512-1xkozyB1zs3eSNTc8ePAMcajUfbKvNMTjs5LYdts2N1Ss0xeZ+K/gphfRg0GaYsNvRYi5piufag/niHCGkT3hA==}
'@storybook/addon-viewport@8.6.12':
resolution: {integrity: sha512-EXK2LArAnABsPP0leJKy78L/lbMWow+EIJfytEP5fHaW4EhMR6h7Hzaqzre6U0IMMr/jVFa1ci+m0PJ0eQc2bw==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/blocks@8.6.9':
resolution: {integrity: sha512-+vSRkHLD7ho3Wd1WVA1KrYAnv7BnGHOhHWHAgTR5IdeMdgzQxm6+HHeqGB5sncilA0AjVC6udBIgHbCSuD61dA==}
'@storybook/blocks@8.6.12':
resolution: {integrity: sha512-DohlTq6HM1jDbHYiXL4ZvZ00VkhpUp5uftzj/CZDLY1fYHRjqtaTwWm2/OpceivMA8zDitLcq5atEZN+f+siTg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
storybook: ^8.6.9
storybook: ^8.6.12
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
'@storybook/builder-vite@8.6.9':
resolution: {integrity: sha512-8U11A7sLPvvcnJQ3pXyoX1LdJDpa4+JOYcASL9A+DL591jkfYKxhim7R4BOHO55aetmqQAoA/LEAD5runu7zoQ==}
'@storybook/builder-vite@8.6.12':
resolution: {integrity: sha512-Gju21ud/3Qw4v2vLNaa5SuJECsI9ICNRr2G0UyCCzRvCHg8jpA9lDReu2NqhLDyFIuDG+ZYT38gcaHEUoNQ8KQ==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
'@storybook/components@8.6.9':
resolution: {integrity: sha512-CqWUAYK/RgV++sXfiDG63DM2JF2FeidvnMO5/bki2hFbEqgs0/yy7BKUjhsGmuri5y+r9B2FJhW0WnE6PI8NWw==}
'@storybook/components@8.6.12':
resolution: {integrity: sha512-FiaE8xvCdvKC2arYusgtlDNZ77b8ysr8njAYQZwwaIHjy27TbR2tEpLDCmUwSbANNmivtc/xGEiDDwcNppMWlQ==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
'@storybook/core@8.6.9':
resolution: {integrity: sha512-psYxJAlj34ZaDAk+OvT/He6ZuUh0eGiHVtZNe0xWbNp5pQvOBjf+dg48swdI6KEbVs3aeU+Wnyra/ViU2RtA+Q==}
'@storybook/core@8.6.12':
resolution: {integrity: sha512-t+ZuDzAlsXKa6tLxNZT81gEAt4GNwsKP/Id2wluhmUWD/lwYW0uum1JiPUuanw8xD6TdakCW/7ULZc7aQUBLCQ==}
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
prettier:
optional: true
'@storybook/csf-plugin@8.6.9':
resolution: {integrity: sha512-IQnhyaVUkcRR9e4xiHN83xMQtTMH+lJp472iMifUIqxx/Yw137BTef2DEEp6EnRct4yKrch24+Nl65LWg0mRpQ==}
'@storybook/csf-plugin@8.6.12':
resolution: {integrity: sha512-6s8CnP1aoKPb3XtC0jRLUp8M5vTA8RhGAwQDKUsFpCC7g89JR9CaKs9FY2ZSzsNbjR15uASi7b3K8BzeYumYQg==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/global@5.0.0':
resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
@@ -3700,50 +3706,50 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
'@storybook/instrumenter@8.6.9':
resolution: {integrity: sha512-Gp6OSiu9KA/p1HWd7VW9TtpWX32ZBfqRVrOm4wW1AM6B4XACbQWFE/aQ25HwU834yfdJkr2BW+uUH8DBAQ6kTw==}
'@storybook/instrumenter@8.6.12':
resolution: {integrity: sha512-VK5fYAF8jMwWP/u3YsmSwKGh+FeSY8WZn78flzRUwirp2Eg1WWjsqPRubAk7yTpcqcC/km9YMF3KbqfzRv2s/A==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/manager-api@8.6.9':
resolution: {integrity: sha512-mxq9B9rxAraOCBapGKsUDfI+8yNtFhTgKMZCxmHoUCxvAHaIt4S9JcdX0qQQKUsBTr/b2hHm0O7A8DYrbgBRfw==}
'@storybook/manager-api@8.6.12':
resolution: {integrity: sha512-O0SpISeJLNTQvhSBOsWzzkCgs8vCjOq1578rwqHlC6jWWm4QmtfdyXqnv7rR1Hk08kQ+Dzqh0uhwHx0nfwy4nQ==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
'@storybook/preview-api@8.6.9':
resolution: {integrity: sha512-hW3Z8NBrGs2bNunaHgrLjpfrOcWsxH0ejAqaba8MolPXjzNs0lTFF/Ela7pUsh2m1R4/kiD+WfddQzyipUo4Mg==}
'@storybook/preview-api@8.6.12':
resolution: {integrity: sha512-84FE3Hrs0AYKHqpDZOwx1S/ffOfxBdL65lhCoeI8GoWwCkzwa9zEP3kvXBo/BnEDO7nAfxvMhjASTZXbKRJh5Q==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
'@storybook/react-dom-shim@8.6.9':
resolution: {integrity: sha512-SjqP6r5yy87OJRAiq1JzFazn6VWfptOA2HaxOiP8zRhJgG41K0Vseh8tbZdycj1AzJYSCcnKaIcfd/GEo/41+g==}
'@storybook/react-dom-shim@8.6.12':
resolution: {integrity: sha512-51QvoimkBzYs8s3rCYnY5h0cFqLz/Mh0vRcughwYaXckWzDBV8l67WBO5Xf5nBsukCbWyqBVPpEQLww8s7mrLA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/test@8.6.9':
resolution: {integrity: sha512-lIJA6jup3ZZNkKFyUiy1q2tHWZv5q5bTaLxTnI85XIWr+sFCZG5oo3pOQESBkX4V95rv8sq9gEmEWySZvW7MBw==}
'@storybook/test@8.6.12':
resolution: {integrity: sha512-0BK1Eg+VD0lNMB1BtxqHE3tP9FdkUmohtvWG7cq6lWvMrbCmAmh3VWai3RMCCDOukPFpjabOr8BBRLVvhNpv2w==}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
'@storybook/theming@8.6.9':
resolution: {integrity: sha512-FQafe66itGnIh0V42R65tgFKyz0RshpIs0pTrxrdByuB2yKsep+f8ZgKLJE3fCKw/Egw4bUuICo2m8d7uOOumA==}
'@storybook/theming@8.6.12':
resolution: {integrity: sha512-6VjZg8HJ2Op7+KV7ihJpYrDnFtd9D1jrQnUS8LckcpuBXrIEbaut5+34ObY8ssQnSqkk2GwIZBBBQYQBCVvkOw==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
'@storybook/vue3-vite@8.6.9':
resolution: {integrity: sha512-TTPzFwK7Yb1diC0o4uUrdmoDYVHRqRlH/xD8sZzud+N7MK+OJ7HPyCN8bw3e6HdsqLdJmfos45hRVTBgTYLOYA==}
'@storybook/vue3-vite@8.6.12':
resolution: {integrity: sha512-ihYH2TiV14B8V1mrCVVrbjuf+F6+V/78oWofVkvnUQnpwH4CnAySGf6bz6c6/Y6qEr9r30ECUe6/sS0TMt1ZAQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
'@storybook/vue3@8.6.9':
resolution: {integrity: sha512-asGlWyITyMGytNO+yXrWKcU+Ygk9G9zlmb0mVoTFxZGLiq/Wk3OmHmOlf5g0LyU8bkps43ZdkovEXfvMwQVm6A==}
'@storybook/vue3@8.6.12':
resolution: {integrity: sha512-mgGRMrFghDW5nHCDbdbhC4YUrOs7mCzwEuLZtdcvpB8TUPP62lTSnv3Gvcz8r12HjyIK6Jow9WgjTtdownGzkA==}
engines: {node: '>=18.0.0'}
peerDependencies:
storybook: ^8.6.9
storybook: ^8.6.12
vue: ^3.0.0
'@stylistic/eslint-plugin@4.2.0':
@@ -4234,6 +4240,7 @@ packages:
'@unraid/libvirt@2.1.0':
resolution: {integrity: sha512-yF/sAzYukM+VpDdAebf0z0O2mE5mGOEAW5lIafFkYoMiu60zNkNmr5IoA9hWCMmQkBOUCam8RZGs9YNwRjMtMQ==}
engines: {node: '>=14'}
cpu: [x64, arm64]
os: [linux, darwin]
'@unraid/shared-callbacks@1.1.1':
@@ -5585,6 +5592,11 @@ packages:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
concurrently@9.1.2:
resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==}
engines: {node: '>=18'}
hasBin: true
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
@@ -10823,8 +10835,8 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
storybook@8.6.9:
resolution: {integrity: sha512-Iw4+R4V3yX7MhXJaLBAT4oLtZ+SaTzX8KvUNZiQzvdD+TrFKVA3QKV8gvWjstGyU2dd+afE1Ph6EG5Xa2Az2CA==}
storybook@8.6.12:
resolution: {integrity: sha512-Z/nWYEHBTLK1ZBtAWdhxC0l5zf7ioJ7G4+zYqtTdYeb67gTnxNj80gehf8o8QY9L2zA2+eyMRGLC2V5fI7Z3Tw==}
hasBin: true
peerDependencies:
prettier: ^2 || ^3
@@ -11247,6 +11259,10 @@ packages:
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
engines: {node: '>=18'}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
trim-newlines@3.0.1:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
@@ -15430,125 +15446,125 @@ snapshots:
'@speed-highlight/core@1.2.7': {}
'@storybook/addon-actions@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-actions@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
'@types/uuid': 9.0.8
dequal: 2.0.3
polished: 4.3.1
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
uuid: 9.0.1
'@storybook/addon-backgrounds@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-backgrounds@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
memoizerific: 1.11.3
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
'@storybook/addon-controls@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-controls@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
dequal: 2.0.3
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
'@storybook/addon-docs@8.6.9(@types/react@19.0.8)(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-docs@8.6.12(@types/react@19.0.8)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@mdx-js/react': 3.1.0(@types/react@19.0.8)(react@19.0.0)
'@storybook/blocks': 8.6.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.9(prettier@3.5.3))
'@storybook/csf-plugin': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/react-dom-shim': 8.6.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.9(prettier@3.5.3))
'@storybook/blocks': 8.6.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.12(prettier@3.5.3))
'@storybook/csf-plugin': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/react-dom-shim': 8.6.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.12(prettier@3.5.3))
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
'@storybook/addon-essentials@8.6.9(@types/react@19.0.8)(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-essentials@8.6.12(@types/react@19.0.8)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/addon-actions': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-backgrounds': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-controls': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-docs': 8.6.9(@types/react@19.0.8)(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-highlight': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-measure': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-outline': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-toolbars': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/addon-viewport': 8.6.9(storybook@8.6.9(prettier@3.5.3))
storybook: 8.6.9(prettier@3.5.3)
'@storybook/addon-actions': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-backgrounds': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-controls': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-docs': 8.6.12(@types/react@19.0.8)(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-highlight': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-measure': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-outline': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-toolbars': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/addon-viewport': 8.6.12(storybook@8.6.12(prettier@3.5.3))
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
'@storybook/addon-highlight@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-highlight@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/addon-interactions@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-interactions@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
'@storybook/instrumenter': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/test': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/instrumenter': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/test': 8.6.12(storybook@8.6.12(prettier@3.5.3))
polished: 4.3.1
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
'@storybook/addon-links@8.6.9(react@19.0.0)(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-links@8.6.12(react@19.0.0)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
optionalDependencies:
react: 19.0.0
'@storybook/addon-measure@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-measure@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
tiny-invariant: 1.3.3
'@storybook/addon-outline@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-outline@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
'@storybook/addon-toolbars@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-toolbars@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/addon-viewport@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/addon-viewport@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
memoizerific: 1.11.3
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/blocks@8.6.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.9(prettier@3.5.3))':
'@storybook/blocks@8.6.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/icons': 1.4.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
optionalDependencies:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
'@storybook/builder-vite@8.6.9(storybook@8.6.9(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))':
'@storybook/builder-vite@8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))':
dependencies:
'@storybook/csf-plugin': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/csf-plugin': 8.6.12(storybook@8.6.12(prettier@3.5.3))
browser-assert: 1.2.1
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
vite: 6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
'@storybook/components@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/components@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/core@8.6.9(prettier@3.5.3)(storybook@8.6.9(prettier@3.5.3))':
'@storybook/core@8.6.12(prettier@3.5.3)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/theming': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/theming': 8.6.12(storybook@8.6.12(prettier@3.5.3))
better-opn: 3.0.2
browser-assert: 1.2.1
esbuild: 0.25.1
@@ -15567,9 +15583,9 @@ snapshots:
- supports-color
- utf-8-validate
'@storybook/csf-plugin@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/csf-plugin@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
unplugin: 1.16.1
'@storybook/global@5.0.0': {}
@@ -15579,48 +15595,48 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
'@storybook/instrumenter@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/instrumenter@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
'@vitest/utils': 2.1.9
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/manager-api@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/manager-api@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/preview-api@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/preview-api@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/react-dom-shim@8.6.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.9(prettier@3.5.3))':
'@storybook/react-dom-shim@8.6.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/test@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
'@storybook/instrumenter': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/instrumenter': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@testing-library/dom': 10.4.0
'@testing-library/jest-dom': 6.5.0
'@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0)
'@vitest/expect': 2.0.5
'@vitest/spy': 2.0.5
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/theming@8.6.9(storybook@8.6.9(prettier@3.5.3))':
'@storybook/theming@8.6.12(storybook@8.6.12(prettier@3.5.3))':
dependencies:
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
'@storybook/vue3-vite@8.6.9(storybook@8.6.9(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.8.3))':
'@storybook/vue3-vite@8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@storybook/builder-vite': 8.6.9(storybook@8.6.9(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
'@storybook/vue3': 8.6.9(storybook@8.6.9(prettier@3.5.3))(vue@3.5.13(typescript@5.8.3))
'@storybook/builder-vite': 8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
'@storybook/vue3': 8.6.12(storybook@8.6.12(prettier@3.5.3))(vue@3.5.13(typescript@5.8.3))
find-package-json: 1.2.0
magic-string: 0.30.17
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
typescript: 5.8.3
vite: 6.2.3(@types/node@22.15.3)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
vue-component-meta: 2.2.8(typescript@5.8.3)
@@ -15628,15 +15644,15 @@ snapshots:
transitivePeerDependencies:
- vue
'@storybook/vue3@8.6.9(storybook@8.6.9(prettier@3.5.3))(vue@3.5.13(typescript@5.8.3))':
'@storybook/vue3@8.6.12(storybook@8.6.12(prettier@3.5.3))(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@storybook/components': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/components': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/global': 5.0.0
'@storybook/manager-api': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/preview-api': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/theming': 8.6.9(storybook@8.6.9(prettier@3.5.3))
'@storybook/manager-api': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/preview-api': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@storybook/theming': 8.6.12(storybook@8.6.12(prettier@3.5.3))
'@vue/compiler-core': 3.5.13
storybook: 8.6.9(prettier@3.5.3)
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
type-fest: 2.19.0
vue: 3.5.13(typescript@5.8.3)
@@ -17762,6 +17778,16 @@ snapshots:
readable-stream: 3.6.2
typedarray: 0.0.6
concurrently@9.1.2:
dependencies:
chalk: 4.1.2
lodash: 4.17.21
rxjs: 7.8.2
shell-quote: 1.8.2
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
confbox@0.1.8: {}
confbox@0.2.1: {}
@@ -23821,9 +23847,9 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
storybook@8.6.9(prettier@3.5.3):
storybook@8.6.12(prettier@3.5.3):
dependencies:
'@storybook/core': 8.6.9(prettier@3.5.3)(storybook@8.6.9(prettier@3.5.3))
'@storybook/core': 8.6.12(prettier@3.5.3)(storybook@8.6.12(prettier@3.5.3))
optionalDependencies:
prettier: 3.5.3
transitivePeerDependencies:
@@ -24285,6 +24311,8 @@ snapshots:
dependencies:
punycode: 2.3.1
tree-kill@1.2.2: {}
trim-newlines@3.0.1: {}
ts-api-utils@2.1.0(typescript@5.8.3):
@@ -25244,7 +25272,7 @@ snapshots:
isarray: 2.0.5
which-boxed-primitive: 1.1.1
which-collection: 1.0.2
which-typed-array: 1.1.18
which-typed-array: 1.1.19
which-collection@1.0.2:
dependencies:

View File

@@ -22,9 +22,9 @@ const config: StorybookConfig = {
autodocs: "tag",
},
async viteFinal(config) {
config.root = dirname(require.resolve('@storybook/builder-vite'));
return {
...config,
root: dirname(require.resolve('@storybook/builder-vite')),
resolve: {
alias: {
'@': join(dirname(new URL(import.meta.url).pathname), '../src'),
@@ -32,6 +32,9 @@ const config: StorybookConfig = {
'@/lib': join(dirname(new URL(import.meta.url).pathname), '../src/lib'),
},
},
optimizeDeps: {
include: [...(config.optimizeDeps?.include ?? []), '@unraid/tailwind-rem-to-rem'],
},
css: {
postcss: {
plugins: [

View File

@@ -1,9 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"paths": {
"@/*": ["../src/*"]
}
},
"include": ["../stories/**/*", "../src/**/*"]
}

View File

@@ -6,89 +6,134 @@ import prettier from 'eslint-plugin-prettier';
import vuePlugin from 'eslint-plugin-vue';
import tseslint from 'typescript-eslint';
export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, {
files: ['**/*.ts', '**/*.tsx', '**/*.vue'],
languageOptions: {
parser: require('vue-eslint-parser'),
parserOptions: {
// Common rules shared across file types
const commonRules = {
'@typescript-eslint/no-unused-vars': ['off'],
'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 1 }],
'no-relative-import-paths/no-relative-import-paths': [
'error',
{ allowSameFolder: false, rootDir: 'src', prefix: '@' },
],
'prettier/prettier': 'error',
'no-restricted-globals': [
'error',
{
name: '__dirname',
message: 'Use import.meta.url instead of __dirname in ESM',
},
{
name: '__filename',
message: 'Use import.meta.url instead of __filename in ESM',
},
],
'eol-last': ['error', 'always'],
'@typescript-eslint/no-explicit-any': [
'error',
{
ignoreRestArgs: true,
fixToUnknown: false,
},
],
};
// Vue-specific rules
const vueRules = {
'vue/multi-word-component-names': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
},
],
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/no-unsupported-features': [
'error',
{
version: '^3.3.0',
},
],
'vue/no-undef-components': ['error'],
'vue/no-unused-properties': [
'error',
{
groups: ['props'],
deepData: false,
},
],
};
// Common language options
const commonLanguageOptions = {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
browser: true,
window: true,
document: true,
es2022: true,
HTMLElement: true,
},
};
export default [
// Base config from recommended configs
eslint.configs.recommended,
...tseslint.configs.recommended,
// TypeScript Files (.ts)
{
files: ['**/*.ts'],
languageOptions: {
parser: tseslint.parser,
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
globals: {
browser: true,
window: true,
document: true,
es2022: true,
HTMLElement: true,
},
},
plugins: {
'no-relative-import-paths': noRelativeImportPaths,
prettier: prettier,
import: importPlugin,
vue: vuePlugin,
},
rules: {
'@typescript-eslint/no-unused-vars': ['off'],
'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 1 }],
'no-relative-import-paths/no-relative-import-paths': [
'error',
{ allowSameFolder: false, rootDir: 'src', prefix: '@' },
],
'prettier/prettier': 'error',
'no-restricted-globals': [
'error',
{
name: '__dirname',
message: 'Use import.meta.url instead of __dirname in ESM',
},
{
name: '__filename',
message: 'Use import.meta.url instead of __filename in ESM',
},
],
'eol-last': ['error', 'always'],
// Vue specific rules
'vue/multi-word-component-names': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
parserOptions: {
...commonLanguageOptions,
ecmaFeatures: {
jsx: true,
},
},
],
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/no-unsupported-features': [
'error',
{
version: '^3.3.0',
},
],
'vue/no-undef-components': ['error'],
'vue/no-unused-properties': [
'error',
{
groups: ['props'],
deepData: false,
},
],
// Allow empty object types and any types in Vue component definitions
'@typescript-eslint/no-explicit-any': [
'error',
{
ignoreRestArgs: true,
fixToUnknown: false,
},
],
},
plugins: {
'no-relative-import-paths': noRelativeImportPaths,
prettier: prettier,
import: importPlugin,
},
rules: {
...commonRules,
},
},
ignores: ['src/graphql/generated/client/**/*'],
});
// Vue Files (.vue)
{
files: ['**/*.vue'],
languageOptions: {
parser: require('vue-eslint-parser'),
parserOptions: {
...commonLanguageOptions,
parser: tseslint.parser,
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'no-relative-import-paths': noRelativeImportPaths,
prettier: prettier,
import: importPlugin,
vue: vuePlugin,
},
rules: {
...commonRules,
...vueRules,
},
},
// Ignores
{
ignores: ['src/graphql/generated/client/**/*'],
},
];

View File

@@ -21,9 +21,9 @@
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage",
"// Build": "",
"prebuild": "npm run clean",
"build": "vite build",
"build:watch": "vite build -c vite.web-component.ts --mode production --watch",
"build:watch": "concurrently \"pnpm build:wc --watch\" \"pnpm build --watch\"",
"build:watch:main": "vite build --watch",
"build:wc": "REM_PLUGIN=true vite build -c vite.web-component.ts --mode production",
"build:all": "vite build && vite build -c vite.web-component.ts --mode production",
"clean": "rimraf dist",
@@ -60,11 +60,12 @@
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@storybook/addon-essentials": "^8.5.8",
"@storybook/addon-interactions": "^8.5.8",
"@storybook/addon-links": "^8.5.8",
"@storybook/builder-vite": "^8.5.8",
"@storybook/vue3-vite": "^8.5.8",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-links": "^8.6.12",
"@storybook/builder-vite": "^8.6.12",
"@storybook/vue3": "^8.6.12",
"@storybook/vue3-vite": "^8.6.12",
"@tailwindcss/typography": "^0.5.15",
"@testing-library/vue": "^8.0.0",
"@types/jsdom": "^21.1.7",
@@ -79,6 +80,7 @@
"@vue/test-utils": "^2.4.0",
"@vue/tsconfig": "^0.7.0",
"autoprefixer": "^10.4.20",
"concurrently": "^9.1.2",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-import": "^2.31.0",
@@ -90,7 +92,7 @@
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"rimraf": "^6.0.1",
"storybook": "^8.5.8",
"storybook": "^8.6.12",
"tailwind-rem-to-rem": "github:unraid/tailwind-rem-to-rem",
"tailwindcss": "^3.0.0",
"tailwindcss-animate": "^1.0.7",

View File

@@ -0,0 +1,20 @@
export * from '@/components/common/badge';
export * from '@/components/brand';
export * from '@/components/common/button';
export * from '@/components/layout';
export * from '@/components/common/dropdown-menu';
export * from '@/components/common/loading';
export * from '@/components/form/input';
export * from '@/components/form/label';
export * from '@/components/form/number';
export * from '@/components/form/lightswitch';
export * from '@/components/form/select';
export * from '@/components/form/switch';
export * from '@/components/common/scroll-area';
export * from '@/components/common/stepper';
export * from '@/components/common/sheet';
export * from '@/components/common/tabs';
export * from '@/components/common/tooltip';
export * from '@/components/common/toast';
export * from '@/components/common/popover';
export * from '@/components/brand';

View File

@@ -1,5 +1,6 @@
export { default as DropdownMenu } from './DropdownMenu.vue';
import DropdownMenu from './DropdownMenu.vue';
export { DropdownMenu };
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue';
export { default as DropdownMenuContent } from './DropdownMenuContent.vue';
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue';

View File

@@ -1,52 +0,0 @@
export { Badge } from '@/components/common/badge';
export { BrandButton, BrandLoading, BrandLogo, BrandLogoConnect } from '@/components/brand';
export { Button } from '@/components/common/button';
export { CardWrapper, PageContainer } from '@/components/layout';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuRadioGroup,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuShortcut,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
} from '@/components/common/dropdown-menu';
export { Bar, Error, Spinner } from '@/components/common/loading';
export { Input } from '@/components/form/input';
export { Label } from '@/components/form/label';
export { Lightswitch } from '@/components/form/lightswitch';
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectItemText,
SelectLabel,
SelectScrollUpButton,
SelectScrollDownButton,
SelectSeparator,
SelectTrigger,
SelectValue,
} from '@/components/form/select';
export { Switch } from '@/components/form/switch';
export { ScrollArea, ScrollBar } from '@/components/common/scroll-area';
export {
Sheet,
SheetTrigger,
SheetContent,
SheetClose,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
} from '@/components/common/sheet';
export { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/common/tabs';
export { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/common/tooltip';
export { Toaster } from '@/components/common/toast';

View File

@@ -1,61 +0,0 @@
<template>
<div :class="styles.categorization.root">
<div :class="styles.categorization.category">
<template v-for="(category, index) in categories" :key="`category-${index}`">
<div v-if="category.value.visible" @click="selected = index">
<button
:class="[selected === index ? styles.categorization.selected : '']"
:disabled="!category.value.enabled"
>
<label>{{ category.value.label }}</label>
</button>
</div>
</template>
</div>
<div :class="styles.categorization.panel">
<DispatchRenderer
v-if="categories[selected]"
:schema="layout.schema"
:uischema="categories[selected].value.uischema"
:path="layout.path"
:enabled="layout.enabled"
:renderers="layout.renderers"
:cells="layout.cells"
/>
</div>
</div>
</template>
<script lang="ts">
import type { JsonFormsRendererRegistryEntry, Layout } from '@jsonforms/core';
import { and, categorizationHasCategory, isCategorization, rankWith } from '@jsonforms/core';
import {
DispatchRenderer,
rendererProps,
useJsonFormsCategorization,
type RendererProps,
} from '@jsonforms/vue';
import { useVanillaLayout } from '@jsonforms/vue-vanilla';
import { defineComponent } from 'vue';
const layoutRenderer = defineComponent({
name: 'CategorizationAccordionRenderer',
components: {
DispatchRenderer,
},
props: {
...rendererProps<Layout>(),
},
setup(props: RendererProps<Layout>) {
return useVanillaLayout(useJsonFormsCategorization(props));
},
data() {
return {
selected: 0,
};
},
});
export default layoutRenderer;
</script>

View File

@@ -0,0 +1,71 @@
<script setup lang="ts">
import { rankWith, uiTypeIs, type UISchemaElement } from '@jsonforms/core';
import { rendererProps, useJsonFormsRenderer } from '@jsonforms/vue';
import { computed } from 'vue';
// Define a type for our specific Label UI Schema
interface LabelUISchema extends UISchemaElement {
text?: string;
options?: {
description?: string;
format?: 'title' | 'heading' | 'documentation' | string; // Add other formats as needed
};
}
const props = defineProps(rendererProps<UISchemaElement>());
// Destructure the renderer ref from the hook's return value
const { renderer } = useJsonFormsRenderer(props);
// Cast the uischema inside the computed ref to our specific type
const typedUISchema = computed(() => renderer.value.uischema as LabelUISchema);
// Access properties via renderer.value
const labelText = computed(() => typedUISchema.value.text);
const descriptionText = computed(() => typedUISchema.value.options?.description);
const labelFormat = computed(() => typedUISchema.value.options?.format);
// --- Access visibility via renderer.value ---
const isVisible = computed(() => renderer.value.visible); // Use renderer.value.visible for visibility check
// Conditional classes or elements based on format
const labelClass = computed(() => {
switch (labelFormat.value) {
case 'title':
return 'text-xl font-semibold mb-2'; // Example styling for title
case 'heading':
return 'text-lg font-medium mt-4 mb-1'; // Example styling for heading
default:
return 'font-medium'; // Default label styling
}
});
const descriptionClass = computed(() => {
switch (labelFormat.value) {
case 'documentation':
return 'text-sm text-gray-500 italic p-2 border-l-4 border-gray-300 bg-gray-50 my-2'; // Example styling for documentation
default:
return 'text-sm text-gray-600 mt-1'; // Default description styling
}
});
// Use v-html for description if it might contain HTML (like the documentation link)
// Ensure any HTML is sanitized if it comes from untrusted sources.
// Assuming the documentation link is safe here.
const allowHtml = computed(() => labelFormat.value === 'documentation');
// --- Tester Export ---
export const labelRendererTester = rankWith(
10, // Adjust rank as needed
uiTypeIs('Label')
);
</script>
<template>
<!-- Use the computed isVisible based on renderer.value.visible -->
<div v-if="isVisible" class="my-2">
<label v-if="labelText" :class="labelClass">{{ labelText }}</label>
<p v-if="descriptionText && allowHtml" :class="descriptionClass" v-html="descriptionText" />
<p v-else-if="descriptionText" :class="descriptionClass">{{ descriptionText }}</p>
</div>
</template>

View File

@@ -8,62 +8,42 @@ import StepperTitle from '@/components/common/stepper/StepperTitle.vue';
import StepperTrigger from '@/components/common/stepper/StepperTrigger.vue';
import { CheckIcon } from '@heroicons/vue/24/solid'; // Example icon
import {
// ControlElement, // No longer needed here, DispatchRenderer handles it
type JsonSchema,
type Layout,
type UISchemaElement,
// Actions, // No longer needed
} from '@jsonforms/core';
import { DispatchRenderer, useJsonFormsLayout, type RendererProps } from '@jsonforms/vue';
import { computed, inject } from 'vue';
import { computed, ref } from 'vue'; // Import ref, remove inject/onMounted
// Define props based on RendererProps<Layout>
const props = defineProps<RendererProps<Layout>>();
// --- JSON Forms Composables and Context ---
const { layout } = useJsonFormsLayout(props);
// Inject core jsonforms functionality (safer than relying on potentially non-exported composables)
const jsonforms = inject<any>('jsonforms');
const core = computed(() => jsonforms?.core);
const dispatch = computed(() => jsonforms?.dispatch);
// --- Step Configuration ---
// Expect options.steps: [{ label: string, description: string }, ...]
const stepsConfig = computed(() => props.uischema.options?.steps || []);
// Get the path to the step control property from uischema options (e.g., '#/properties/configStep')
const stepControlPath = computed(() => props.uischema.options?.stepControl as string | undefined);
// --- Current Step Logic ---
// Function to safely extract the step value from the data without lodash
const getCurrentStep = () => {
if (!stepControlPath.value || !core?.value?.data) return 0;
const pathSegments = stepControlPath.value.startsWith('#/')
? stepControlPath.value.substring(2).split('/')
: stepControlPath.value.split('.'); // Allow dot notation too
// Use local state for the current step index
const localCurrentStep = ref(0);
let currentData = core.value.data;
for (const segment of pathSegments) {
if (currentData === null || typeof currentData !== 'object' || !(segment in currentData)) {
return 0; // Path not found or data structure incorrect, default to step 0
}
currentData = currentData[segment];
}
return typeof currentData === 'number' ? currentData : 0; // Return step number or default
};
const currentStep = computed(getCurrentStep);
// currentStep now reflects the local state
const currentStep = computed(() => localCurrentStep.value);
// --- Step Update Logic ---
const updateStep = (newStep: number) => {
if (!stepControlPath.value || !dispatch?.value || newStep < 0 || newStep >= stepsConfig.value.length)
// Validate step index bounds
if (newStep < 0 || newStep >= stepsConfig.value.length) {
return;
// Use Actions.update to modify the data property controlling the step
const updateAction = jsonforms.Actions.update(stepControlPath.value, newStep);
dispatch.value(updateAction);
}
// Simply update the local state
localCurrentStep.value = newStep;
};
// --- Filtered Elements for Current Step ---
@@ -91,7 +71,6 @@ const getStepState = (stepIndex: number): StepState => {
<StepperItem
v-for="(step, index) in stepsConfig"
:key="index"
v-slot="{ state }"
class="relative flex w-full flex-col items-center justify-center"
:step="index + 1"
:disabled="getStepState(index) === 'inactive'"

View File

@@ -13,7 +13,7 @@
* @prop renderers - Available renderers
* @prop cells - Available cells
*/
import { Label } from '@/components/form/label';
import type { VerticalLayout } from '@jsonforms/core';
import { DispatchRenderer, type RendererProps } from '@jsonforms/vue';
import { computed } from 'vue';
@@ -21,14 +21,14 @@ import { computed } from 'vue';
const props = defineProps<RendererProps<VerticalLayout>>();
const elements = computed(() => {
console.log('elements', props.uischema?.elements);
return props.uischema?.elements || [];
});
</script>
<template>
<div class="grid grid-cols-settings items-baseline gap-y-6">
<div class="flex flex-col gap-y-2">
<template v-for="(element, index) in elements" :key="index">
<Label v-if="element.label" class="text-end">{{ element.label ?? index }}</Label>
<DispatchRenderer
class="ml-10"
:schema="props.schema"

View File

@@ -1,4 +1,3 @@
import CategorizationAccordionRenderer from '@/forms/CategorizationAccordionRenderer.vue';
import comboBoxRenderer from '@/forms/ComboBoxField.vue';
import inputFieldRenderer from '@/forms/InputField.vue';
import MissingRenderer from '@/forms/MissingRenderer.vue';
@@ -12,7 +11,6 @@ import VerticalLayout from '@/forms/VerticalLayout.vue';
import {
and,
isBooleanControl,
isCategorization,
isControl,
isEnumControl,
isIntegerControl,
@@ -26,6 +24,7 @@ import {
uiTypeIs,
} from '@jsonforms/core';
import type { JsonFormsRendererRegistryEntry, JsonSchema } from '@jsonforms/core';
import { LabelRenderer } from '@jsonforms/vue-vanilla';
const isStringArray = (schema: JsonSchema): boolean => {
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return false;
@@ -45,7 +44,7 @@ const formSelectEntry: JsonFormsRendererRegistryEntry = {
const formComboBoxEntry: JsonFormsRendererRegistryEntry = {
renderer: comboBoxRenderer,
tester: rankWith(4, and(isControl, optionIs('type', 'combobox'))),
tester: rankWith(4, and(isControl, optionIs('format', 'combobox'))),
};
const numberFieldEntry: JsonFormsRendererRegistryEntry = {
@@ -73,11 +72,6 @@ const missingRendererEntry: JsonFormsRendererRegistryEntry = {
tester: rankWith(3, isControl),
};
const categorizationAccordionEntry: JsonFormsRendererRegistryEntry = {
renderer: CategorizationAccordionRenderer,
tester: rankWith(5, isCategorization),
};
const verticalLayoutEntry: JsonFormsRendererRegistryEntry = {
renderer: VerticalLayout,
tester: rankWith(2, and(isLayout, uiTypeIs('VerticalLayout'))),
@@ -88,14 +82,13 @@ const steppedLayoutEntry: JsonFormsRendererRegistryEntry = {
tester: rankWith(3, and(isLayout, uiTypeIs('SteppedLayout'))),
};
/**
* JSONForms renderers for Unraid UI
*
* This file exports a list of JSONForms renderers that are used in the Unraid UI.
* It combines the vanilla renderers with the custom renderers defined in
* `@unraid/ui/src/forms/renderer-entries.ts`.
*/
const labelRendererEntry: JsonFormsRendererRegistryEntry = {
renderer: LabelRenderer,
tester: rankWith(3, and(uiTypeIs('Label'))),
};
export const jsonFormsRenderers = [
labelRendererEntry,
verticalLayoutEntry,
steppedLayoutEntry,
formSwitchEntry,
@@ -106,5 +99,4 @@ export const jsonFormsRenderers = [
preconditionsLabelEntry,
stringArrayEntry,
missingRendererEntry,
categorizationAccordionEntry,
];

View File

@@ -1,177 +1,18 @@
// Styles
import '@/styles/index.css';
import {
BrandButton,
brandButtonVariants,
BrandLoading,
brandLoadingVariants,
BrandLogo,
BrandLogoConnect,
type BrandButtonProps,
} from '@/components/brand';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/common/accordion';
// Components
import { Badge, type BadgeProps } from '@/components/common/badge';
import { Button, buttonVariants, type ButtonProps } from '@/components/common/button';
import {
DropdownMenu,
DropdownMenuArrow,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/common/dropdown-menu';
import { Bar, Error, Spinner } from '@/components/common/loading';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/common/popover';
import { ScrollArea, ScrollBar } from '@/components/common/scroll-area';
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/common/sheet';
import {
Stepper,
StepperDescription,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from '@/components/common/stepper';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/common/tabs';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/common/tooltip';
import { Input } from '@/components/form/input';
import { Label } from '@/components/form/label';
import { Lightswitch } from '@/components/form/lightswitch';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectItemText,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
} from '@/components/form/select';
import { Switch } from '@/components/form/switch';
import { CardWrapper, PageContainer } from '@/components/layout';
// Composables
import useTeleport from '@/composables/useTeleport';
// Lib
import { cn } from '@/lib/utils';
// Config
import tailwindConfig from '../tailwind.config';
export * from '@/components';
// Export
export {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Bar,
Badge,
BrandButton,
brandButtonVariants,
BrandLoading,
brandLoadingVariants,
BrandLogo,
BrandLogoConnect,
Button,
buttonVariants,
CardWrapper,
cn,
DropdownMenu,
DropdownMenuArrow,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuItem,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
DropdownMenuPortal,
Error,
Input,
Label,
PageContainer,
Popover,
PopoverContent,
PopoverTrigger,
ScrollBar,
ScrollArea,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectItemText,
SelectLabel,
SelectScrollUpButton,
SelectScrollDownButton,
SelectSeparator,
SelectTrigger,
SelectValue,
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
Spinner,
Stepper,
StepperDescription,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
Switch,
tailwindConfig,
Lightswitch,
Tabs,
TabsList,
TabsTrigger,
TabsContent,
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipProvider,
useTeleport,
// Type exports
type BrandButtonProps,
type BadgeProps,
type ButtonProps,
};
export { Toaster } from '@/components/common/toast';
export * from '@/components/common/popover';
export * from '@/components/form/number';
// JsonForms
export * from '@/forms/renderers';
// Lib
export * from '@/lib/utils';
// Config
export { default as tailwindConfig } from '../tailwind.config';
// Composables
export { default as useTeleport } from '@/composables/useTeleport';
// Additional exports not in components.ts

View File

@@ -10,25 +10,68 @@ type RegisterParams = {
pathToSharedCss?: string;
};
// Type for our simplified Vue component representation
type CustomElementComponent = {
styles?: string[];
render?: Function;
setup?: Function;
[key: string]: any;
};
export function registerAllComponents(params: RegisterParams = {}) {
const { namePrefix = 'uui', pathToSharedCss = './src/styles/index.css' } = params;
Object.entries(Components).forEach(([name, component]) => {
// add our shared css to each web component
component.styles ??= [];
component.styles.unshift(`@import "${pathToSharedCss}"`);
Object.entries(Components).forEach(([name, originalComponent]) => {
// Use explicit type assertion instead of type predicates
try {
// Skip anything that doesn't look like a Vue component
if (typeof originalComponent !== 'object' || originalComponent === null) {
if (debugImports) {
console.log(`[register components] Skipping non-object: ${name}`);
}
return;
}
// Skip function values
if (typeof originalComponent === 'function') {
if (debugImports) {
console.log(`[register components] Skipping function: ${name}`);
}
return;
}
// Skip if not a Vue component
if (!('render' in originalComponent || 'setup' in originalComponent)) {
if (debugImports) {
console.log(`[register components] Skipping non-component object: ${name}`);
}
return;
}
// translate ui component names from PascalCase to kebab-case
let elementName = kebabCase(name);
if (!elementName) {
console.log('[register components] Could not translate component name to kebab-case:', name);
elementName = name;
}
elementName = namePrefix + elementName;
// Now we can safely use type assertion since we've validated the component
const component = originalComponent as CustomElementComponent;
// add our shared css to each web component
component.styles ??= [];
component.styles.unshift(`@import "${pathToSharedCss}"`);
// register custom web components
if (debugImports) {
console.log(name, elementName, component.styles);
// translate ui component names from PascalCase to kebab-case
let elementName = kebabCase(name);
if (!elementName) {
console.log('[register components] Could not translate component name to kebab-case:', name);
elementName = name;
}
elementName = namePrefix + elementName;
// register custom web components
if (debugImports) {
console.log(name, elementName, component.styles);
}
// Use appropriate casting for defineCustomElement
customElements.define(elementName, defineCustomElement(component as any));
} catch (error) {
console.error(`[register components] Error registering component ${name}:`, error);
}
customElements.define(elementName, defineCustomElement(component));
});
}

View File

@@ -1,13 +1,15 @@
import type { Meta, StoryObj } from '@storybook/vue3';
import { MoreVertical } from 'lucide-vue-next';
import Button from '../../../src/components/common/button/Button.vue';
import DropdownMenu from '../../../src/components/common/dropdown-menu/DropdownMenu.vue';
import DropdownMenuArrow from '../../../src/components/common/dropdown-menu/DropdownMenuArrow.vue';
import DropdownMenuContent from '../../../src/components/common/dropdown-menu/DropdownMenuContent.vue';
import DropdownMenuItem from '../../../src/components/common/dropdown-menu/DropdownMenuItem.vue';
import DropdownMenuLabel from '../../../src/components/common/dropdown-menu/DropdownMenuLabel.vue';
import DropdownMenuSeparator from '../../../src/components/common/dropdown-menu/DropdownMenuSeparator.vue';
import DropdownMenuTrigger from '../../../src/components/common/dropdown-menu/DropdownMenuTrigger.vue';
import {
DropdownMenu,
DropdownMenuArrow,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/common/dropdown-menu';
import { Button } from '@/components/common/button';
const meta = {
title: 'Components/Common/DropdownMenu',

View File

@@ -31,7 +31,7 @@
"node",
"happy-dom",
"@vue/test-utils",
"@testing-library/vue"
"@testing-library/vue",
],
"forceConsistentCasingInFileNames": true
},
@@ -44,7 +44,10 @@
"tailwind.config.ts",
"src/theme/**/*.ts",
"**/*.config.ts",
"eslint.config.ts"
"eslint.config.ts",
"stories/**/*.ts",
"stories/**/*.tsx",
"stories/**/*.vue"
],
"exclude": [
"node_modules",

View File

@@ -10,7 +10,6 @@ import type { CreateRCloneRemoteInput } from '~/composables/gql/graphql';
import {
CREATE_REMOTE,
GET_RCLONE_CONFIG_FORM,
LIST_REMOTES,
} from '~/components/RClone/graphql/settings.query';
import { useUnraidApiStore } from '~/store/unraidApi';