@julien-wiegandt/open-ui 0.3.0 → 0.3.2

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 ADDED
@@ -0,0 +1,514 @@
1
+ # open-ui
2
+
3
+ Simple lightweight UI components for React.
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ npm install @julien-wiegandt/open-ui
9
+ ```
10
+
11
+ Wrap your app with the `OpenUIProvider` and pass at least one theme:
12
+
13
+ ```tsx
14
+ import { OpenUIProvider, createTheme } from "@julien-wiegandt/open-ui";
15
+
16
+ const theme = createTheme({
17
+ radius: "md",
18
+ primary: "#6090fa",
19
+ });
20
+
21
+ function App() {
22
+ return (
23
+ <OpenUIProvider themes={[theme]}>
24
+ <MyApp />
25
+ </OpenUIProvider>
26
+ );
27
+ }
28
+ ```
29
+
30
+ ## Components
31
+
32
+ ### Button
33
+
34
+ ```tsx
35
+ <Button variant="contained" color="primary" label="Click me" />
36
+
37
+ <Button variant="outlined" color="secondary" label="Outlined" />
38
+
39
+ <Button
40
+ variant="contained"
41
+ color="primary"
42
+ label="Submit"
43
+ starticon="sparkles"
44
+ size="lg"
45
+ onClick={async () => { await submitForm(); }}
46
+ />
47
+ ```
48
+
49
+ | Prop | Type | Default | Description |
50
+ | -------------- | ------------------------------------------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------- |
51
+ | `label` | `string` | — | Button text |
52
+ | `variant` | `"contained" \| "outlined" \| "text"` | theme default | Visual style |
53
+ | `color` | `"default" \| "primary" \| "secondary" \| "error" \| string` | theme default | Color token or hex |
54
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Button size |
55
+ | `starticon` | `ReactNode \| Icon` | — | Icon before label (`"bell"`, `"check"`, `"heart"`, `"sync"`, `"sparkles"`, `"dots"`, `"send"`, `"copy"`, `"hamburger"`) |
56
+ | `endicon` | `ReactNode \| Icon` | — | Icon after label |
57
+ | `loading` | `boolean` | — | Show loading state |
58
+ | `endanimation` | `boolean` | — | Show check icon on completion |
59
+ | `radius` | `Radius` | theme default | Border radius |
60
+ | `disabled` | `boolean` | `false` | Disable button |
61
+ | `w` | `string` | — | Width |
62
+ | `h` | `string` | — | Height |
63
+ | `align` | `"left" \| "center" \| "right"` | — | Text alignment |
64
+
65
+ ### Checkbox
66
+
67
+ ```tsx
68
+ <Checkbox label="Accept terms" color="primary" />
69
+
70
+ <Checkbox
71
+ checked={isChecked}
72
+ onChange={(checked) => setIsChecked(checked)}
73
+ label="Remember me"
74
+ labelPosition="right"
75
+ size="lg"
76
+ />
77
+ ```
78
+
79
+ | Prop | Type | Default | Description |
80
+ | ---------------- | ---------------------------- | --------- | ---------------------------- |
81
+ | `checked` | `boolean` | — | Controlled state |
82
+ | `defaultChecked` | `boolean` | `false` | Initial state (uncontrolled) |
83
+ | `onChange` | `(checked: boolean) => void` | — | Change handler |
84
+ | `label` | `string` | — | Label text |
85
+ | `labelPosition` | `"left" \| "right"` | `"right"` | Label placement |
86
+ | `color` | `Color \| string` | — | Check color |
87
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Checkbox size |
88
+ | `disabled` | `boolean` | `false` | Disable checkbox |
89
+
90
+ ### Chip
91
+
92
+ ```tsx
93
+ <Chip label="Tag" color="primary" variant="contained" />
94
+
95
+ <Chip label="Status" color="secondary" variant="outlined" startIcon={<MyIcon />} />
96
+ ```
97
+
98
+ | Prop | Type | Default | Description |
99
+ | ----------- | ------------------------------------- | ------------- | ------------------ |
100
+ | `label` | `string` | — | Chip text |
101
+ | `variant` | `"contained" \| "outlined" \| "text"` | theme default | Visual style |
102
+ | `color` | `Color \| string` | — | Color token or hex |
103
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Chip size |
104
+ | `startIcon` | `ReactNode` | — | Icon before label |
105
+ | `endIcon` | `ReactNode` | — | Icon after label |
106
+ | `radius` | `Radius` | theme default | Border radius |
107
+
108
+ ### Divider
109
+
110
+ ```tsx
111
+ <Divider />
112
+
113
+ <Divider orientation="vertical" color="#ccc" type="dashed" size="md" />
114
+ ```
115
+
116
+ | Prop | Type | Default | Description |
117
+ | ------------- | --------------------------------------------- | -------------- | --------------------------- |
118
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Divider direction |
119
+ | `color` | `string` | `"black"` | Line color |
120
+ | `size` | `"sm" \| "md" \| "lg"` | `"sm"` | Thickness (1px / 2px / 3px) |
121
+ | `type` | `"solid" \| "dotted" \| "dashed" \| "double"` | `"solid"` | Line style |
122
+
123
+ ### Flex
124
+
125
+ ```tsx
126
+ <Flex direction="row" align="center" justify="between" gap={16}>
127
+ <Text>Left</Text>
128
+ <Text>Right</Text>
129
+ </Flex>
130
+
131
+ <Flex direction="column" gap={8} elevation={2} p={16}>
132
+ <Text>Card content</Text>
133
+ </Flex>
134
+ ```
135
+
136
+ | Prop | Type | Default | Description |
137
+ | ----------- | ------------------------------------------------------------------- | ---------- | -------------------- |
138
+ | `direction` | `"row" \| "column" \| "row-reverse" \| "column-reverse"` | `"column"` | Flex direction |
139
+ | `align` | `"normal" \| "center" \| "start" \| "end" \| "stretch"` | — | Align items |
140
+ | `justify` | `"center" \| "start" \| "end" \| "between" \| "around" \| "evenly"` | — | Justify content |
141
+ | `gap` | `string \| number` | — | Gap between children |
142
+ | `wrap` | `"nowrap" \| "wrap" \| "wrap-reverse"` | — | Flex wrap |
143
+ | `width` | `string` | — | Container width |
144
+ | `height` | `string` | — | Container height |
145
+ | `elevation` | `number` | — | Box shadow depth |
146
+
147
+ ### Image
148
+
149
+ ```tsx
150
+ <Image src="/photo.jpg" alt="Photo" hoverstyle={{ transform: "scale(1.05)" }} />
151
+ ```
152
+
153
+ | Prop | Type | Default | Description |
154
+ | ------------ | --------------- | ------- | --------------- |
155
+ | `hoverstyle` | `CSSProperties` | — | Styles on hover |
156
+ | `pressstyle` | `CSSProperties` | — | Styles on press |
157
+
158
+ Extends all standard `<img>` attributes.
159
+
160
+ ### Input
161
+
162
+ ```tsx
163
+ <Input label="Email" placeholder="you@example.com" color="primary" required />
164
+
165
+ <Input label="Password" type="password" error="Password is required" />
166
+ ```
167
+
168
+ | Prop | Type | Default | Description |
169
+ | ---------- | ----------------- | ----------- | ------------------------- |
170
+ | `label` | `string` | — | Label text |
171
+ | `color` | `Color \| string` | `"default"` | Focus/border color |
172
+ | `error` | `string` | — | Error message below input |
173
+ | `required` | `boolean` | — | Shows required asterisk |
174
+ | `w` | `string` | — | Width |
175
+ | `h` | `string` | — | Height |
176
+
177
+ Extends all standard `<input>` attributes.
178
+
179
+ ### Modal
180
+
181
+ ```tsx
182
+ const [isOpen, setIsOpen] = useState(false);
183
+
184
+ <Button label="Open modal" onClick={() => setIsOpen(true)} />
185
+
186
+ <Modal
187
+ isOpen={isOpen}
188
+ onClose={() => setIsOpen(false)}
189
+ title="Confirmation"
190
+ size="m"
191
+ footer={<Button label="Confirm" color="primary" variant="contained" />}
192
+ >
193
+ <Text>Are you sure you want to proceed?</Text>
194
+ </Modal>
195
+ ```
196
+
197
+ | Prop | Type | Default | Description |
198
+ | --------------------- | ----------------------------------- | ------- | --------------------------------- |
199
+ | `isOpen` | `boolean` | — | **Required.** Controls visibility |
200
+ | `onClose` | `() => void` | — | Close callback |
201
+ | `title` | `ReactNode` | — | Modal title |
202
+ | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | `"m"` | Modal width (30vw–70vw) |
203
+ | `fullScreen` | `boolean` | — | Full screen mode |
204
+ | `footer` | `ReactNode` | — | Footer content |
205
+ | `close` | `ReactNode` | — | Custom close button |
206
+ | `closeOnClickOutside` | `boolean` | `true` | Close on backdrop click |
207
+
208
+ ### Popover
209
+
210
+ ```tsx
211
+ <Popover
212
+ content={<Text>Popover content</Text>}
213
+ color="primary"
214
+ placement="bottom"
215
+ >
216
+ <Button label="Hover me" />
217
+ </Popover>
218
+ ```
219
+
220
+ | Prop | Type | Default | Description |
221
+ | ------------ | ---------------------------------------- | ------------- | ------------------------------ |
222
+ | `content` | `ReactNode` | — | **Required.** Popover content |
223
+ | `color` | `Color \| string` | — | **Required.** Background color |
224
+ | `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Position relative to trigger |
225
+ | `visible` | `boolean` | — | Controlled visibility |
226
+ | `gap` | `number` | `8` | Distance from trigger (px) |
227
+ | `radius` | `Radius` | theme default | Border radius |
228
+ | `arrowcolor` | `string` | — | Arrow color override |
229
+
230
+ ### ProgressBar
231
+
232
+ ```tsx
233
+ <ProgressBar color="primary" value={75} />
234
+
235
+ <ProgressBar color="#22c55e" value={100} h="12px" animationDurationInSeconds={2} />
236
+ ```
237
+
238
+ | Prop | Type | Default | Description |
239
+ | ---------------------------- | ----------------- | -------- | ---------------------------- |
240
+ | `color` | `Color \| string` | — | **Required.** Bar color |
241
+ | `value` | `number` | — | **Required.** Progress 0–100 |
242
+ | `h` | `string` | `"8px"` | Bar height |
243
+ | `w` | `string` | `"100%"` | Bar width |
244
+ | `animationDurationInSeconds` | `number` | `1` | Animation duration |
245
+
246
+ ### Select
247
+
248
+ ```tsx
249
+ const options = [
250
+ { key: "react", label: "React" },
251
+ { key: "vue", label: "Vue" },
252
+ { key: "svelte", label: "Svelte" },
253
+ ];
254
+
255
+ <Select
256
+ options={options}
257
+ label="Framework"
258
+ placeholder="Pick one"
259
+ onChange={(option) => console.log(option)}
260
+ required
261
+ />;
262
+ ```
263
+
264
+ | Prop | Type | Default | Description |
265
+ | --------------- | ---------------------------------------------- | -------- | ---------------------------- |
266
+ | `options` | `{ key: string; label: string; data?: any }[]` | — | **Required.** Options list |
267
+ | `value` | `SelectOption` | — | Controlled value |
268
+ | `initialValue` | `SelectOption` | — | Initial value (uncontrolled) |
269
+ | `onChange` | `(value: SelectOption) => void` | — | Change handler |
270
+ | `placeholder` | `string` | — | Placeholder text |
271
+ | `label` | `string` | — | Label text |
272
+ | `required` | `boolean` | — | Shows required asterisk |
273
+ | `orientation` | `"up" \| "down"` | `"down"` | Dropdown direction |
274
+ | `CustomOption` | `(props) => ReactNode` | — | Custom option renderer |
275
+ | `hideScrollbar` | `boolean` | — | Hide dropdown scrollbar |
276
+
277
+ ### Skeleton
278
+
279
+ ```tsx
280
+ <Skeleton width="200px" height="20px" color="primary" />
281
+
282
+ <Skeleton width="48px" height="48px" radius="50%" />
283
+ ```
284
+
285
+ | Prop | Type | Default | Description |
286
+ | -------- | ----------------- | ------- | --------------- |
287
+ | `width` | `string` | `auto` | Skeleton width |
288
+ | `height` | `string` | `auto` | Skeleton height |
289
+ | `radius` | `string` | — | Border radius |
290
+ | `color` | `Color \| string` | — | Shimmer color |
291
+
292
+ ### Switch
293
+
294
+ ```tsx
295
+ <Switch value={isOn} onChange={(on) => setIsOn(on)} color="primary" />
296
+
297
+ <Switch color="secondary" size={32} />
298
+ ```
299
+
300
+ | Prop | Type | Default | Description |
301
+ | ---------- | ------------------------- | ------- | ---------------------------------- |
302
+ | `value` | `boolean` | `false` | On/off state |
303
+ | `onChange` | `(isOn: boolean) => void` | — | Change handler |
304
+ | `color` | `Color \| string` | — | Active color |
305
+ | `size` | `number` | `48` | Width in px (height = size / 1.75) |
306
+
307
+ ### Text
308
+
309
+ ```tsx
310
+ <Text size="16" weight="medium" color="primary">Hello world</Text>
311
+
312
+ <Text size="14" color="#666" align="center">Centered subtitle</Text>
313
+ ```
314
+
315
+ | Prop | Type | Default | Description |
316
+ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------- |
317
+ | `size` | `"8" \| "10" \| "12" \| "14" \| "15" \| "16" \| "18" \| "20" \| "24" \| "28" \| "32" \| "36" \| "40" \| "44" \| "48" \| "52" \| "56" \| "60" \| "64" \| "72" \| "80" \| "96"` | — | Font size |
318
+ | `weight` | `"regular" \| "medium" \| "semibold" \| "bold"` | — | Font weight |
319
+ | `color` | `Color \| string` | — | Text color |
320
+ | `align` | `"left" \| "center" \| "right" \| "justify"` | — | Text alignment |
321
+ | `width` | `string` | — | Container width |
322
+
323
+ ### TextArea
324
+
325
+ ```tsx
326
+ <TextArea label="Message" placeholder="Type here..." color="primary" rows={4} />
327
+
328
+ <TextArea label="Bio" required error="This field is required" />
329
+ ```
330
+
331
+ | Prop | Type | Default | Description |
332
+ | ---------- | ----------------- | ----------- | ---------------------------- |
333
+ | `label` | `string` | — | Label text |
334
+ | `color` | `Color \| string` | `"default"` | Focus/border color |
335
+ | `error` | `string` | — | Error message below textarea |
336
+ | `required` | `boolean` | — | Shows required asterisk |
337
+ | `w` | `string` | — | Width |
338
+ | `h` | `string` | — | Height |
339
+
340
+ Extends all standard `<textarea>` attributes.
341
+
342
+ ### Title
343
+
344
+ ```tsx
345
+ <Title level={1} weight="bold" color="primary">Page Title</Title>
346
+
347
+ <Title level={3}>Section Heading</Title>
348
+ ```
349
+
350
+ | Prop | Type | Default | Description |
351
+ | -------- | ----------------------------------------------- | ------- | -------------------------------------------- |
352
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — | **Required.** HTML heading level (`h1`–`h6`) |
353
+ | `weight` | `"regular" \| "medium" \| "semibold" \| "bold"` | — | Font weight |
354
+ | `color` | `Color \| string` | — | Title color |
355
+
356
+ ### Toast
357
+
358
+ Toasts are rendered via context. Use the `useToast` hook:
359
+
360
+ ```tsx
361
+ const { addToast } = useToast();
362
+
363
+ addToast({ title: "Saved!", color: "primary", icon: <CheckIcon /> });
364
+ ```
365
+
366
+ | Prop | Type | Default | Description |
367
+ | ------- | ----------------- | ------- | ------------- |
368
+ | `title` | `string` | — | Toast message |
369
+ | `color` | `Color \| string` | — | Accent color |
370
+ | `icon` | `ReactNode` | — | Leading icon |
371
+
372
+ ### Tooltip
373
+
374
+ ```tsx
375
+ <Tooltip label="Copy to clipboard" color="primary" placement="top">
376
+ <Button starticon="copy" variant="text" />
377
+ </Tooltip>
378
+ ```
379
+
380
+ | Prop | Type | Default | Description |
381
+ | ----------- | ---------------------------------------- | ------------- | ----------------------------- |
382
+ | `label` | `ReactNode` | — | **Required.** Tooltip content |
383
+ | `color` | `Color \| string` | `"primary"` | Background color |
384
+ | `variant` | `"contained" \| "outlined" \| "text"` | theme default | Visual style |
385
+ | `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Position |
386
+ | `gap` | `number` | `8` | Distance from trigger (px) |
387
+ | `radius` | `Radius` | theme default | Border radius |
388
+
389
+ ### Icons
390
+
391
+ All icons accept `size`, `color`, `strokeWidth`, and `animated` props.
392
+
393
+ ```tsx
394
+ <BellIcon size={24} hasNotification={true} />
395
+ <HeartIcon size={24} isLiked={liked} />
396
+ <CheckIcon size={24} isVisible={done} />
397
+ <SyncIcon size={24} isSyncing={loading} />
398
+ <HamburgerIcon size={24} isOpen={menuOpen} />
399
+ <CopyIcon size={24} isCopied={copied} />
400
+ <SparklesIcon size={24} animated />
401
+ <DotsIcon size={24} animated />
402
+ <SendHorizontalIcon size={24} animated />
403
+ ```
404
+
405
+ Icons can also be referenced by name in `Button` via `starticon` / `endicon`:
406
+
407
+ ```tsx
408
+ <Button starticon="bell" label="Notifications" />
409
+ ```
410
+
411
+ ## Spacing
412
+
413
+ All components (except a few like Select, Image) support margin and padding shorthand props:
414
+
415
+ ```tsx
416
+ <Button label="Spaced" mt={16} mx={8} p={12} />
417
+ ```
418
+
419
+ | Prop | Description |
420
+ | ---------------------- | ----------------------------------- |
421
+ | `m` | Margin all sides |
422
+ | `mt`, `mr`, `mb`, `ml` | Margin top / right / bottom / left |
423
+ | `mx`, `my` | Margin horizontal / vertical |
424
+ | `p` | Padding all sides |
425
+ | `pt`, `pr`, `pb`, `pl` | Padding top / right / bottom / left |
426
+ | `px`, `py` | Padding horizontal / vertical |
427
+
428
+ ## Theme
429
+
430
+ ### Creating a theme
431
+
432
+ Use `createTheme` to build a theme object. Pass a color string and the palette (5 shades) is generated automatically:
433
+
434
+ ```tsx
435
+ import { createTheme } from "@julien-wiegandt/open-ui";
436
+
437
+ const theme = createTheme({
438
+ radius: "md",
439
+ primary: "#6090fa",
440
+ secondary: "#8b5cf6",
441
+ error: "#e74c3c",
442
+ titleFontFamily: "Inter Variable",
443
+ textFontFamily: "Space Mono",
444
+ });
445
+ ```
446
+
447
+ | Param | Type | Default | Description |
448
+ | ----------------- | ------------------------------------------ | ----------------------- | ----------------------------------------------------------------------------- |
449
+ | `radius` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | — | **Required.** Global border radius (`0px` / `8px` / `12px` / `14px` / `42px`) |
450
+ | `primary` | `string \| ColorPalette` | — | **Required.** Primary color hex or custom palette |
451
+ | `secondary` | `string \| ColorPalette` | `"#000000"` | Secondary color |
452
+ | `default` | `string \| ColorPalette` | `"#000000"` | Default color |
453
+ | `error` | `string \| ColorPalette` | `"#e74c3c"` | Error color |
454
+ | `titleFontFamily` | `string` | `"Poppins"` | Heading font family |
455
+ | `textFontFamily` | `string` | `"Poppins, sans-serif"` | Body text font family |
456
+ | `components` | `object` | — | Per-component style overrides |
457
+
458
+ ### Custom color palette
459
+
460
+ Instead of a hex string, pass a full palette object for precise control:
461
+
462
+ ```tsx
463
+ const theme = createTheme({
464
+ radius: "lg",
465
+ primary: {
466
+ darker: "#162a55",
467
+ dark: "#1c448c",
468
+ main: "#6090fa",
469
+ light: "#daeeff",
470
+ lighter: "#eff8ff",
471
+ },
472
+ });
473
+ ```
474
+
475
+ ### Multiple themes
476
+
477
+ Pass multiple themes and switch at runtime:
478
+
479
+ ```tsx
480
+ import { OpenUIProvider, createTheme } from "@julien-wiegandt/open-ui";
481
+
482
+ const light = createTheme({ radius: "md", primary: "#6090fa" });
483
+ const dark = createTheme({ radius: "md", primary: "#818cf8" });
484
+
485
+ <OpenUIProvider themes={[light, dark]}>
486
+ <App />
487
+ </OpenUIProvider>;
488
+ ```
489
+
490
+ Switch themes from any child component:
491
+
492
+ ```tsx
493
+ import { ThemeContext } from "@julien-wiegandt/open-ui";
494
+
495
+ const { setTheme } = useContext(ThemeContext);
496
+
497
+ // Switch by index
498
+ setTheme({ index: 1 });
499
+
500
+ // Or pass a new theme object
501
+ setTheme({ theme: myCustomTheme });
502
+ ```
503
+
504
+ ### Provider settings
505
+
506
+ ```tsx
507
+ <OpenUIProvider
508
+ themes={[theme]}
509
+ settings={{
510
+ autoContrast: true, // auto-adjust text color for readability (default: true)
511
+ toasts: { ... }, // toast notification settings
512
+ }}
513
+ >
514
+ ```
@@ -1,5 +1,5 @@
1
- import { Color, Radius, Variant } from '../../theme/types';
2
1
  import { default as React } from 'react';
2
+ import { Color, Radius, Variant } from '../../theme/types';
3
3
  import { MarginProps, PaddingProps } from '../common/types';
4
4
  import { TextProps } from '../text';
5
5
  export type Icon = "bell" | "check" | "hamburger" | "heart" | "sync" | "sparkles" | "dots" | "send" | "copy";
@@ -0,0 +1,2 @@
1
+ import { RefObject } from 'react';
2
+ export declare function useAutoContrastColor(elementRef: RefObject<Element | null>, skip?: boolean): string | undefined;