diff --git a/Source/.storybook/preview.css b/Source/.storybook/preview.css index 8934873..50215c6 100644 --- a/Source/.storybook/preview.css +++ b/Source/.storybook/preview.css @@ -1,7 +1,6 @@ /* 1. Tailwind (includes preflight reset) — must come first */ @import "tailwindcss"; -/* 2. PrimeReact theme — must come AFTER Tailwind so it overrides the preflight reset */ -@import 'primereact/resources/themes/lara-dark-blue/theme.css'; +/* 2. PrimeReact theme is injected dynamically by the Storybook theme decorator */ html, body { background-color: var(--surface-ground); diff --git a/Source/.storybook/preview.js b/Source/.storybook/preview.js index 06e72d8..80b4556 100644 --- a/Source/.storybook/preview.js +++ b/Source/.storybook/preview.js @@ -1,17 +1,56 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +import React from 'react'; import 'primeicons/primeicons.css'; import './preview.css'; +import darkThemeUrl from 'primereact/resources/themes/lara-dark-blue/theme.css?url'; +import lightThemeUrl from 'primereact/resources/themes/lara-light-blue/theme.css?url'; + +export const globalTypes = { + theme: { + name: 'Theme', + description: 'PrimeReact theme', + defaultValue: 'dark', + toolbar: { + icon: 'paintbrush', + items: [ + { value: 'dark', title: 'Lara Dark Blue' }, + { value: 'light', title: 'Lara Light Blue' }, + ], + showName: true, + }, + }, +}; + +function applyThemeLink(href) { + let link = document.getElementById('primereact-theme'); + if (!link) { + link = document.createElement('link'); + link.id = 'primereact-theme'; + link.rel = 'stylesheet'; + document.head.appendChild(link); + } + if (link.href !== href) link.href = href; +} + +export const decorators = [ + (Story, context) => { + applyThemeLink(context.globals.theme === 'light' ? lightThemeUrl : darkThemeUrl); + return React.createElement(Story); + }, +]; export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { expanded: true }, - backgrounds: { - default: 'dark', - values: [ - { name: 'dark', value: '#111827' }, - { name: 'surface-card', value: '#1f2937' }, - ], - }, + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { expanded: true }, + backgrounds: { + default: 'dark', + values: [ + { name: 'dark', value: '#111827' }, + { name: 'surface-card', value: '#1f2937' }, + { name: 'light', value: '#ffffff' }, + { name: 'surface-light', value: '#f8f9fa' }, + ], + }, }; diff --git a/Source/CommandDialog/CommandStepper.tsx b/Source/CommandDialog/CommandStepper.tsx index c65bee5..f246fb0 100644 --- a/Source/CommandDialog/CommandStepper.tsx +++ b/Source/CommandDialog/CommandStepper.tsx @@ -196,7 +196,7 @@ export const CommandStepperContent = ({ const existingStyle = existing.style as Record | undefined; return { ...existing, - style: { ...existingStyle, backgroundColor: bgColor, color: '#fff' } + style: { ...existingStyle, backgroundColor: bgColor, color: 'var(--primary-color-text)' } }; } } diff --git a/Source/CommandDialog/StepperCommandDialog.tsx b/Source/CommandDialog/StepperCommandDialog.tsx index 341376b..407e081 100644 --- a/Source/CommandDialog/StepperCommandDialog.tsx +++ b/Source/CommandDialog/StepperCommandDialog.tsx @@ -103,6 +103,9 @@ const StepperCommandDialogWrapper = >(new Set([0])); const [stepErrors, setStepErrors] = useState([]); + // useDialogContext() is called unconditionally on every render — the try/catch only suppresses + // the exception when the dialog is used standalone (outside a provider). React's Rules of Hooks + // are not violated because the hook is always called; the try/catch never skips the call. let contextCloseDialog: ((result: DialogResult) => void) | undefined; try { const context = useDialogContext(); @@ -174,13 +177,13 @@ const StepperCommandDialogWrapper = - {title} +
+ {title}
); const footer = ( -
+
{!isFirstStep && (