@object-ui/components 2.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +12 -12
- package/CHANGELOG.md +28 -0
- package/dist/index.css +1 -1
- package/dist/index.js +19610 -19344
- package/dist/index.umd.cjs +29 -29
- package/dist/src/custom/index.d.ts +2 -0
- package/dist/src/custom/view-skeleton.d.ts +37 -0
- package/dist/src/custom/view-states.d.ts +33 -0
- package/package.json +17 -17
- package/src/__tests__/__snapshots__/snapshot-critical.test.tsx.snap +811 -0
- package/src/__tests__/__snapshots__/snapshot.test.tsx.snap +327 -0
- package/src/__tests__/accessibility.test.tsx +137 -0
- package/src/__tests__/api-consistency.test.tsx +596 -0
- package/src/__tests__/color-contrast.test.tsx +212 -0
- package/src/__tests__/edge-cases.test.tsx +285 -0
- package/src/__tests__/snapshot-critical.test.tsx +317 -0
- package/src/__tests__/snapshot.test.tsx +205 -0
- package/src/__tests__/wcag-audit.test.tsx +493 -0
- package/src/custom/index.ts +2 -0
- package/src/custom/view-skeleton.tsx +243 -0
- package/src/custom/view-states.tsx +153 -0
- package/src/renderers/complex/data-table.tsx +28 -13
- package/src/renderers/complex/resizable.tsx +20 -17
- package/src/renderers/data-display/list.tsx +1 -1
- package/src/renderers/data-display/table.tsx +1 -1
- package/src/renderers/data-display/tree-view.tsx +2 -1
- package/src/renderers/form/form.tsx +10 -6
- package/src/renderers/layout/aspect-ratio.tsx +1 -1
- package/src/stories-json/Accessibility.mdx +297 -0
- package/src/stories-json/EdgeCases.stories.tsx +160 -0
- package/src/stories-json/GettingStarted.mdx +89 -0
- package/src/stories-json/Introduction.mdx +127 -0
- package/src/ui/slider.tsx +6 -2
- package/src/stories/Introduction.mdx +0 -61
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Getting Started/Accessibility" />
|
|
4
|
+
|
|
5
|
+
# Accessibility Reference
|
|
6
|
+
|
|
7
|
+
ObjectUI targets **WCAG 2.1 Level AA** conformance. Every component inherits accessible
|
|
8
|
+
primitives from [Radix UI](https://www.radix-ui.com/) and is styled with Tailwind CSS
|
|
9
|
+
utility classes that respect user preferences for reduced motion and color contrast.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Compliance Baseline
|
|
14
|
+
|
|
15
|
+
| Standard | Level | Status |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| WCAG 2.1 | AA | ✅ Target conformance for all components |
|
|
18
|
+
| WAI-ARIA 1.2 | — | ✅ Roles, states, and properties via Radix |
|
|
19
|
+
| Section 508 | — | ✅ Covered by WCAG AA compliance |
|
|
20
|
+
|
|
21
|
+
ObjectUI relies on **Radix UI** for its accessible primitive layer. Radix components
|
|
22
|
+
ship with correct ARIA roles, keyboard interactions, and focus management built in.
|
|
23
|
+
Shadcn UI wraps these primitives with Tailwind styling while preserving all accessibility
|
|
24
|
+
behaviour.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Keyboard Navigation Patterns
|
|
29
|
+
|
|
30
|
+
All interactive components support keyboard navigation following WAI-ARIA authoring
|
|
31
|
+
practices. Below is a reference of the patterns used across component types.
|
|
32
|
+
|
|
33
|
+
### General Controls
|
|
34
|
+
|
|
35
|
+
| Component | Key | Action |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| **Button** | `Enter` / `Space` | Activate the button |
|
|
38
|
+
| **Link** | `Enter` | Follow the link |
|
|
39
|
+
| **Toggle** | `Enter` / `Space` | Toggle the state |
|
|
40
|
+
| All focusable | `Tab` | Move focus to next element |
|
|
41
|
+
| All focusable | `Shift + Tab` | Move focus to previous element |
|
|
42
|
+
|
|
43
|
+
### Composite Widgets
|
|
44
|
+
|
|
45
|
+
| Component | Key | Action |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| **Tabs** | `Arrow Left` / `Arrow Right` | Move between tab triggers |
|
|
48
|
+
| **Tabs** | `Enter` / `Space` | Activate focused tab |
|
|
49
|
+
| **Tabs** | `Home` / `End` | Jump to first / last tab |
|
|
50
|
+
| **Accordion** | `Arrow Up` / `Arrow Down` | Move between headers |
|
|
51
|
+
| **Accordion** | `Enter` / `Space` | Expand / collapse section |
|
|
52
|
+
| **Accordion** | `Home` / `End` | Jump to first / last header |
|
|
53
|
+
| **Menu / DropdownMenu** | `Arrow Up` / `Arrow Down` | Navigate menu items |
|
|
54
|
+
| **Menu / DropdownMenu** | `Enter` / `Space` | Activate item |
|
|
55
|
+
| **Menu / DropdownMenu** | `Escape` | Close menu and return focus |
|
|
56
|
+
| **NavigationMenu** | `Arrow Left` / `Arrow Right` | Move between top-level items |
|
|
57
|
+
| **NavigationMenu** | `Arrow Down` | Open submenu |
|
|
58
|
+
| **NavigationMenu** | `Escape` | Close submenu |
|
|
59
|
+
|
|
60
|
+
### Form Fields
|
|
61
|
+
|
|
62
|
+
| Component | Key | Action |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| **Input / Textarea** | Standard typing | Enter text |
|
|
65
|
+
| **Select** | `Arrow Up` / `Arrow Down` | Navigate options |
|
|
66
|
+
| **Select** | `Enter` | Select option |
|
|
67
|
+
| **Select** | `Escape` | Close listbox |
|
|
68
|
+
| **Checkbox** | `Space` | Toggle checked state |
|
|
69
|
+
| **Radio Group** | `Arrow Up` / `Arrow Down` | Move selection |
|
|
70
|
+
| **Switch** | `Space` | Toggle on/off |
|
|
71
|
+
| **Slider** | `Arrow Left` / `Arrow Right` | Decrease / increase value |
|
|
72
|
+
| **Slider** | `Home` / `End` | Set to minimum / maximum |
|
|
73
|
+
| **DatePicker** | `Arrow Keys` | Navigate calendar grid |
|
|
74
|
+
| **DatePicker** | `Enter` | Select date |
|
|
75
|
+
| **DatePicker** | `Escape` | Close calendar popover |
|
|
76
|
+
|
|
77
|
+
### Overlay Components
|
|
78
|
+
|
|
79
|
+
| Component | Key | Action |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| **Dialog** | `Escape` | Close the dialog |
|
|
82
|
+
| **Dialog** | `Tab` | Cycle focus within dialog (focus trap) |
|
|
83
|
+
| **AlertDialog** | `Escape` | Close (if not critical) |
|
|
84
|
+
| **AlertDialog** | `Tab` | Cycle focus within alert |
|
|
85
|
+
| **Popover** | `Escape` | Close popover |
|
|
86
|
+
| **Tooltip** | `Escape` | Dismiss tooltip |
|
|
87
|
+
| **Sheet** | `Escape` | Close sheet overlay |
|
|
88
|
+
| **Sheet** | `Tab` | Cycle focus within sheet |
|
|
89
|
+
|
|
90
|
+
### Data Views (Plugins)
|
|
91
|
+
|
|
92
|
+
| Component | Key | Action |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| **DataTable / Grid** | `Arrow Keys` | Navigate cells |
|
|
95
|
+
| **DataTable / Grid** | `Enter` | Activate cell / begin editing |
|
|
96
|
+
| **DataTable / Grid** | `Escape` | Cancel editing |
|
|
97
|
+
| **DataTable / Grid** | `Tab` | Move to next focusable cell |
|
|
98
|
+
| **Kanban** | `Tab` | Move between cards |
|
|
99
|
+
| **Kanban** | `Enter` / `Space` | Open card detail |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ARIA Roles and Attributes
|
|
104
|
+
|
|
105
|
+
ObjectUI components emit the correct ARIA roles and attributes automatically through
|
|
106
|
+
Radix primitives. Below is a reference of the key mappings.
|
|
107
|
+
|
|
108
|
+
### Primitive Components
|
|
109
|
+
|
|
110
|
+
| Component | ARIA Role | Key Attributes |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| **Button** | `button` | `aria-disabled`, `aria-pressed` (toggle) |
|
|
113
|
+
| **Badge** | `status` | `aria-label` (when used as status indicator) |
|
|
114
|
+
| **Card** | `region` | `aria-labelledby` (card title) |
|
|
115
|
+
| **Separator** | `separator` | `aria-orientation` |
|
|
116
|
+
| **Avatar** | `img` | `alt` text on image, fallback initials |
|
|
117
|
+
| **Progress** | `progressbar` | `aria-valuenow`, `aria-valuemin`, `aria-valuemax` |
|
|
118
|
+
| **Skeleton** | `status` | `aria-busy="true"`, `aria-label="Loading"` |
|
|
119
|
+
|
|
120
|
+
### Composite Components
|
|
121
|
+
|
|
122
|
+
| Component | ARIA Role | Key Attributes |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| **Tabs** | `tablist`, `tab`, `tabpanel` | `aria-selected`, `aria-controls`, `aria-labelledby` |
|
|
125
|
+
| **Accordion** | `region` | `aria-expanded`, `aria-controls` on triggers |
|
|
126
|
+
| **Breadcrumb** | `navigation` | `aria-label="Breadcrumb"`, `aria-current="page"` on active |
|
|
127
|
+
| **NavigationMenu** | `navigation` | `aria-label`, `aria-expanded` on submenus |
|
|
128
|
+
| **Carousel** | `region` | `aria-roledescription="carousel"`, `aria-label` on slides |
|
|
129
|
+
|
|
130
|
+
### Form Components
|
|
131
|
+
|
|
132
|
+
| Component | ARIA Role | Key Attributes |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| **Input** | `textbox` | `aria-required`, `aria-invalid`, `aria-describedby` (error/hint) |
|
|
135
|
+
| **Textarea** | `textbox` | `aria-required`, `aria-invalid`, `aria-describedby` |
|
|
136
|
+
| **Select** | `combobox` / `listbox` | `aria-expanded`, `aria-activedescendant`, `aria-required` |
|
|
137
|
+
| **Checkbox** | `checkbox` | `aria-checked`, `aria-required` |
|
|
138
|
+
| **Radio Group** | `radiogroup`, `radio` | `aria-checked`, `aria-required` |
|
|
139
|
+
| **Switch** | `switch` | `aria-checked`, `aria-label` |
|
|
140
|
+
| **Slider** | `slider` | `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, `aria-label` |
|
|
141
|
+
| **Label** | — | `for` / `htmlFor` association with input |
|
|
142
|
+
|
|
143
|
+
### Overlay Components
|
|
144
|
+
|
|
145
|
+
| Component | ARIA Role | Key Attributes |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| **Dialog** | `dialog` | `aria-modal="true"`, `aria-labelledby`, `aria-describedby` |
|
|
148
|
+
| **AlertDialog** | `alertdialog` | `aria-modal="true"`, `aria-labelledby`, `aria-describedby` |
|
|
149
|
+
| **Popover** | `dialog` | `aria-expanded` on trigger, auto `aria-labelledby` |
|
|
150
|
+
| **Tooltip** | `tooltip` | `aria-describedby` on trigger element |
|
|
151
|
+
| **Sheet** | `dialog` | `aria-modal="true"`, `aria-labelledby` |
|
|
152
|
+
| **DropdownMenu** | `menu`, `menuitem` | `aria-expanded`, `aria-haspopup` |
|
|
153
|
+
| **ContextMenu** | `menu`, `menuitem` | `aria-expanded`, `aria-haspopup` |
|
|
154
|
+
|
|
155
|
+
### Data View Components
|
|
156
|
+
|
|
157
|
+
| Component | ARIA Role | Key Attributes |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| **DataTable** | `table`, `row`, `cell` | `aria-sort` on sortable columns, `aria-rowcount` |
|
|
160
|
+
| **Grid (AG Grid)** | `grid`, `row`, `gridcell` | `aria-colcount`, `aria-rowindex`, `aria-selected` |
|
|
161
|
+
| **Kanban** | `list`, `listitem` | `aria-label` per column, `aria-grabbed` on draggable cards |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Screen Reader Behaviour
|
|
166
|
+
|
|
167
|
+
ObjectUI components are designed to work seamlessly with screen readers
|
|
168
|
+
(NVDA, JAWS, VoiceOver, TalkBack). Key behaviours include:
|
|
169
|
+
|
|
170
|
+
### Focus Management
|
|
171
|
+
- **Dialogs and Sheets** trap focus within the overlay while open. On close, focus
|
|
172
|
+
returns to the trigger element.
|
|
173
|
+
- **Menus and Popovers** return focus to their trigger on dismiss.
|
|
174
|
+
- **Toast notifications** use `role="status"` with `aria-live="polite"` so screen
|
|
175
|
+
readers announce them without interrupting the user.
|
|
176
|
+
|
|
177
|
+
### Live Regions
|
|
178
|
+
- **Form validation errors** are linked via `aria-describedby`, so errors are announced
|
|
179
|
+
when the field receives focus.
|
|
180
|
+
- **Loading states** use `aria-busy="true"` on the loading container and `aria-live`
|
|
181
|
+
regions to announce completion.
|
|
182
|
+
- **Toast / Alert** components use `aria-live="polite"` or `aria-live="assertive"`
|
|
183
|
+
depending on urgency.
|
|
184
|
+
|
|
185
|
+
### Landmark Regions
|
|
186
|
+
- **AppShell** emits `<header>`, `<nav>`, `<main>`, and `<aside>` landmarks so screen
|
|
187
|
+
reader users can jump between sections.
|
|
188
|
+
- **Breadcrumb** uses `<nav aria-label="Breadcrumb">` for quick landmark navigation.
|
|
189
|
+
- **Sidebar** uses `<aside>` with a descriptive `aria-label`.
|
|
190
|
+
|
|
191
|
+
### Announcements
|
|
192
|
+
- **Sortable table columns** announce sort direction changes via `aria-sort`.
|
|
193
|
+
- **Accordion** sections announce expanded/collapsed state.
|
|
194
|
+
- **Tabs** announce the active tab label on selection.
|
|
195
|
+
- **Carousel** announces slide transitions with `aria-roledescription`.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Color Contrast Requirements
|
|
200
|
+
|
|
201
|
+
ObjectUI uses Tailwind CSS design tokens (CSS custom properties) for all color
|
|
202
|
+
values. The default theme meets WCAG AA contrast ratios:
|
|
203
|
+
|
|
204
|
+
| Element | Minimum Ratio | Standard |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| Body text (`foreground` on `background`) | **4.5 : 1** | WCAG AA — Normal text |
|
|
207
|
+
| Large text (≥ 18pt or ≥ 14pt bold) | **3 : 1** | WCAG AA — Large text |
|
|
208
|
+
| UI components and graphical objects | **3 : 1** | WCAG 2.1 SC 1.4.11 |
|
|
209
|
+
| Focus indicators | **3 : 1** | WCAG 2.1 SC 1.4.11 |
|
|
210
|
+
|
|
211
|
+
### Theme Tokens
|
|
212
|
+
|
|
213
|
+
All colors are defined as CSS variables in HSL format:
|
|
214
|
+
|
|
215
|
+
```css
|
|
216
|
+
:root {
|
|
217
|
+
--background: 0 0% 100%;
|
|
218
|
+
--foreground: 222 84% 5%;
|
|
219
|
+
--primary: 222 47% 11%;
|
|
220
|
+
--primary-foreground: 210 40% 98%;
|
|
221
|
+
--destructive: 0 84% 60%;
|
|
222
|
+
--destructive-foreground: 0 0% 98%;
|
|
223
|
+
--muted: 210 40% 96%;
|
|
224
|
+
--muted-foreground: 215 16% 47%;
|
|
225
|
+
/* ...additional tokens */
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
When customising tokens, verify contrast ratios with a tool such as the
|
|
230
|
+
[WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) or the
|
|
231
|
+
built-in Storybook accessibility panel.
|
|
232
|
+
|
|
233
|
+
### Dark Mode
|
|
234
|
+
|
|
235
|
+
ObjectUI supports a `.dark` class on the root element. The dark theme ships with
|
|
236
|
+
its own set of tokens that maintain the same contrast ratios:
|
|
237
|
+
|
|
238
|
+
```css
|
|
239
|
+
.dark {
|
|
240
|
+
--background: 222 84% 5%;
|
|
241
|
+
--foreground: 210 40% 98%;
|
|
242
|
+
/* ...dark tokens */
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Focus Indicators
|
|
247
|
+
|
|
248
|
+
All interactive elements display a visible focus ring when navigated via keyboard.
|
|
249
|
+
The default ring uses the `--ring` token and applies a `2px` outline with a `2px`
|
|
250
|
+
offset, ensuring it meets the **3 : 1** non-text contrast requirement.
|
|
251
|
+
|
|
252
|
+
```css
|
|
253
|
+
:root {
|
|
254
|
+
--ring: 222 47% 11%;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
> **Tip:** Never remove focus outlines (`outline: none`) without providing an
|
|
259
|
+
> equivalent visible indicator. ObjectUI's `focus-visible` styles handle this
|
|
260
|
+
> automatically.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Testing Accessibility
|
|
265
|
+
|
|
266
|
+
### Storybook A11y Addon
|
|
267
|
+
|
|
268
|
+
Every story in this Storybook can be audited via the **Accessibility** panel in the
|
|
269
|
+
addons bar. It runs [axe-core](https://github.com/dequelabs/axe-core) checks and
|
|
270
|
+
reports violations, passes, and incomplete audits.
|
|
271
|
+
|
|
272
|
+
### Automated Testing
|
|
273
|
+
|
|
274
|
+
Use `vitest` with `@testing-library/jest-dom` matchers for assertions like
|
|
275
|
+
`toBeVisible()`, `toHaveAccessibleName()`, and `toHaveAttribute('aria-expanded')`.
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
import { render, screen } from '@testing-library/react';
|
|
279
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
280
|
+
|
|
281
|
+
expect.extend(toHaveNoViolations);
|
|
282
|
+
|
|
283
|
+
it('Button has no accessibility violations', async () => {
|
|
284
|
+
const { container } = render(<Button>Click me</Button>);
|
|
285
|
+
const results = await axe(container);
|
|
286
|
+
expect(results).toHaveNoViolations();
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Manual Testing Checklist
|
|
291
|
+
|
|
292
|
+
- [ ] Navigate all interactive elements using only the keyboard (`Tab`, `Enter`, `Space`, `Escape`, `Arrow` keys).
|
|
293
|
+
- [ ] Verify visible focus indicators on every focusable element.
|
|
294
|
+
- [ ] Test with a screen reader (VoiceOver on macOS, NVDA on Windows).
|
|
295
|
+
- [ ] Check colour contrast using the browser DevTools or WebAIM checker.
|
|
296
|
+
- [ ] Confirm the page is usable at 200% browser zoom.
|
|
297
|
+
- [ ] Verify `prefers-reduced-motion` is respected (animations are disabled or reduced).
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { SchemaRenderer } from '../SchemaRenderer';
|
|
3
|
+
import type { BaseSchema } from '@object-ui/types';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components / Edge Cases',
|
|
7
|
+
component: SchemaRenderer,
|
|
8
|
+
parameters: { layout: 'padded' },
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
argTypes: {
|
|
11
|
+
schema: { table: { disable: true } },
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<any>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
const renderStory = (args: any) => <SchemaRenderer schema={args as unknown as BaseSchema} />;
|
|
19
|
+
|
|
20
|
+
// ── Empty States ──────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export const EmptyStateDefault: Story = {
|
|
23
|
+
name: 'Empty State – Default',
|
|
24
|
+
render: renderStory,
|
|
25
|
+
args: {
|
|
26
|
+
type: 'empty',
|
|
27
|
+
description: 'No items to display',
|
|
28
|
+
} as any,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const EmptyStateWithAction: Story = {
|
|
32
|
+
name: 'Empty State – With Action',
|
|
33
|
+
render: renderStory,
|
|
34
|
+
args: {
|
|
35
|
+
type: 'empty',
|
|
36
|
+
description: 'No results found. Try a different search or create a new item.',
|
|
37
|
+
children: [
|
|
38
|
+
{ type: 'button', content: 'Create New', variant: 'default', size: 'sm' },
|
|
39
|
+
],
|
|
40
|
+
} as any,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ── Loading / Spinner ─────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export const SpinnerSmall: Story = {
|
|
46
|
+
name: 'Spinner – Small',
|
|
47
|
+
render: renderStory,
|
|
48
|
+
args: {
|
|
49
|
+
type: 'spinner',
|
|
50
|
+
size: 'sm',
|
|
51
|
+
className: 'text-primary',
|
|
52
|
+
} as any,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const SpinnerLarge: Story = {
|
|
56
|
+
name: 'Spinner – Large',
|
|
57
|
+
render: renderStory,
|
|
58
|
+
args: {
|
|
59
|
+
type: 'spinner',
|
|
60
|
+
size: 'lg',
|
|
61
|
+
className: 'text-primary',
|
|
62
|
+
} as any,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const LoadingWithText: Story = {
|
|
66
|
+
name: 'Loading – With Text',
|
|
67
|
+
render: renderStory,
|
|
68
|
+
args: {
|
|
69
|
+
type: 'loading',
|
|
70
|
+
text: 'Please wait while we fetch your data…',
|
|
71
|
+
className: 'h-[200px]',
|
|
72
|
+
} as any,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ── Overflow / Long Text ──────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
const LONG_TEXT =
|
|
78
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit.';
|
|
79
|
+
|
|
80
|
+
export const CardWithLongText: Story = {
|
|
81
|
+
name: 'Card – Very Long Text',
|
|
82
|
+
render: renderStory,
|
|
83
|
+
args: {
|
|
84
|
+
type: 'card',
|
|
85
|
+
className: 'w-[350px]',
|
|
86
|
+
title: 'This is an extremely long card title that should test how the component handles overflow and text wrapping gracefully',
|
|
87
|
+
description: LONG_TEXT,
|
|
88
|
+
children: [
|
|
89
|
+
{
|
|
90
|
+
type: 'text',
|
|
91
|
+
content: LONG_TEXT + ' ' + LONG_TEXT,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
} as any,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const BadgeWithLongText: Story = {
|
|
98
|
+
name: 'Badge – Very Long Text',
|
|
99
|
+
render: renderStory,
|
|
100
|
+
args: {
|
|
101
|
+
type: 'badge',
|
|
102
|
+
label: 'This is an unusually long badge label that tests truncation and overflow behaviour in tight layouts',
|
|
103
|
+
} as any,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const ButtonWithIconAndLongText: Story = {
|
|
107
|
+
name: 'Button – Icon + Long Text',
|
|
108
|
+
render: (args: any) => <SchemaRenderer schema={args as unknown as BaseSchema} />,
|
|
109
|
+
args: {
|
|
110
|
+
type: 'button',
|
|
111
|
+
props: { variant: 'default' },
|
|
112
|
+
children: [
|
|
113
|
+
{ type: 'icon', name: 'download', className: 'mr-2 h-4 w-4' },
|
|
114
|
+
{
|
|
115
|
+
type: 'text',
|
|
116
|
+
content: 'Download All Reports For The Current Financial Year Including Amendments',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
} as any,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// ── RTL Layout ────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
export const RTLCard: Story = {
|
|
125
|
+
name: 'RTL – Card Layout',
|
|
126
|
+
render: (args: any) => (
|
|
127
|
+
<div dir="rtl">
|
|
128
|
+
<SchemaRenderer schema={args as unknown as BaseSchema} />
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
args: {
|
|
132
|
+
type: 'card',
|
|
133
|
+
className: 'w-[350px]',
|
|
134
|
+
title: 'عنوان البطاقة',
|
|
135
|
+
description: 'هذا النص باللغة العربية لاختبار تخطيط الاتجاه من اليمين إلى اليسار.',
|
|
136
|
+
children: [
|
|
137
|
+
{ type: 'text', content: 'محتوى البطاقة الرئيسي يظهر هنا باللغة العربية.' },
|
|
138
|
+
],
|
|
139
|
+
footer: [
|
|
140
|
+
{ type: 'button', props: { variant: 'outline' }, children: [{ type: 'text', content: 'إلغاء' }] },
|
|
141
|
+
{ type: 'button', children: [{ type: 'text', content: 'حفظ' }] },
|
|
142
|
+
],
|
|
143
|
+
} as any,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const RTLAlert: Story = {
|
|
147
|
+
name: 'RTL – Alert',
|
|
148
|
+
render: (args: any) => (
|
|
149
|
+
<div dir="rtl">
|
|
150
|
+
<SchemaRenderer schema={args as unknown as BaseSchema} />
|
|
151
|
+
</div>
|
|
152
|
+
),
|
|
153
|
+
args: {
|
|
154
|
+
type: 'alert',
|
|
155
|
+
variant: 'destructive',
|
|
156
|
+
title: 'خطأ',
|
|
157
|
+
description: 'انتهت صلاحية الجلسة. الرجاء تسجيل الدخول مرة أخرى.',
|
|
158
|
+
className: 'w-[400px]',
|
|
159
|
+
} as any,
|
|
160
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Getting Started" />
|
|
4
|
+
|
|
5
|
+
# ObjectUI Component Library
|
|
6
|
+
|
|
7
|
+
ObjectUI is a **server-driven UI engine** that renders enterprise interfaces from JSON metadata.
|
|
8
|
+
Built on **React**, **Tailwind CSS**, and **Shadcn UI (Radix)**, it bridges the speed of
|
|
9
|
+
low-code with the design quality of hand-crafted components.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Core packages
|
|
15
|
+
pnpm add @object-ui/types @object-ui/core @object-ui/react
|
|
16
|
+
|
|
17
|
+
# UI layer
|
|
18
|
+
pnpm add @object-ui/components @object-ui/fields @object-ui/layout
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peer dependencies — make sure these are in your project:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add react react-dom tailwindcss
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { ObjectRenderer } from '@object-ui/react';
|
|
31
|
+
|
|
32
|
+
const schema = {
|
|
33
|
+
type: 'Card',
|
|
34
|
+
props: { title: 'Hello' },
|
|
35
|
+
children: [
|
|
36
|
+
{
|
|
37
|
+
type: 'Text',
|
|
38
|
+
props: { content: 'Rendered from JSON!' },
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default function App() {
|
|
44
|
+
return <ObjectRenderer schema={schema} />;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The `ObjectRenderer` resolves each node in the schema tree against the **Component Registry**,
|
|
49
|
+
producing a fully interactive React tree.
|
|
50
|
+
|
|
51
|
+
## Accessibility
|
|
52
|
+
|
|
53
|
+
Every component in ObjectUI follows the **WAI-ARIA** authoring practices provided by
|
|
54
|
+
[Radix UI](https://www.radix-ui.com/). This means:
|
|
55
|
+
|
|
56
|
+
- Correct ARIA roles and attributes out of the box
|
|
57
|
+
- Full keyboard navigation support
|
|
58
|
+
- Focus management for overlays, dialogs, and menus
|
|
59
|
+
- Screen-reader-friendly labels and live regions
|
|
60
|
+
|
|
61
|
+
Run the Storybook accessibility addon (included) to audit any story for violations.
|
|
62
|
+
|
|
63
|
+
## Theme Configuration
|
|
64
|
+
|
|
65
|
+
ObjectUI uses Tailwind CSS for styling. Customise the design tokens through your
|
|
66
|
+
`tailwind.config` and CSS variables:
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
@layer base {
|
|
70
|
+
:root {
|
|
71
|
+
--background: 0 0% 100%;
|
|
72
|
+
--foreground: 222 84% 5%;
|
|
73
|
+
--primary: 222 47% 11%;
|
|
74
|
+
--primary-foreground: 210 40% 98%;
|
|
75
|
+
/* ...other tokens */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
All Shadcn-based components pick up these tokens automatically — no prop drilling required.
|
|
81
|
+
|
|
82
|
+
## Explore
|
|
83
|
+
|
|
84
|
+
Use the sidebar to browse every component. Each story shows:
|
|
85
|
+
|
|
86
|
+
- **Live preview** — interact directly in the canvas
|
|
87
|
+
- **Controls** — tweak props in real time
|
|
88
|
+
- **Docs** — auto-generated API tables (when tagged with `autodocs`)
|
|
89
|
+
- **Accessibility** — axe audit panel
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Getting Started/Introduction" />
|
|
4
|
+
|
|
5
|
+
# ObjectUI — Universal Server-Driven UI Engine
|
|
6
|
+
|
|
7
|
+
ObjectUI is a **JSON-to-UI rendering engine** for React that transforms declarative schemas
|
|
8
|
+
into fully interactive enterprise interfaces. Built on **Shadcn UI**, **Radix Primitives**,
|
|
9
|
+
and **Tailwind CSS**, it bridges the speed of low-code platforms with the design quality of
|
|
10
|
+
hand-crafted components.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why ObjectUI?
|
|
15
|
+
|
|
16
|
+
| Benefit | Description |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **Schema-Driven** | Define your UI as JSON — render it anywhere with zero custom JSX. |
|
|
19
|
+
| **Enterprise-Ready** | Dashboards, data grids, Kanbans, forms, and CRUDs out of the box. |
|
|
20
|
+
| **Accessible by Default** | Every component inherits WAI-ARIA patterns from Radix UI. |
|
|
21
|
+
| **Themeable** | Tailwind CSS design tokens + CSS variables — no prop drilling required. |
|
|
22
|
+
| **Backend Agnostic** | Works with any API or data source; no server lock-in. |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Architecture Overview
|
|
27
|
+
|
|
28
|
+
ObjectUI follows a layered architecture where each layer has a single responsibility:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────────────────────┐
|
|
32
|
+
│ Your Application │
|
|
33
|
+
├─────────────────────────────────────────────────────┤
|
|
34
|
+
│ @object-ui/react (Runtime) │
|
|
35
|
+
│ ObjectRenderer · SchemaRenderer · Hooks │
|
|
36
|
+
├──────────┬──────────────────────────┬───────────────┤
|
|
37
|
+
│ Plugins │ @object-ui/fields │ @object-ui/ │
|
|
38
|
+
│ Grid, │ Text, Number, Select, │ layout │
|
|
39
|
+
│ Kanban, │ Date, Checkbox, … │ AppShell, │
|
|
40
|
+
│ Charts, │ │ Sidebar, │
|
|
41
|
+
│ Form, … │ │ Header │
|
|
42
|
+
├──────────┴──────────────────────────┴───────────────┤
|
|
43
|
+
│ @object-ui/components (Atoms) │
|
|
44
|
+
│ Button · Card · Badge · Dialog · Tabs · … │
|
|
45
|
+
├─────────────────────────────────────────────────────┤
|
|
46
|
+
│ @object-ui/core (Engine) │
|
|
47
|
+
│ Component Registry · Schema Validation · │
|
|
48
|
+
│ Expression Evaluation · Data Binding │
|
|
49
|
+
├─────────────────────────────────────────────────────┤
|
|
50
|
+
│ @object-ui/types (Protocol) │
|
|
51
|
+
│ ComponentSchema · ActionSchema · FieldSchema │
|
|
52
|
+
│ Pure TypeScript — 0 deps │
|
|
53
|
+
└─────────────────────────────────────────────────────┘
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Layer Breakdown
|
|
57
|
+
|
|
58
|
+
1. **`@object-ui/types`** — Pure TypeScript interfaces that define the JSON protocol.
|
|
59
|
+
Zero runtime dependencies.
|
|
60
|
+
2. **`@object-ui/core`** — The rendering engine: component registry, schema validation,
|
|
61
|
+
expression evaluation (`visible: "${data.age > 18}"`), and data binding. No UI code.
|
|
62
|
+
3. **`@object-ui/components`** — Shadcn-based UI atoms (Button, Card, Badge, Dialog, etc.).
|
|
63
|
+
Pure presentation — no business logic.
|
|
64
|
+
4. **`@object-ui/fields`** — Standard field renderers for form inputs (Text, Number,
|
|
65
|
+
Select, Date). Each field implements the `FieldWidgetProps` contract.
|
|
66
|
+
5. **`@object-ui/layout`** — Page-level structure components (AppShell, Header, Sidebar).
|
|
67
|
+
Routing-aware composition.
|
|
68
|
+
6. **`@object-ui/plugin-*`** — Complex view widgets (Grid, Kanban, Charts, Map, Gantt,
|
|
69
|
+
Form, Timeline). Heavy third-party dependencies are isolated here.
|
|
70
|
+
7. **`@object-ui/react`** — The public runtime entry point that ties everything together.
|
|
71
|
+
Provides `ObjectRenderer`, hooks, and provider components.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { ObjectRenderer } from '@object-ui/react';
|
|
79
|
+
|
|
80
|
+
const schema = {
|
|
81
|
+
type: 'Card',
|
|
82
|
+
props: { title: 'Welcome' },
|
|
83
|
+
children: [
|
|
84
|
+
{ type: 'Text', props: { content: 'Rendered from JSON!' } },
|
|
85
|
+
{ type: 'Button', props: { label: 'Click me', variant: 'default' } },
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default function App() {
|
|
90
|
+
return <ObjectRenderer schema={schema} />;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The `ObjectRenderer` walks the schema tree, resolves each node against the **Component
|
|
95
|
+
Registry**, and produces a fully interactive React component tree.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Browsing This Storybook
|
|
100
|
+
|
|
101
|
+
Use the **sidebar** on the left to explore every component. Stories are organised into:
|
|
102
|
+
|
|
103
|
+
| Section | What You'll Find |
|
|
104
|
+
|---|---|
|
|
105
|
+
| **Getting Started** | Introduction, data binding guides, and setup instructions. |
|
|
106
|
+
| **Primitives** | Base Shadcn atoms — Buttons, Cards, Badges, Inputs, Dialogs, and more. |
|
|
107
|
+
| **Fields** | Form field renderers — text, number, select, date, checkbox, etc. |
|
|
108
|
+
| **Plugins** | Complex view widgets — Data Grid, Kanban, Charts, Map, Gantt, Timeline. |
|
|
109
|
+
| **Templates** | Pre-built page layouts — Dashboards, Reports, Sidebar navigation. |
|
|
110
|
+
| **Apps** | Full application demos (e.g. CRM) composed from multiple plugins. |
|
|
111
|
+
|
|
112
|
+
Each story provides:
|
|
113
|
+
|
|
114
|
+
- **Canvas** — Live interactive preview.
|
|
115
|
+
- **Controls** — Tweak JSON props in real time via the controls panel.
|
|
116
|
+
- **Docs** — Auto-generated API reference (for stories tagged `autodocs`).
|
|
117
|
+
- **Accessibility** — axe-core audit panel to check WCAG compliance.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
Full documentation, migration guides, and API references are available at the
|
|
124
|
+
[ObjectUI docs site](https://objectui.dev).
|
|
125
|
+
|
|
126
|
+
For source code and contribution guidelines, visit the
|
|
127
|
+
[GitHub repository](https://github.com/objectstack-ai/objectui).
|
package/src/ui/slider.tsx
CHANGED
|
@@ -16,7 +16,7 @@ import { cn } from "../lib/utils"
|
|
|
16
16
|
const Slider = React.forwardRef<
|
|
17
17
|
React.ElementRef<typeof SliderPrimitive.Root>,
|
|
18
18
|
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
|
19
|
-
>(({ className, ...props }, ref) => (
|
|
19
|
+
>(({ className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }, ref) => (
|
|
20
20
|
<SliderPrimitive.Root
|
|
21
21
|
ref={ref}
|
|
22
22
|
className={cn(
|
|
@@ -28,7 +28,11 @@ const Slider = React.forwardRef<
|
|
|
28
28
|
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
|
29
29
|
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
|
30
30
|
</SliderPrimitive.Track>
|
|
31
|
-
<SliderPrimitive.Thumb
|
|
31
|
+
<SliderPrimitive.Thumb
|
|
32
|
+
aria-label={ariaLabel}
|
|
33
|
+
aria-labelledby={ariaLabelledBy}
|
|
34
|
+
className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
|
35
|
+
/>
|
|
32
36
|
</SliderPrimitive.Root>
|
|
33
37
|
))
|
|
34
38
|
Slider.displayName = SliderPrimitive.Root.displayName
|