@korsolutions/ui 0.0.89 → 0.0.90

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,21 @@
1
+ # KorUI
2
+
3
+ A minimal-dependency, cross-platform UI library for React Native and Expo. Flexible components with beautiful default styling.
4
+
5
+ ## Philosophy
6
+
7
+ - **Minimal dependencies** - We keep the dependency footprint as small as possible
8
+ - **Beautiful defaults** - Production-ready styling out of the box
9
+ - **Fully customizable** - Flexible variant system to match your brand
10
+ - **Cross-platform** - iOS, Android, and Web support
11
+ - **Easy to use** - Simple, intuitive APIs that just work
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @korsolutions/ui
17
+ ```
18
+
19
+ ## License
20
+
21
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korsolutions/ui",
3
- "version": "0.0.89",
3
+ "version": "0.0.90",
4
4
  "bugs": {
5
5
  "url": "https://github.com/KorSoftwareSolutions/ui/issues"
6
6
  },
@@ -20,8 +20,15 @@
20
20
  },
21
21
  "./package.json": "./package.json"
22
22
  },
23
+ "files": [
24
+ "dist",
25
+ "src",
26
+ "README.md"
27
+ ],
23
28
  "scripts": {
24
29
  "prepare": "bob build",
30
+ "prepack": "bun scripts/prepack.ts",
31
+ "postpack": "bun scripts/postpack.ts",
25
32
  "lint": "tsc --noEmit && oxlint .",
26
33
  "fmt": "oxfmt"
27
34
  },
package/AGENTS.md DELETED
@@ -1,678 +0,0 @@
1
- # AGENTS.md - KorUI Library
2
-
3
- ## Overview
4
-
5
- This is the main UI component library package (`@korsolutions/ui`), a minimal-dependency, cross-platform UI library for React Native and Expo. Flexible components with beautiful default styling.
6
-
7
- ## Core Principles
8
-
9
- 1. **Minimal Dependencies** - Keep external dependencies to a minimum
10
- 2. **Beautiful Defaults** - Components ship with production-ready styling
11
- 3. **Variant System** - Use themed style hooks for component variants
12
- 4. **Compound Components** - Export components as objects with sub-components
13
- 5. **Type Safety** - Full TypeScript coverage with strict mode enabled
14
-
15
- ## Architecture
16
-
17
- The library uses a **3-layer architecture**:
18
-
19
- ```
20
- Layer 1: Primitives (behavior, no styles)
21
- Layer 2: Components (styled wrappers)
22
- Layer 3: Variants (themed style hooks)
23
-
24
- Themes (color tokens, spacing, typography)
25
- ```
26
-
27
- ### Layer 1: Primitives (`src/primitives/`)
28
-
29
- Headless UI components providing behavior without visual styles.
30
-
31
- **Pattern**:
32
-
33
- ```typescript
34
- // primitives/button/button-root.tsx
35
- export interface ButtonRootProps {
36
- children?: React.ReactNode;
37
- onPress?: () => void;
38
- isDisabled?: boolean;
39
- isLoading?: boolean;
40
- style?: StyleProp<ViewStyle>;
41
- styles?: ButtonStyles; // Injected by variants
42
- }
43
-
44
- export function ButtonRoot(props: ButtonRootProps) {
45
- const composedStyle = [props.styles?.root, props.style];
46
-
47
- return (
48
- <ButtonContext.Provider value={{ styles: props.styles }}>
49
- <Pressable
50
- style={composedStyle}
51
- disabled={props.isDisabled}
52
- onPress={props.onPress}
53
- >
54
- {props.children}
55
- </Pressable>
56
- </ButtonContext.Provider>
57
- );
58
- }
59
-
60
- // primitives/button/index.ts
61
- export const ButtonPrimitive = {
62
- Root: ButtonRoot,
63
- Label: ButtonLabel,
64
- Spinner: ButtonSpinner,
65
- };
66
- ```
67
-
68
- **Key Rules**:
69
-
70
- - ❌ No inline styles or hardcoded colors
71
- - ❌ No theme access (`useTheme`)
72
- - ✅ Accept `styles` prop for variant injection
73
- - ✅ Provide context for sub-components
74
- - ✅ Handle behavior (state, events, accessibility)
75
-
76
- ### Layer 2: Components (`src/components/`)
77
-
78
- Styled components using primitives with variant system.
79
-
80
- **Pattern**:
81
-
82
- ```typescript
83
- // components/button/components/button-root.tsx
84
- export interface ButtonRootProps extends ButtonPrimitiveRootProps {
85
- variant?: keyof typeof ButtonVariants;
86
- }
87
-
88
- export function ButtonRoot(props: ButtonRootProps) {
89
- const variant = props.variant ?? "default";
90
- const variantStyles = ButtonVariants[variant]();
91
-
92
- return <ButtonPrimitive.Root {...props} styles={variantStyles} />;
93
- }
94
-
95
- // components/button/index.ts
96
- export const Button = {
97
- Root: ButtonRoot,
98
- Label: ButtonPrimitive.Label,
99
- Spinner: ButtonPrimitive.Spinner,
100
- };
101
- ```
102
-
103
- **Key Rules**:
104
-
105
- - ✅ Use primitives as base
106
- - ✅ Apply variants for styling
107
- - ✅ Default variant is "default"
108
- - ✅ Re-export primitive sub-components
109
- - ❌ No direct styling in components
110
-
111
- ### Layer 3: Variants (`src/components/[name]/variants/`)
112
-
113
- Theme-aware style hooks providing reactive styles.
114
-
115
- **Pattern**:
116
-
117
- ```typescript
118
- // components/button/variants/default.tsx
119
- import { useThemedStyles } from "../../../utils/use-themed-styles";
120
- import { type ButtonStyles } from "../types";
121
-
122
- export const useButtonVariantDefault = (): ButtonStyles => {
123
- return useThemedStyles(
124
- ({ colors, radius, fontSize }): ButtonStyles => ({
125
- root: {
126
- backgroundColor: colors.primary,
127
- borderRadius: radius,
128
- padding: 12,
129
- paddingHorizontal: 16,
130
- },
131
- label: {
132
- color: colors.background,
133
- fontSize: fontSize,
134
- fontWeight: "600",
135
- },
136
- spinner: {
137
- color: colors.background,
138
- },
139
- }),
140
- );
141
- };
142
-
143
- // components/button/variants/index.ts
144
- export const ButtonVariants = {
145
- default: useButtonVariantDefault,
146
- secondary: useButtonVariantSecondary,
147
- };
148
- ```
149
-
150
- **Key Rules**:
151
-
152
- - ✅ Always use `useThemedStyles` hook
153
- - ✅ Return complete style objects
154
- - ✅ Access theme tokens (colors, radius, fontSize, etc.)
155
- - ❌ No hardcoded values (use theme)
156
- - ✅ Each variant is a hook
157
-
158
- ## Component Development Workflow
159
-
160
- ### Step 1: Create Primitive Structure
161
-
162
- ```bash
163
- mkdir -p src/primitives/alert/components
164
- touch src/primitives/alert/components/alert-root.tsx
165
- touch src/primitives/alert/components/alert-icon.tsx
166
- touch src/primitives/alert/components/alert-title.tsx
167
- touch src/primitives/alert/components/alert-description.tsx
168
- touch src/primitives/alert/context.ts
169
- touch src/primitives/alert/types.ts
170
- touch src/primitives/alert/index.ts
171
- ```
172
-
173
- ### Step 2: Define Types
174
-
175
- ```typescript
176
- // src/primitives/alert/types.ts
177
- import type { AlertRootProps } from "./components/alert-root";
178
- import type { AlertIconProps } from "./components/alert-icon";
179
- import type { AlertTitleProps } from "./components/alert-title";
180
- import type { AlertDescriptionProps } from "./components/alert-description";
181
-
182
- export interface AlertStyles {
183
- root?: AlertRootProps["style"];
184
- icon?: AlertIconProps;
185
- title?: AlertTitleProps["style"];
186
- description?: AlertDescriptionProps["style"];
187
- }
188
- ```
189
-
190
- ### Step 3: Create Context
191
-
192
- ```typescript
193
- // src/primitives/alert/context.ts
194
- import { createContext, useContext } from "react";
195
- import type { AlertStyles } from "./types";
196
-
197
- export interface AlertContext {
198
- styles?: AlertStyles;
199
- }
200
-
201
- export const AlertContext = createContext<AlertContext | undefined>(undefined);
202
-
203
- export const useAlert = () => {
204
- const context = useContext(AlertContext);
205
- if (!context) {
206
- throw new Error("useAlert must be used within Alert");
207
- }
208
- return context;
209
- };
210
- ```
211
-
212
- ### Step 4: Create Root Component
213
-
214
- ```typescript
215
- // src/primitives/alert/components/alert-root.tsx
216
- import React from "react";
217
- import { type StyleProp, View, type ViewStyle } from "react-native";
218
- import { AlertContext } from "../context";
219
- import type { AlertStyles } from "../types";
220
-
221
- export interface AlertRootProps {
222
- children?: React.ReactNode;
223
- style?: StyleProp<ViewStyle>;
224
- styles?: AlertStyles;
225
- }
226
-
227
- export function AlertRoot(props: AlertRootProps) {
228
- const composedStyle = [props.styles?.root, props.style];
229
-
230
- return (
231
- <AlertContext.Provider value={{ styles: props.styles }}>
232
- <View style={composedStyle}>{props.children}</View>
233
- </AlertContext.Provider>
234
- );
235
- }
236
- ```
237
-
238
- ### Step 5: Create Sub-Components
239
-
240
- ```typescript
241
- // src/primitives/alert/components/alert-icon.tsx
242
- import type { PropsWithRequiredRender, SvgProps } from "@/types/props.types";
243
- import React from "react";
244
- import { useAlert } from "../context";
245
-
246
- export type AlertIconProps = SvgProps;
247
-
248
- export function AlertIcon({
249
- render: Component,
250
- ...props
251
- }: PropsWithRequiredRender<AlertIconProps>) {
252
- const alert = useAlert();
253
-
254
- const composedProps = {
255
- ...alert.styles?.icon,
256
- ...props,
257
- style: [alert.styles?.icon?.style, props.style],
258
- };
259
-
260
- return <Component absoluteStrokeWidth {...composedProps} />;
261
- }
262
- ```
263
-
264
- ### Step 6: Export Primitive
265
-
266
- ```typescript
267
- // src/primitives/alert/index.ts
268
- import { AlertRoot } from "./components/alert-root";
269
- import { AlertIcon } from "./components/alert-icon";
270
- import { AlertTitle } from "./components/alert-title";
271
- import { AlertDescription } from "./components/alert-description";
272
-
273
- export const AlertPrimitive = {
274
- Root: AlertRoot,
275
- Icon: AlertIcon,
276
- Title: AlertTitle,
277
- Description: AlertDescription,
278
- };
279
-
280
- export type { AlertRootProps } from "./components/alert-root";
281
- export type { AlertIconProps } from "./components/alert-icon";
282
- export type { AlertTitleProps } from "./components/alert-title";
283
- export type { AlertDescriptionProps } from "./components/alert-description";
284
- export type { AlertStyles } from "./types";
285
- ```
286
-
287
- ### Step 7: Create Component Wrapper
288
-
289
- ```bash
290
- mkdir -p src/components/alert/components
291
- mkdir -p src/components/alert/variants
292
- touch src/components/alert/components/alert-root.tsx
293
- touch src/components/alert/variants/default.tsx
294
- touch src/components/alert/variants/destructive.tsx
295
- touch src/components/alert/variants/index.ts
296
- touch src/components/alert/types.ts
297
- touch src/components/alert/index.ts
298
- ```
299
-
300
- ### Step 8: Create Variant
301
-
302
- ```typescript
303
- // src/components/alert/variants/default.tsx
304
- import { useThemedStyles } from "../../../utils/use-themed-styles";
305
- import type { AlertStyles } from "../types";
306
-
307
- export const useAlertVariantDefault = (): AlertStyles => {
308
- return useThemedStyles(
309
- ({ colors, radius, fontSize }): AlertStyles => ({
310
- root: {
311
- padding: 16,
312
- backgroundColor: colors.background,
313
- borderRadius: radius,
314
- borderWidth: 1,
315
- borderColor: colors.border,
316
- flexDirection: "row",
317
- gap: 12,
318
- },
319
- icon: {
320
- color: colors.foreground,
321
- size: 20,
322
- style: {
323
- marginTop: 2,
324
- },
325
- },
326
- title: {
327
- fontSize: fontSize,
328
- fontWeight: "600",
329
- color: colors.foreground,
330
- marginBottom: 4,
331
- },
332
- description: {
333
- fontSize: fontSize * 0.875,
334
- color: colors.mutedForeground,
335
- lineHeight: 20,
336
- },
337
- }),
338
- );
339
- };
340
- ```
341
-
342
- ### Step 9: Create Component
343
-
344
- ```typescript
345
- // src/components/alert/components/alert-root.tsx
346
- import { AlertPrimitive } from "@/primitives";
347
- import type { AlertRootProps as AlertPrimitiveRootProps } from "@/primitives";
348
- import React from "react";
349
- import { AlertVariants } from "../variants";
350
-
351
- export interface AlertRootProps extends AlertPrimitiveRootProps {
352
- variant?: keyof typeof AlertVariants;
353
- }
354
-
355
- export function AlertRoot(props: AlertRootProps) {
356
- const variant = props.variant ?? "default";
357
- const variantStyles = AlertVariants[variant]();
358
-
359
- return <AlertPrimitive.Root {...props} styles={variantStyles} />;
360
- }
361
-
362
- // src/components/alert/index.ts
363
- import { AlertPrimitive } from "@/primitives";
364
- import { AlertRoot } from "./components/alert-root";
365
-
366
- export const Alert = {
367
- Root: AlertRoot,
368
- Icon: AlertPrimitive.Icon,
369
- Title: AlertPrimitive.Title,
370
- Description: AlertPrimitive.Description,
371
- };
372
-
373
- export type { AlertRootProps } from "./components/alert-root";
374
- export type { AlertIconProps } from "@/primitives";
375
- export type { AlertTitleProps } from "@/primitives";
376
- export type { AlertDescriptionProps } from "@/primitives";
377
- export type { AlertStyles } from "./types";
378
- ```
379
-
380
- ### Step 10: Export from Indices
381
-
382
- ```typescript
383
- // src/primitives/index.ts
384
- export * from "./alert";
385
- // ... other primitives
386
-
387
- // src/components/index.ts
388
- export * from "./alert";
389
- // ... other components
390
- ```
391
-
392
- ## Common Patterns
393
-
394
- ### Context Pattern for Sub-Components
395
-
396
- Sub-components access styles via context:
397
-
398
- ```typescript
399
- // In Root component
400
- <ComponentContext.Provider value={{ styles: props.styles }}>
401
- {props.children}
402
- </ComponentContext.Provider>
403
-
404
- // In sub-component
405
- const component = useComponent();
406
- const composedProps = {
407
- ...component.styles?.subComponent,
408
- ...props,
409
- style: [component.styles?.subComponent?.style, props.style],
410
- };
411
- ```
412
-
413
- ### Style Composition Order
414
-
415
- Always compose styles in this order:
416
-
417
- 1. Variant styles (from `styles` prop)
418
- 2. User styles (from `style` prop)
419
-
420
- ```typescript
421
- const composedStyle = [props.styles?.root, props.style];
422
- ```
423
-
424
- ### SVG Icon Components
425
-
426
- For components accepting icons:
427
-
428
- ```typescript
429
- export type IconProps = SvgProps;
430
-
431
- export function Icon({
432
- render: Component,
433
- ...props
434
- }: PropsWithRequiredRender<IconProps>) {
435
- const composedProps = {
436
- ...context.styles?.icon,
437
- ...props,
438
- style: [context.styles?.icon?.style, props.style],
439
- absoluteStrokeWidth: true,
440
- };
441
-
442
- return <Component {...composedProps} />;
443
- }
444
- ```
445
-
446
- ## Theme System
447
-
448
- ### Accessing Theme
449
-
450
- ```typescript
451
- // In component code (variants only)
452
- import { useThemedStyles } from "../../../utils/use-themed-styles";
453
-
454
- const styles = useThemedStyles((theme) => ({
455
- container: {
456
- backgroundColor: theme.colors.background,
457
- borderRadius: theme.radius,
458
- },
459
- }));
460
- ```
461
-
462
- ### Theme Structure
463
-
464
- ```typescript
465
- interface Theme {
466
- colors: {
467
- background: string;
468
- foreground: string;
469
- primary: string;
470
- secondary: string;
471
- muted: string;
472
- mutedForeground: string;
473
- border: string;
474
- success: string;
475
- warning: string;
476
- danger: string;
477
- // ... more
478
- };
479
- radius: number;
480
- fontSize: number;
481
- fontFamily: string;
482
- letterSpacing: number;
483
- }
484
- ```
485
-
486
- ### Theme Tokens
487
-
488
- Always use theme tokens instead of hardcoded values:
489
-
490
- | Token | Usage |
491
- | ------------------- | -------------------------------- |
492
- | `colors.background` | Main background color |
493
- | `colors.foreground` | Main text color |
494
- | `colors.primary` | Primary action color |
495
- | `colors.muted` | Subtle background (hover states) |
496
- | `colors.border` | Border color |
497
- | `radius` | Border radius value |
498
- | `fontSize` | Base font size |
499
- | `fontFamily` | Font family string |
500
-
501
- ## Anti-Patterns (Avoid)
502
-
503
- ### ❌ Inline Styles in Primitives
504
-
505
- ```typescript
506
- // BAD
507
- <View style={{ padding: 16, backgroundColor: '#fff' }}>
508
- ```
509
-
510
- ### ✅ Use styles prop
511
-
512
- ```typescript
513
- // GOOD
514
- <View style={[props.styles?.root, props.style]}>
515
- ```
516
-
517
- ### ❌ Direct Theme in Primitives
518
-
519
- ```typescript
520
- // BAD - primitives should not access theme
521
- const theme = useTheme();
522
- ```
523
-
524
- ### ✅ Theme Only in Variants
525
-
526
- ```typescript
527
- // GOOD - use in variant hooks
528
- export const useVariant = (): Styles => {
529
- return useThemedStyles((theme) => ({
530
- root: { backgroundColor: theme.colors.background },
531
- }));
532
- };
533
- ```
534
-
535
- ### ❌ Hardcoded Colors
536
-
537
- ```typescript
538
- // BAD
539
- backgroundColor: "#007AFF";
540
- ```
541
-
542
- ### ✅ Theme Colors
543
-
544
- ```typescript
545
- // GOOD
546
- backgroundColor: theme.colors.primary;
547
- ```
548
-
549
- ### ❌ String Values for Spacing
550
-
551
- ```typescript
552
- // BAD
553
- padding: "16px";
554
- ```
555
-
556
- ### ✅ Number Values
557
-
558
- ```typescript
559
- // GOOD
560
- padding: 16;
561
- ```
562
-
563
- ## Type Safety
564
-
565
- ### Export All Types
566
-
567
- ```typescript
568
- export type { ComponentRootProps } from "./components/component-root";
569
- export type { ComponentStyles } from "./types";
570
- ```
571
-
572
- ### Use PropsWithRequiredRender
573
-
574
- For components requiring `render` prop:
575
-
576
- ```typescript
577
- import type { PropsWithRequiredRender, SvgProps } from "@/types/props.types";
578
-
579
- function Icon(props: PropsWithRequiredRender<SvgProps>) {
580
- const { render: Component, ...rest } = props;
581
- return <Component {...rest} />;
582
- }
583
- ```
584
-
585
- ### StyleProp Types
586
-
587
- ```typescript
588
- import type { StyleProp, ViewStyle, TextStyle } from "react-native";
589
-
590
- interface Props {
591
- style?: StyleProp<ViewStyle>;
592
- textStyle?: StyleProp<TextStyle>;
593
- }
594
- ```
595
-
596
- ## Build System
597
-
598
- ### Building the Library
599
-
600
- ```bash
601
- cd library
602
- bun build
603
- ```
604
-
605
- This runs:
606
-
607
- 1. `bob build` - Compiles TypeScript with Babel
608
- 2. `tsc-alias` - Resolves path aliases in type definitions
609
-
610
- ### Build Output
611
-
612
- ```
613
- dist/
614
- ├── commonjs/ # CommonJS modules
615
- ├── module/ # ES modules (.mjs)
616
- └── typescript/ # Type definitions (.d.mts)
617
- ```
618
-
619
- ### Package Exports
620
-
621
- ```json
622
- {
623
- "exports": {
624
- ".": "./src/index.tsx",
625
- "./primitives": "./src/primitives/index.ts",
626
- "./components": "./src/components/index.ts",
627
- "./hooks": "./src/hooks/index.ts"
628
- }
629
- }
630
- ```
631
-
632
- ## Component Checklist
633
-
634
- - [ ] Primitive created with compound component pattern
635
- - [ ] Context created for sharing styles
636
- - [ ] Types defined in `types.ts`
637
- - [ ] Root component provides context
638
- - [ ] Sub-components use context for styles
639
- - [ ] Component wrapper uses primitive
640
- - [ ] At least one variant created (default)
641
- - [ ] Variant uses `useThemedStyles`
642
- - [ ] Exported from primitive index
643
- - [ ] Exported from component index
644
- - [ ] TypeScript types exported
645
- - [ ] No console errors or warnings
646
- - [ ] Example page created
647
- - [ ] Added to navigation
648
-
649
- ## Common Issues
650
-
651
- **Issue**: "Cannot find module @/..."
652
-
653
- - **Fix**: Check `tsconfig.json` has path alias: `"@/*": ["./src/*"]`
654
-
655
- **Issue**: Styles not applying
656
-
657
- - **Fix**: Ensure variant styles passed to primitive via `styles` prop
658
-
659
- **Issue**: Theme not updating
660
-
661
- - **Fix**: Use `useThemedStyles` hook, not plain `useMemo`
662
-
663
- **Issue**: Type errors with compound exports
664
-
665
- - **Fix**: Export both types and components from index files
666
-
667
- **Issue**: Build fails
668
-
669
- - **Fix**: Run `bun build` from library directory, check for TypeScript errors
670
-
671
- ## Resources
672
-
673
- - **Monorepo docs**: [`../AGENTS.md`](../AGENTS.md)
674
- - **Example app**: [`../example/AGENTS.md`](../example/AGENTS.md)
675
- - **Source code**: [`src/`](src/)
676
- - **Primitives**: [`src/primitives/`](src/primitives/)
677
- - **Components**: [`src/components/`](src/components/)
678
- - **Themes**: [`src/themes/`](src/themes/)
package/babel.config.js DELETED
@@ -1,8 +0,0 @@
1
- module.exports = function (api) {
2
- api.cache(true);
3
-
4
- return {
5
- presets: ["module:react-native-builder-bob/babel-preset"],
6
- plugins: [],
7
- };
8
- };
package/bob.config.js DELETED
@@ -1,14 +0,0 @@
1
- module.exports = {
2
- source: "src",
3
- output: "dist",
4
- targets: [
5
- [
6
- "module",
7
- {
8
- esm: true,
9
- configFile: true,
10
- },
11
- ],
12
- "typescript",
13
- ],
14
- };
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "customConditions": ["dev-source", "react-native", "react-native-strict-api"],
4
- "allowJs": true,
5
- "esModuleInterop": true,
6
- "jsx": "react-native",
7
- "lib": ["DOM", "ESNext"],
8
- "module": "preserve",
9
- "moduleDetection": "force",
10
- "moduleResolution": "bundler",
11
- "noEmit": true,
12
- "resolveJsonModule": true,
13
- "skipLibCheck": true,
14
- "target": "ESNext",
15
- "strict": true,
16
- "verbatimModuleSyntax": true,
17
- "allowUnreachableCode": false,
18
- "allowUnusedLabels": false,
19
- "forceConsistentCasingInFileNames": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noImplicitReturns": true,
22
- "noImplicitUseStrict": false,
23
- "noStrictGenericChecks": false,
24
- "noUncheckedIndexedAccess": true,
25
- "noUnusedLocals": true,
26
- "noUnusedParameters": true
27
- },
28
- "exclude": ["node_modules", "dist"]
29
- }