From b5b26ad961b11d7bd3b3dc7547b374906b91ea97 Mon Sep 17 00:00:00 2001 From: Einar Date: Tue, 24 Feb 2026 21:32:12 +0100 Subject: [PATCH 1/5] Fixing styling --- Source/.storybook/preview.css | 82 +++++++++++++++++++++++++++++++---- Source/.storybook/preview.js | 11 +++-- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/Source/.storybook/preview.css b/Source/.storybook/preview.css index 8684cf1..8934873 100644 --- a/Source/.storybook/preview.css +++ b/Source/.storybook/preview.css @@ -1,6 +1,18 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +/* 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'; + +html, body { + background-color: var(--surface-ground); + color: var(--text-color); + min-height: 100vh; + margin: 0; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 12px; + line-height: 24px; + font-weight: 400; +} .storybook-wrapper { display: flex; @@ -8,12 +20,66 @@ justify-content: center; min-height: 100vh; padding: 1rem; - background-color: #f3f4f6; /* approx. Tailwind bg-gray-100 */ + background-color: var(--surface-ground); +} + +/* Utility classes matching Chronicle Workbench */ +.highlight { + background-color: var(--highlight-bg); + color: var(--highlight-text-color); +} + +.contentBackground { + background-color: var(--surface-0); } -/* small helper to ensure Dialog widths appear nicely in Storybook */ -.p-dialog .p-dialog-content { +.panel { + background: var(--surface-card); + border: 1px solid var(--surface-border); + border-radius: var(--border-radius); + color: var(--text-color); display: flex; - align-items: center; - gap: 0.75rem; + flex-direction: column; + min-height: 0; +} + +.card { + display: flex; + flex-direction: column; + min-height: 0; +} + +/* DataTable layout helpers */ +.p-datatable { + display: flex; + flex-direction: column; + min-height: 0; + height: 100%; +} + +.p-datatable .p-datatable-wrapper { + flex: 1 1 auto; + min-height: 0; + overflow: auto; +} + +.p-datatable .p-datatable-table { + width: 100%; +} + +/* Prevent menubar from collapsing to hamburger on small screens */ +.p-menubar .p-menubar-button { + display: none !important; +} + +.p-menubar .p-menubar-root-list { + display: flex !important; + flex-direction: row !important; + position: static !important; + width: auto !important; + padding: 0 !important; +} + +.p-menubar { + border: 1px solid var(--surface-border) !important; } diff --git a/Source/.storybook/preview.js b/Source/.storybook/preview.js index 551ed14..06e72d8 100644 --- a/Source/.storybook/preview.js +++ b/Source/.storybook/preview.js @@ -1,12 +1,17 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -import 'primereact/resources/themes/lara-dark-blue/theme.css'; -import 'primereact/resources/primereact.min.css'; import 'primeicons/primeicons.css'; import './preview.css'; export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { expanded: true } + controls: { expanded: true }, + backgrounds: { + default: 'dark', + values: [ + { name: 'dark', value: '#111827' }, + { name: 'surface-card', value: '#1f2937' }, + ], + }, }; From 23c944223cb003c920ddf20e306672e92e8d0f16 Mon Sep 17 00:00:00 2001 From: Einar Date: Wed, 25 Feb 2026 08:47:45 +0100 Subject: [PATCH 2/5] Fixing stories to work --- Source/DataPage/DataPage.stories.tsx | 32 +++++++++++++++--- .../DataTableForObservableQuery.stories.tsx | 32 +++++++++++++++--- .../DataTables/DataTableForQuery.stories.tsx | 33 ++++++++++++++++--- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/Source/DataPage/DataPage.stories.tsx b/Source/DataPage/DataPage.stories.tsx index f47ac0d..9e418cd 100644 --- a/Source/DataPage/DataPage.stories.tsx +++ b/Source/DataPage/DataPage.stories.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { DataPage, MenuItem } from './DataPage'; import { Column } from 'primereact/column'; -import { QueryFor } from '@cratis/arc/queries'; +import { QueryFor, QueryResult } from '@cratis/arc/queries'; const meta: Meta = { title: 'DataPage/DataPage', @@ -26,17 +26,41 @@ interface Person { role: string; } -// Mock query +const mockPersons: Person[] = [ + { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' }, + { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Editor' }, + { id: 3, name: 'Carol White', email: 'carol@example.com', role: 'Viewer' }, + { id: 4, name: 'David Brown', email: 'david@example.com', role: 'Editor' }, + { id: 5, name: 'Eve Davis', email: 'eve@example.com', role: 'Admin' }, + { id: 6, name: 'Frank Miller', email: 'frank@example.com', role: 'Viewer' }, + { id: 7, name: 'Grace Wilson', email: 'grace@example.com', role: 'Editor' }, + { id: 8, name: 'Henry Taylor', email: 'henry@example.com', role: 'Viewer' }, +]; + +// Mock query — overrides perform() to return static data instead of making HTTP calls class PersonsQuery extends QueryFor { readonly route = '/api/persons'; readonly routeTemplate = '/api/persons'; - readonly defaultValue: Person = { id: 0, name: '', email: '', role: '' }; + readonly defaultValue: Person = [] as unknown as Person; readonly parameterDescriptors = []; get requiredRequestParameters() { return []; } constructor() { - super(Object, false); + super(Object, true); + } + override perform(): Promise> { + return Promise.resolve({ + data: mockPersons, + paging: { totalItems: mockPersons.length, totalPages: 1, page: 0, size: mockPersons.length }, + isSuccess: true, + isAuthorized: true, + isValid: true, + hasExceptions: false, + validationResults: [], + exceptionMessages: [], + exceptionStackTrace: '', + } as unknown as QueryResult); } } diff --git a/Source/DataTables/DataTableForObservableQuery.stories.tsx b/Source/DataTables/DataTableForObservableQuery.stories.tsx index 0722279..098da83 100644 --- a/Source/DataTables/DataTableForObservableQuery.stories.tsx +++ b/Source/DataTables/DataTableForObservableQuery.stories.tsx @@ -5,7 +5,7 @@ import React, { useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { DataTableForObservableQuery } from './DataTableForObservableQuery'; import { Column } from 'primereact/column'; -import { ObservableQueryFor } from '@cratis/arc/queries'; +import { ObservableQueryFor, QueryResult, ObservableQuerySubscription } from '@cratis/arc/queries'; import { DataTableSelectionSingleChangeEvent } from 'primereact/datatable'; const meta: Meta = { @@ -25,11 +25,21 @@ interface Task { assignee: string; } -// Mock observable query +const mockTasks: Task[] = [ + { id: 1, title: 'Design system architecture', status: 'done', priority: 'high', assignee: 'Alice' }, + { id: 2, title: 'Implement authentication', status: 'in-progress', priority: 'high', assignee: 'Bob' }, + { id: 3, title: 'Write unit tests', status: 'in-progress', priority: 'medium', assignee: 'Alice' }, + { id: 4, title: 'Set up CI/CD pipeline', status: 'done', priority: 'medium', assignee: 'Charlie' }, + { id: 5, title: 'Update documentation', status: 'todo', priority: 'low', assignee: 'Bob' }, + { id: 6, title: 'Performance profiling', status: 'todo', priority: 'medium', assignee: 'Charlie' }, + { id: 7, title: 'Security audit', status: 'todo', priority: 'high', assignee: 'Alice' }, + { id: 8, title: 'Dependency updates', status: 'in-progress', priority: 'low', assignee: 'Bob' }, +]; + +// Mock observable query — overrides subscribe() to deliver static data instead of opening a WebSocket class TasksQuery extends ObservableQueryFor { readonly route = '/api/tasks'; - readonly routeTemplate = '/api/tasks'; - readonly defaultValue: Task = { id: 0, title: '', status: 'todo', priority: 'low', assignee: '' }; + readonly defaultValue: Task = [] as unknown as Task; readonly parameterDescriptors = []; get requiredRequestParameters() { return []; @@ -37,6 +47,20 @@ class TasksQuery extends ObservableQueryFor { constructor() { super(Object, false); } + override subscribe(callback: (result: QueryResult) => void): ObservableQuerySubscription { + callback({ + data: mockTasks, + paging: { totalItems: mockTasks.length, totalPages: 1, page: 0, size: mockTasks.length }, + isSuccess: true, + isAuthorized: true, + isValid: true, + hasExceptions: false, + validationResults: [], + exceptionMessages: [], + exceptionStackTrace: '', + } as unknown as QueryResult); + return { unsubscribe: () => {} } as unknown as ObservableQuerySubscription; + } } const getStatusColor = (status: string) => { diff --git a/Source/DataTables/DataTableForQuery.stories.tsx b/Source/DataTables/DataTableForQuery.stories.tsx index 6a6dbdd..85020ad 100644 --- a/Source/DataTables/DataTableForQuery.stories.tsx +++ b/Source/DataTables/DataTableForQuery.stories.tsx @@ -5,7 +5,7 @@ import React, { useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { DataTableForQuery } from './DataTableForQuery'; import { Column } from 'primereact/column'; -import { QueryFor } from '@cratis/arc/queries'; +import { QueryFor, QueryResult } from '@cratis/arc/queries'; import { DataTableSelectionSingleChangeEvent } from 'primereact/datatable'; const meta: Meta = { @@ -25,17 +25,40 @@ interface Product { inStock: boolean; } -// Mock query +const mockProducts: Product[] = [ + { id: 1, name: 'Wireless Headphones', category: 'Electronics', price: 79.99, inStock: true }, + { id: 2, name: 'Mechanical Keyboard', category: 'Electronics', price: 129.99, inStock: true }, + { id: 3, name: 'USB-C Hub', category: 'Electronics', price: 49.99, inStock: false }, + { id: 4, name: 'Standing Desk Mat', category: 'Office', price: 34.99, inStock: true }, + { id: 5, name: 'Monitor Stand', category: 'Office', price: 59.99, inStock: true }, + { id: 6, name: 'Webcam HD', category: 'Electronics', price: 89.99, inStock: false }, + { id: 7, name: 'Laptop Sleeve', category: 'Accessories', price: 24.99, inStock: true }, + { id: 8, name: 'Cable Management Kit', category: 'Accessories', price: 14.99, inStock: true }, +]; + +// Mock query — overrides perform() to return static data instead of making HTTP calls class ProductsQuery extends QueryFor { readonly route = '/api/products'; - readonly routeTemplate = '/api/products'; - readonly defaultValue: Product = { id: 0, name: '', category: '', price: 0, inStock: false }; + readonly defaultValue: Product = [] as unknown as Product; readonly parameterDescriptors = []; get requiredRequestParameters() { return []; } constructor() { - super(Object, false); + super(Object, true); + } + override perform(): Promise> { + return Promise.resolve({ + data: mockProducts, + paging: { totalItems: mockProducts.length, totalPages: 1, page: 0, size: mockProducts.length }, + isSuccess: true, + isAuthorized: true, + isValid: true, + hasExceptions: false, + validationResults: [], + exceptionMessages: [], + exceptionStackTrace: '', + } as unknown as QueryResult); } } From b5384d9df98c0fa3181518f98c89198f437eafa4 Mon Sep 17 00:00:00 2001 From: Einar Date: Wed, 25 Feb 2026 08:47:51 +0100 Subject: [PATCH 3/5] Centering things --- Source/Dialogs/BusyIndicatorDialog.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Dialogs/BusyIndicatorDialog.tsx b/Source/Dialogs/BusyIndicatorDialog.tsx index a38da9f..32d5f71 100644 --- a/Source/Dialogs/BusyIndicatorDialog.tsx +++ b/Source/Dialogs/BusyIndicatorDialog.tsx @@ -13,10 +13,12 @@ export const BusyIndicatorDialog = (props: BusyIndicatorDialogRequest) => { onCancel={() => undefined} buttons={null} > - -

- {props.message} -

+
+ +

+ {props.message} +

+
); }; From b5567e02638111850b76018ce2514e8eb686b062 Mon Sep 17 00:00:00 2001 From: Einar Date: Wed, 25 Feb 2026 08:47:56 +0100 Subject: [PATCH 4/5] Adding Dialog stories --- Source/Dialogs/Dialog.stories.tsx | 126 ++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Source/Dialogs/Dialog.stories.tsx diff --git a/Source/Dialogs/Dialog.stories.tsx b/Source/Dialogs/Dialog.stories.tsx new file mode 100644 index 0000000..9968b84 --- /dev/null +++ b/Source/Dialogs/Dialog.stories.tsx @@ -0,0 +1,126 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import React, { useState } from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import { Dialog } from './Dialog'; +import { DialogButtons } from '@cratis/arc.react/dialogs'; +import { Button } from 'primereact/button'; +import { InputText } from 'primereact/inputtext'; + +const meta: Meta = { + title: 'Dialogs/Dialog', + component: Dialog, + parameters: { + layout: 'centered', + }, +}; + +export default meta; +type Story = StoryObj; + +const DialogWrapper = ({ buttons, title, children, isValid }: { buttons: DialogButtons; title: string; children: React.ReactNode; isValid?: boolean }) => { + const [visible, setVisible] = useState(false); + return ( + <> +