@process.co/ui 0.0.9 → 0.0.11

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/README.md CHANGED
@@ -1,20 +1,6 @@
1
- # UI Component Library
1
+ # @process.co/ui
2
2
 
3
- This package contains a collection of reusable UI components for the proc-app project. All components are built with React, TypeScript, and Tailwind CSS.
4
-
5
- ## Table of Contents
6
-
7
- - [Installation](#installation)
8
- - [Usage](#usage)
9
- - [Components](#components)
10
- - [Basic Components](#basic-components)
11
- - [Form Components](#form-components)
12
- - [Layout Components](#layout-components)
13
- - [Feedback Components](#feedback-components)
14
- - [Navigation Components](#navigation-components)
15
- - [Blocks](#blocks)
16
- - [Providers](#providers)
17
- - [Storybook](#storybook)
3
+ A React UI component library for Process.co applications with built-in collaborative editing, type inference, and expression support.
18
4
 
19
5
  ## Installation
20
6
 
@@ -22,393 +8,490 @@ This package contains a collection of reusable UI components for the proc-app pr
22
8
  npm install @process.co/ui
23
9
  ```
24
10
 
25
- Or with pnpm:
26
-
27
- ```bash
28
- pnpm add @process.co/ui
29
- ```
30
-
31
- ## Usage
32
-
33
- ### Importing Components
34
-
35
- ```tsx
36
- import { Button, Card, Alert } from '@process.co/ui';
37
- import { UIProvider } from '@process.co/ui';
38
- ```
39
-
40
11
  ### Importing Styles
41
12
 
42
- The library includes pre-built CSS that must be imported in your application:
43
-
44
13
  ```tsx
45
- // In your main app file or layout
46
14
  import '@process.co/ui/styles';
47
15
  ```
48
16
 
49
- Or in your CSS:
17
+ ---
50
18
 
51
- ```css
52
- @import '@process.co/ui/styles';
53
- ```
19
+ ## Field Components
54
20
 
55
- ### Basic Example
21
+ The `Input` and `Select` components are collaborative-aware form controls designed for building custom UIs that integrate with Process.co's flow editor.
22
+
23
+ ### Quick Start
56
24
 
57
25
  ```tsx
58
- import { Button, Card, Alert } from '@process.co/ui';
59
- import { UIProvider } from '@process.co/ui';
26
+ import { Input, Select, useNodeProperty } from '@process.co/ui/fields';
60
27
  import '@process.co/ui/styles';
61
28
 
62
- // Wrap your app with UIProvider
63
- function App() {
29
+ function MyCustomControl({ fieldName }) {
30
+ const [value, setValue] = useNodeProperty(fieldName);
31
+
64
32
  return (
65
- <UIProvider>
66
- <Button>Click me</Button>
67
- </UIProvider>
33
+ <Input
34
+ fieldName="expression"
35
+ label="Expression"
36
+ expectedType="string"
37
+ value={value}
38
+ onChange={setValue}
39
+ />
68
40
  );
69
41
  }
70
42
  ```
71
43
 
72
- ## Components
73
-
74
- ### Basic Components
44
+ ---
75
45
 
76
- #### Button
77
- A customizable button component with multiple variants and sizes.
46
+ ## Input Component
78
47
 
79
- ```tsx
80
- import { Button } from '@repo/ui';
48
+ A text input with expression support, type inference, and real-time collaboration.
81
49
 
82
- <Button variant="primary" size="md" onClick={handleClick}>
83
- Click me
84
- </Button>
85
- ```
50
+ ### Props
86
51
 
87
- **Props:**
88
- - `variant`: 'primary' | 'secondary' | 'outline' | 'ghost' | 'link' | 'destructive'
89
- - `size`: 'sm' | 'md' | 'lg'
90
- - `disabled`: boolean
91
- - `loading`: boolean
92
- - Standard button HTML attributes
52
+ | Prop | Type | Description |
53
+ |------|------|-------------|
54
+ | `fieldName` | `string` | Unique identifier for collaborative sync |
55
+ | `label` | `string` | Display label |
56
+ | `value` | `any` | Current value |
57
+ | `onChange` | `(value: any) => void` | Change handler |
58
+ | `expectedType` | `string` | Expected type for validation (see Type Inference) |
59
+ | `placeholder` | `string` | Placeholder text |
93
60
 
94
- #### Card
95
- A clickable card component that links to external resources.
61
+ ### Example
96
62
 
97
63
  ```tsx
98
- import { Card } from '@repo/ui';
99
-
100
- <Card
101
- title="Documentation"
102
- href="https://docs.example.com"
103
- className="custom-class"
104
- >
105
- Learn more about our components
106
- </Card>
64
+ <Input
65
+ fieldName="userEmail"
66
+ label="Email Address"
67
+ expectedType="string"
68
+ value={email}
69
+ onChange={setEmail}
70
+ placeholder="user@example.com"
71
+ />
107
72
  ```
108
73
 
109
- **Props:**
110
- - `title`: string (required)
111
- - `href`: string (required)
112
- - `className`: string (optional)
113
- - `children`: React.ReactNode
74
+ ---
114
75
 
115
- #### Code
116
- A simple inline code component for displaying code snippets.
76
+ ## Select Component
117
77
 
118
- ```tsx
119
- import { Code } from '@repo/ui';
78
+ A dropdown select with expression support and type-aware options.
120
79
 
121
- <Code className="custom-class">const example = "Hello";</Code>
122
- ```
80
+ ### Props
123
81
 
124
- **Props:**
125
- - `className`: string (optional)
126
- - `children`: React.ReactNode
82
+ | Prop | Type | Description |
83
+ |------|------|-------------|
84
+ | `fieldName` | `string` | Unique identifier for collaborative sync |
85
+ | `label` | `string` | Display label |
86
+ | `value` | `any` | Current selected value |
87
+ | `onChange` | `(value: any) => void` | Change handler |
88
+ | `options` | `SelectOption[]` | Available options |
89
+ | `expectedType` | `string` | Expected type for validation |
127
90
 
128
- #### Header
129
- A header component with navigation and branding.
91
+ ### SelectOption Type
130
92
 
131
93
  ```tsx
132
- import { Header } from '@repo/ui';
94
+ type SelectOption = {
95
+ value: string;
96
+ label: string;
97
+ node?: ReactNode; // Custom render
98
+ };
99
+ ```
100
+
101
+ ### Example
133
102
 
134
- <Header
135
- logoSrc="/logo.svg"
136
- logoAlt="Company Logo"
137
- navigation={navigationItems}
103
+ ```tsx
104
+ <Select
105
+ fieldName="operator"
106
+ label="Operation"
107
+ value={operator}
108
+ onChange={setOperator}
109
+ options={[
110
+ { value: 'equals', label: 'Equals' },
111
+ { value: 'contains', label: 'Contains' },
112
+ { value: 'startsWith', label: 'Starts With' },
113
+ ]}
138
114
  />
139
115
  ```
140
116
 
141
- ### Form Components
117
+ ---
118
+
119
+ ## Type Inference System
120
+
121
+ The `$infer<...>` syntax enables automatic type inference and propagation between fields.
122
+
123
+ ### How It Works
124
+
125
+ When a field uses `$infer` or `$infer<allowedTypes>` as its `expectedType`:
126
+
127
+ 1. The field **infers** the type of the user's input
128
+ 2. The field **publishes** that inferred type under its `fieldName`
129
+ 3. Other fields can **subscribe** to that type using `$infer<[fieldName]>`
130
+
131
+ ### Publishing Types (Automatic)
142
132
 
143
- #### TextInput
144
- A text input component with built-in validation and error handling.
133
+ Any field with `$infer` syntax automatically publishes its inferred type:
145
134
 
146
135
  ```tsx
147
- import { TextInput } from '@repo/ui';
148
-
149
- <TextInput
150
- label="Email"
151
- name="email"
152
- type="email"
153
- placeholder="Enter your email"
154
- error={errors.email}
155
- onChange={handleChange}
136
+ // This field publishes its inferred type as "switchExpression"
137
+ <Input
138
+ fieldName="switchExpression"
139
+ expectedType="$infer<string | number | boolean>"
156
140
  />
157
141
  ```
158
142
 
159
- **Props:**
160
- - `label`: string
161
- - `name`: string
162
- - `type`: string
163
- - `placeholder`: string
164
- - `error`: string
165
- - `required`: boolean
166
- - Standard input HTML attributes
143
+ The field will:
144
+ 1. Accept string, number, or boolean values
145
+ 2. Infer the actual type from user input (e.g., if user types `"hello"`, infers `string`)
146
+ 3. **Publish** the inferred type so other fields can access it via `fieldName`
167
147
 
168
- #### PasswordMeter
169
- A password input with strength meter visualization.
148
+ You can also use just `$infer` without constraints:
170
149
 
171
150
  ```tsx
172
- import { PasswordMeter } from '@repo/ui';
173
-
174
- <PasswordMeter
175
- password={password}
176
- onChange={setPassword}
177
- requirements={{
178
- minLength: 8,
179
- requireUppercase: true,
180
- requireNumbers: true,
181
- requireSpecialChars: true
182
- }}
151
+ // Publishes inferred type with no restrictions on allowed types
152
+ <Input
153
+ fieldName="myExpression"
154
+ expectedType="$infer"
183
155
  />
184
156
  ```
185
157
 
186
- **Props:**
187
- - `password`: string
188
- - `onChange`: (value: string) => void
189
- - `requirements`: PasswordRequirements object
190
-
191
- ### Layout Components
192
-
193
- #### NotFound (404)
194
- A pre-styled 404 error page layout.
158
+ Or with a single known type:
195
159
 
196
160
  ```tsx
197
- import { NotFound } from '@repo/ui';
198
-
199
- <NotFound />
161
+ // Publishes "string" as the inferred type for this field
162
+ <Input
163
+ fieldName="stringField"
164
+ expectedType="$infer<string>"
165
+ />
200
166
  ```
201
167
 
202
- The component uses translations from UIProvider for customization.
168
+ ### Subscribing to Types
203
169
 
204
- #### Sidebar
205
- A collapsible sidebar navigation component.
170
+ A field can **subscribe** to another field's published type:
206
171
 
207
172
  ```tsx
208
- import { Sidebar } from '@repo/ui';
209
-
210
- <Sidebar
211
- items={navigationItems}
212
- collapsed={isCollapsed}
213
- onToggle={handleToggle}
173
+ // This field receives its expected type from "switchExpression"
174
+ <Input
175
+ fieldName="caseValue"
176
+ expectedType="$infer<[switchExpression]>"
214
177
  />
215
178
  ```
216
179
 
217
- ### Feedback Components
180
+ This field will:
181
+ 1. Look up the inferred type published by `switchExpression`
182
+ 2. Use that type for validation and autocomplete
183
+ 3. Update automatically when the source field's type changes
218
184
 
219
- #### Alert
220
- A dismissible alert component for displaying messages.
185
+ ### Multi-Field Subscription
221
186
 
222
- ```tsx
223
- import { Alert } from '@repo/ui';
187
+ Subscribe to multiple fields - types are intersected:
224
188
 
225
- <Alert
226
- type="success"
227
- message="Operation completed successfully"
228
- onClose={handleClose}
189
+ ```tsx
190
+ <Input
191
+ fieldName="value"
192
+ expectedType='$infer<["statementField", "operatorField"]>'
229
193
  />
230
194
  ```
231
195
 
232
- **Props:**
233
- - `type`: 'success' | 'error' | 'warning' | 'info'
234
- - `message`: string
235
- - `onClose`: () => void (optional)
196
+ The expected type is computed by intersecting types from both fields.
236
197
 
237
- #### Loader
238
- An animated loading indicator.
198
+ ### Example Flow
239
199
 
240
200
  ```tsx
241
- import { Loader } from '@repo/ui';
201
+ // 1. Statement field publishes its inferred type
202
+ <Input
203
+ fieldName="statement"
204
+ expectedType="$infer<string | number | boolean>"
205
+ // User enters: this.user.age
206
+ // Infers and publishes: "number"
207
+ />
242
208
 
243
- <Loader
244
- id="unique-loader-id"
245
- getAnimationData={() => animationData}
246
- speed={1}
209
+ // 2. Operator dropdown reads the published type to filter options
210
+ const ctx = useInferredTypes();
211
+ const statementType = ctx?.getInferredType('statement'); // "number"
212
+ const operators = statementType === 'number'
213
+ ? ['=', '!=', '<', '>', '<=', '>=']
214
+ : ['=', '!='];
215
+
216
+ // 3. Value field subscribes to the statement's type
217
+ <Input
218
+ fieldName="value"
219
+ expectedType="$infer<[statement]>"
220
+ // Expects: "number" (from statement field)
247
221
  />
248
222
  ```
249
223
 
250
- #### SuspenseLoader
251
- A full-screen loading component with animation, wrapped in React Query provider.
224
+ ---
252
225
 
253
- ```tsx
254
- import { SuspenseLoader } from '@repo/ui';
226
+ ## useInferredTypes Hook
255
227
 
256
- <SuspenseLoader />
228
+ Access inferred types programmatically for building type-aware UIs:
229
+
230
+ ```tsx
231
+ import { useInferredTypes } from '@process.co/ui/fields';
232
+
233
+ function OperatorSelect() {
234
+ const ctx = useInferredTypes();
235
+
236
+ // Read the published type from another field
237
+ const expressionType = ctx?.getInferredType('switchExpression') || 'any';
238
+
239
+ // Manually publish a type (e.g., for operator narrowing)
240
+ ctx?.setInferredType('operatorNarrow', 'string');
241
+
242
+ // Filter operators based on type
243
+ const operators = expressionType === 'number'
244
+ ? ['=', '!=', '<', '>', '<=', '>=']
245
+ : expressionType === 'string'
246
+ ? ['=', '!=', 'contains', 'startsWith', 'endsWith']
247
+ : ['=', '!='];
248
+
249
+ return (
250
+ <Select options={operators.map(op => ({ value: op, label: op }))} />
251
+ );
252
+ }
257
253
  ```
258
254
 
259
- #### FullScreenLoader
260
- A centered full-screen loading overlay.
255
+ ### Context Methods
261
256
 
262
- ```tsx
263
- import { FullScreenLoader } from '@repo/ui';
257
+ | Method | Description |
258
+ |--------|-------------|
259
+ | `getInferredType(fieldName)` | Get the published type for a field |
260
+ | `setInferredType(fieldName, type)` | Manually publish a type for a field |
261
+ | `clearInferredType(fieldName)` | Remove a published type for a field |
262
+ | `clearAllInferredTypes()` | Remove all published types |
263
+ | `inferredTypes` | Record of all fieldName → type mappings |
264
264
 
265
- <FullScreenLoader
266
- message="Loading..."
267
- showSpinner={true}
268
- />
269
- ```
265
+ ### Cleanup on Unmount
270
266
 
271
- #### Tooltip
272
- A simple tooltip component for hover information.
267
+ When building controls that publish inferred types, clean up on unmount to avoid stale types:
273
268
 
274
269
  ```tsx
275
- import { Tooltip } from '@repo/ui';
276
-
277
- <Tooltip content="Additional information">
278
- <Button>Hover me</Button>
279
- </Tooltip>
270
+ useEffect(() => {
271
+ // Set the inferred type
272
+ ctx?.setInferredType(myFieldName, computedType);
273
+
274
+ // Cleanup on unmount
275
+ return () => {
276
+ ctx?.clearInferredType?.(myFieldName);
277
+ };
278
+ }, [myFieldName, computedType]);
280
279
  ```
281
280
 
282
- ### Navigation Components
281
+ ---
283
282
 
284
- #### NavigationPortal
285
- A portal component for setting navigation breadcrumbs and content.
283
+ ## useNodeProperty Hook
286
284
 
287
- ```tsx
288
- import NavigationPortal from '@repo/ui';
285
+ Subscribe to and update node properties with automatic collaboration sync:
289
286
 
290
- <NavigationPortal
291
- breadcrumbs={[
292
- { label: 'Home', href: '/' },
293
- { label: 'Products', href: '/products' },
294
- { label: 'Current Page' }
295
- ]}
296
- clearOnUnmount={true}
297
- >
298
- <CustomNavigation />
299
- </NavigationPortal>
287
+ ```tsx
288
+ import { useNodeProperty } from '@process.co/ui/fields';
289
+
290
+ function MyControl({ fieldName }) {
291
+ const [value, setValue] = useNodeProperty<MyValueType>(fieldName);
292
+
293
+ // value: Current property value (undefined if not set)
294
+ // setValue: Update function with automatic sync
295
+
296
+ return (
297
+ <button onClick={() => setValue({ ...value, enabled: true })}>
298
+ Enable
299
+ </button>
300
+ );
301
+ }
300
302
  ```
301
303
 
302
- **Props:**
303
- - `breadcrumbs`: Breadcrumb[] (optional)
304
- - `clearOnUnmount`: boolean (default: true)
305
- - `keys`: unknown[] (for memoization)
306
- - `children`: React.ReactNode (navigation content)
304
+ ---
307
305
 
308
- ### Blocks
306
+ ## Building Custom Controls
309
307
 
310
- #### LinkTree
311
- A component displaying a tree of navigation links.
308
+ Custom controls integrate with Process.co's collaborative editing system through the `useNodeProperty` hook.
309
+
310
+ ### Basic Pattern
312
311
 
313
312
  ```tsx
314
- import { LinkTree } from '@repo/ui';
313
+ import { useNodeProperty, useInferredTypes } from '@process.co/ui/fields';
314
+
315
+ interface MyControlProps {
316
+ fieldName: string;
317
+ readonly?: boolean;
318
+ }
315
319
 
316
- <LinkTree links={navigationLinks} />
320
+ export default function MyControl({ fieldName, readonly = false }: MyControlProps) {
321
+ // Subscribe to the property value
322
+ const [value, setValue] = useNodeProperty<MyValueType>(fieldName);
323
+
324
+ // Access type inference context (optional)
325
+ const ctx = useInferredTypes();
326
+
327
+ // Derive state from value
328
+ const items = value?.items ?? [];
329
+
330
+ // Update handler
331
+ const addItem = () => {
332
+ setValue({
333
+ ...value,
334
+ items: [...items, { id: generateId(), name: '' }]
335
+ });
336
+ };
337
+
338
+ return (
339
+ <div>
340
+ {items.map(item => (
341
+ <ItemRow key={item.id} item={item} />
342
+ ))}
343
+ {!readonly && <button onClick={addItem}>Add Item</button>}
344
+ </div>
345
+ );
346
+ }
317
347
  ```
318
348
 
319
- #### Copyright
320
- A copyright notice component with customizable text.
349
+ ### With Type Inference
321
350
 
322
351
  ```tsx
323
- import { Copyright } from '@repo/ui';
324
-
325
- <Copyright year={2024} company="Process Co" />
352
+ import {
353
+ useNodeProperty,
354
+ useInferredTypes,
355
+ Input,
356
+ Select,
357
+ } from '@process.co/ui/fields';
358
+
359
+ export default function ConditionalEditor({ fieldName }) {
360
+ const [value, setValue] = useNodeProperty(fieldName);
361
+ const ctx = useInferredTypes();
362
+
363
+ // Get inferred type from statement field (it publishes automatically)
364
+ const statementType = ctx?.getInferredType(`${fieldName}_statement`) || 'any';
365
+
366
+ // Filter operators based on statement type
367
+ const operators = getOperatorsForType(statementType);
368
+
369
+ return (
370
+ <div>
371
+ {/* This field PUBLISHES its inferred type as "${fieldName}_statement" */}
372
+ <Input
373
+ fieldName={`${fieldName}_statement`}
374
+ label="Statement"
375
+ expectedType="$infer<string | number | boolean>"
376
+ value={value?.statement}
377
+ onChange={(v) => setValue({ ...value, statement: v })}
378
+ />
379
+
380
+ <Select
381
+ fieldName={`${fieldName}_operator`}
382
+ label="Operator"
383
+ options={operators}
384
+ value={value?.operator}
385
+ onChange={(v) => setValue({ ...value, operator: v })}
386
+ />
387
+
388
+ {/* This field SUBSCRIBES to the statement field's type */}
389
+ <Input
390
+ fieldName={`${fieldName}_value`}
391
+ label="Value"
392
+ expectedType={`$infer<[${fieldName}_statement]>`}
393
+ value={value?.value}
394
+ onChange={(v) => setValue({ ...value, value: v })}
395
+ />
396
+ </div>
397
+ );
398
+ }
326
399
  ```
327
400
 
328
- ### Authentication Components
401
+ ---
329
402
 
330
- #### SocialLogin
331
- A component for social authentication providers.
332
-
333
- ```tsx
334
- import { SocialLogin } from '@repo/ui';
335
-
336
- <SocialLogin
337
- providers={['google', 'github', 'microsoft']}
338
- onLogin={handleSocialLogin}
339
- />
340
- ```
403
+ ## Operator Utilities
341
404
 
342
- ### Providers
405
+ Shared utilities for building query builders with type-aware operators.
343
406
 
344
- #### UIProvider
345
- The main provider component that wraps your application and provides context for translations, settings, and state management.
407
+ ### Types
346
408
 
347
409
  ```tsx
348
- import { UIProvider } from '@repo/ui';
349
-
350
- <UIProvider
351
- locale="en"
352
- translations={translations}
353
- theme="light"
354
- >
355
- <App />
356
- </UIProvider>
410
+ import {
411
+ BaseOperatorType,
412
+ OperatorDef,
413
+ ParsedTypes,
414
+ } from '@process.co/ui/fields';
415
+
416
+ // BaseOperatorType includes: 'exists', 'not_exists', 'string_equals',
417
+ // 'string_contains', 'number_gt', 'boolean_equals', etc.
418
+
419
+ // OperatorDef<T> is generic - extend with custom operators
420
+ type MyOperator = 'expression' | 'custom_check';
421
+ const operators: OperatorDef<MyOperator>[] = [...];
357
422
  ```
358
423
 
359
- **Exports:**
360
- - `useSettings`: Hook to access UI settings
361
- - `useTranslation`: Hook for translations
362
- - `KeyValueStore`: Type definition for storage
363
- - `StorageType`: Enum for storage types
364
-
365
- ## Utility Functions
366
-
367
- The package also exports utility functions from `lib/utils`:
424
+ ### Functions
368
425
 
369
426
  ```tsx
370
- import { cn, formatDate } from '@repo/ui';
427
+ import {
428
+ parseInferredTypes,
429
+ computeExtendedType,
430
+ filterOperatorsByType,
431
+ getStringConstants,
432
+ getNumberConstants,
433
+ } from '@process.co/ui/fields';
434
+
435
+ // Parse type string into components
436
+ const parsed = parseInferredTypes('"adam" | "beth" | number');
437
+ // { baseTypes: ['string', 'number'], stringConstants: ['adam', 'beth'], ... }
438
+
439
+ // Filter operators by compatible type
440
+ const ops = filterOperatorsByType(OPERATORS, '"adam" | "beth"');
441
+ // Returns operators with types: ['any'] or ['string']
442
+
443
+ // Compute extended type for operators with extendsWithBase
444
+ const expectedType = computeExtendedType('"adam" | "beth"', operatorDef);
445
+ // If extendsWithBase: true, returns '"adam" | "beth" | string'
371
446
  ```
372
447
 
373
- ## Storybook
448
+ ### Extended Type Narrowing
374
449
 
375
- To view all components in Storybook:
450
+ Some operators support `extendsWithBase` for flexible type matching:
376
451
 
377
- ```bash
378
- pnpm storybook
452
+ ```tsx
453
+ const OPERATORS: OperatorDef[] = [
454
+ // Exact match - only accepts the literal values
455
+ { value: 'string_equals', narrowsTo: 'string', extendsWithBase: false },
456
+
457
+ // Extended match - accepts literals OR any string
458
+ { value: 'string_starts_with', narrowsTo: 'string', extendsWithBase: true },
459
+ ];
379
460
  ```
380
461
 
381
- ## Development
462
+ **Example:** If statement infers `"adam" | "beth"`:
463
+ - `string_equals` expects: `"adam" | "beth"` (must match exactly)
464
+ - `string_starts_with` expects: `"adam" | "beth" | string` (can provide partial match like `"a"`)
382
465
 
383
- ### Adding New Components
466
+ ---
384
467
 
385
- 1. Create the component in `src/components/`
386
- 2. Export it from `src/index.tsx`
387
- 3. Create a Storybook story in `src/stories/components/`
388
- 4. Update this documentation
468
+ ## Collaboration Features
389
469
 
390
- ### Component Guidelines
470
+ All field components support real-time collaboration when used within the Process.co flow editor:
391
471
 
392
- - Use TypeScript for all components
393
- - Follow the existing naming conventions
394
- - Include proper TypeScript types
395
- - Add JSDoc comments for props
396
- - Create comprehensive Storybook stories
397
- - Ensure accessibility compliance
398
- - Use Tailwind CSS with the `ui:` prefix for styling
472
+ - **Cursor sharing**: See where other users are editing
473
+ - **Conflict resolution**: Automatic handling of concurrent edits via Yjs
474
+ - **Presence indicators**: Visual indication of active editors
399
475
 
400
- ### Testing
476
+ These features work automatically when components are rendered within a `NodePropertyProvider` context.
401
477
 
402
- Run tests with:
478
+ ---
403
479
 
404
- ```bash
405
- pnpm test
406
- ```
480
+ ## UI Components
407
481
 
408
- ### Building
482
+ The library also includes standard UI components:
409
483
 
410
- Build the package with:
484
+ ```tsx
485
+ import {
486
+ Button,
487
+ Card,
488
+ Alert,
489
+ DropdownMenu,
490
+ DropdownMenuTrigger,
491
+ DropdownMenuContent,
492
+ DropdownMenuItem,
493
+ ConfirmationDropdownMenuItem,
494
+ } from '@process.co/ui';
495
+ ```
411
496
 
412
- ```bash
413
- pnpm build
414
- ```
497
+ See the [Storybook documentation](https://main--674d30e40a127b13419e46ba.chromatic.com) for full component reference.