@process.co/ui 0.0.9 → 0.0.10

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,472 @@ 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 />
257
- ```
258
-
259
- #### FullScreenLoader
260
- A centered full-screen loading overlay.
228
+ Access inferred types programmatically for building type-aware UIs:
261
229
 
262
230
  ```tsx
263
- import { FullScreenLoader } from '@repo/ui';
264
-
265
- <FullScreenLoader
266
- message="Loading..."
267
- showSpinner={true}
268
- />
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
+ }
269
253
  ```
270
254
 
271
- #### Tooltip
272
- A simple tooltip component for hover information.
255
+ ### Context Methods
273
256
 
274
- ```tsx
275
- import { Tooltip } 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
+ | `inferredTypes` | Record of all fieldName → type mappings |
276
262
 
277
- <Tooltip content="Additional information">
278
- <Button>Hover me</Button>
279
- </Tooltip>
280
- ```
263
+ ---
281
264
 
282
- ### Navigation Components
265
+ ## useNodeProperty Hook
283
266
 
284
- #### NavigationPortal
285
- A portal component for setting navigation breadcrumbs and content.
267
+ Subscribe to and update node properties with automatic collaboration sync:
286
268
 
287
269
  ```tsx
288
- import NavigationPortal from '@repo/ui';
289
-
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>
270
+ import { useNodeProperty } from '@process.co/ui/fields';
271
+
272
+ function MyControl({ fieldName }) {
273
+ const [value, setValue] = useNodeProperty<MyValueType>(fieldName);
274
+
275
+ // value: Current property value (undefined if not set)
276
+ // setValue: Update function with automatic sync
277
+
278
+ return (
279
+ <button onClick={() => setValue({ ...value, enabled: true })}>
280
+ Enable
281
+ </button>
282
+ );
283
+ }
300
284
  ```
301
285
 
302
- **Props:**
303
- - `breadcrumbs`: Breadcrumb[] (optional)
304
- - `clearOnUnmount`: boolean (default: true)
305
- - `keys`: unknown[] (for memoization)
306
- - `children`: React.ReactNode (navigation content)
286
+ ---
287
+
288
+ ## Building Custom Controls
307
289
 
308
- ### Blocks
290
+ Custom controls integrate with Process.co's collaborative editing system through the `useNodeProperty` hook.
309
291
 
310
- #### LinkTree
311
- A component displaying a tree of navigation links.
292
+ ### Basic Pattern
312
293
 
313
294
  ```tsx
314
- import { LinkTree } from '@repo/ui';
295
+ import { useNodeProperty, useInferredTypes } from '@process.co/ui/fields';
315
296
 
316
- <LinkTree links={navigationLinks} />
297
+ interface MyControlProps {
298
+ fieldName: string;
299
+ readonly?: boolean;
300
+ }
301
+
302
+ export default function MyControl({ fieldName, readonly = false }: MyControlProps) {
303
+ // Subscribe to the property value
304
+ const [value, setValue] = useNodeProperty<MyValueType>(fieldName);
305
+
306
+ // Access type inference context (optional)
307
+ const ctx = useInferredTypes();
308
+
309
+ // Derive state from value
310
+ const items = value?.items ?? [];
311
+
312
+ // Update handler
313
+ const addItem = () => {
314
+ setValue({
315
+ ...value,
316
+ items: [...items, { id: generateId(), name: '' }]
317
+ });
318
+ };
319
+
320
+ return (
321
+ <div>
322
+ {items.map(item => (
323
+ <ItemRow key={item.id} item={item} />
324
+ ))}
325
+ {!readonly && <button onClick={addItem}>Add Item</button>}
326
+ </div>
327
+ );
328
+ }
317
329
  ```
318
330
 
319
- #### Copyright
320
- A copyright notice component with customizable text.
331
+ ### With Type Inference
321
332
 
322
333
  ```tsx
323
- import { Copyright } from '@repo/ui';
324
-
325
- <Copyright year={2024} company="Process Co" />
334
+ import {
335
+ useNodeProperty,
336
+ useInferredTypes,
337
+ Input,
338
+ Select,
339
+ } from '@process.co/ui/fields';
340
+
341
+ export default function ConditionalEditor({ fieldName }) {
342
+ const [value, setValue] = useNodeProperty(fieldName);
343
+ const ctx = useInferredTypes();
344
+
345
+ // Get inferred type from statement field (it publishes automatically)
346
+ const statementType = ctx?.getInferredType(`${fieldName}_statement`) || 'any';
347
+
348
+ // Filter operators based on statement type
349
+ const operators = getOperatorsForType(statementType);
350
+
351
+ return (
352
+ <div>
353
+ {/* This field PUBLISHES its inferred type as "${fieldName}_statement" */}
354
+ <Input
355
+ fieldName={`${fieldName}_statement`}
356
+ label="Statement"
357
+ expectedType="$infer<string | number | boolean>"
358
+ value={value?.statement}
359
+ onChange={(v) => setValue({ ...value, statement: v })}
360
+ />
361
+
362
+ <Select
363
+ fieldName={`${fieldName}_operator`}
364
+ label="Operator"
365
+ options={operators}
366
+ value={value?.operator}
367
+ onChange={(v) => setValue({ ...value, operator: v })}
368
+ />
369
+
370
+ {/* This field SUBSCRIBES to the statement field's type */}
371
+ <Input
372
+ fieldName={`${fieldName}_value`}
373
+ label="Value"
374
+ expectedType={`$infer<[${fieldName}_statement]>`}
375
+ value={value?.value}
376
+ onChange={(v) => setValue({ ...value, value: v })}
377
+ />
378
+ </div>
379
+ );
380
+ }
326
381
  ```
327
382
 
328
- ### Authentication Components
383
+ ---
329
384
 
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
- ```
385
+ ## Operator Utilities
341
386
 
342
- ### Providers
387
+ Shared utilities for building query builders with type-aware operators.
343
388
 
344
- #### UIProvider
345
- The main provider component that wraps your application and provides context for translations, settings, and state management.
389
+ ### Types
346
390
 
347
391
  ```tsx
348
- import { UIProvider } from '@repo/ui';
349
-
350
- <UIProvider
351
- locale="en"
352
- translations={translations}
353
- theme="light"
354
- >
355
- <App />
356
- </UIProvider>
392
+ import {
393
+ BaseOperatorType,
394
+ OperatorDef,
395
+ ParsedTypes,
396
+ } from '@process.co/ui/fields';
397
+
398
+ // BaseOperatorType includes: 'exists', 'not_exists', 'string_equals',
399
+ // 'string_contains', 'number_gt', 'boolean_equals', etc.
400
+
401
+ // OperatorDef<T> is generic - extend with custom operators
402
+ type MyOperator = 'expression' | 'custom_check';
403
+ const operators: OperatorDef<MyOperator>[] = [...];
357
404
  ```
358
405
 
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`:
406
+ ### Functions
368
407
 
369
408
  ```tsx
370
- import { cn, formatDate } from '@repo/ui';
409
+ import {
410
+ parseInferredTypes,
411
+ computeExtendedType,
412
+ filterOperatorsByType,
413
+ getStringConstants,
414
+ getNumberConstants,
415
+ } from '@process.co/ui/fields';
416
+
417
+ // Parse type string into components
418
+ const parsed = parseInferredTypes('"adam" | "beth" | number');
419
+ // { baseTypes: ['string', 'number'], stringConstants: ['adam', 'beth'], ... }
420
+
421
+ // Filter operators by compatible type
422
+ const ops = filterOperatorsByType(OPERATORS, '"adam" | "beth"');
423
+ // Returns operators with types: ['any'] or ['string']
424
+
425
+ // Compute extended type for operators with extendsWithBase
426
+ const expectedType = computeExtendedType('"adam" | "beth"', operatorDef);
427
+ // If extendsWithBase: true, returns '"adam" | "beth" | string'
371
428
  ```
372
429
 
373
- ## Storybook
430
+ ### Extended Type Narrowing
374
431
 
375
- To view all components in Storybook:
432
+ Some operators support `extendsWithBase` for flexible type matching:
376
433
 
377
- ```bash
378
- pnpm storybook
434
+ ```tsx
435
+ const OPERATORS: OperatorDef[] = [
436
+ // Exact match - only accepts the literal values
437
+ { value: 'string_equals', narrowsTo: 'string', extendsWithBase: false },
438
+
439
+ // Extended match - accepts literals OR any string
440
+ { value: 'string_starts_with', narrowsTo: 'string', extendsWithBase: true },
441
+ ];
379
442
  ```
380
443
 
381
- ## Development
444
+ **Example:** If statement infers `"adam" | "beth"`:
445
+ - `string_equals` expects: `"adam" | "beth"` (must match exactly)
446
+ - `string_starts_with` expects: `"adam" | "beth" | string` (can provide partial match like `"a"`)
382
447
 
383
- ### Adding New Components
448
+ ---
384
449
 
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
450
+ ## Collaboration Features
389
451
 
390
- ### Component Guidelines
452
+ All field components support real-time collaboration when used within the Process.co flow editor:
391
453
 
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
454
+ - **Cursor sharing**: See where other users are editing
455
+ - **Conflict resolution**: Automatic handling of concurrent edits via Yjs
456
+ - **Presence indicators**: Visual indication of active editors
399
457
 
400
- ### Testing
458
+ These features work automatically when components are rendered within a `NodePropertyProvider` context.
401
459
 
402
- Run tests with:
460
+ ---
403
461
 
404
- ```bash
405
- pnpm test
406
- ```
462
+ ## UI Components
407
463
 
408
- ### Building
464
+ The library also includes standard UI components:
409
465
 
410
- Build the package with:
466
+ ```tsx
467
+ import {
468
+ Button,
469
+ Card,
470
+ Alert,
471
+ DropdownMenu,
472
+ DropdownMenuTrigger,
473
+ DropdownMenuContent,
474
+ DropdownMenuItem,
475
+ ConfirmationDropdownMenuItem,
476
+ } from '@process.co/ui';
477
+ ```
411
478
 
412
- ```bash
413
- pnpm build
414
- ```
479
+ See the [Storybook documentation](https://main--674d30e40a127b13419e46ba.chromatic.com) for full component reference.