@nwartz/design-system 0.1.0

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/index.ts ADDED
@@ -0,0 +1,89 @@
1
+ // Tokens
2
+ export {
3
+ darkColors,
4
+ lightColors,
5
+ typography,
6
+ elevation,
7
+ spacing,
8
+ radius,
9
+ colorMap,
10
+ spacingMap,
11
+ radiusMap,
12
+ } from './tokens';
13
+ export type {
14
+ ColorToken,
15
+ ResolvedColors,
16
+ BrandColors,
17
+ TypographyToken,
18
+ ElevationToken,
19
+ SpacingToken,
20
+ RadiusToken,
21
+ } from './tokens';
22
+
23
+ // Theme
24
+ export { ThemeProvider, useTheme } from './theme';
25
+ export type { ThemeContextValue, ThemeMode, ThemeProviderProps } from './theme';
26
+
27
+ // Components
28
+ export {
29
+ Box,
30
+ Text,
31
+ Card,
32
+ Button,
33
+ Icon,
34
+ DropdownSelect,
35
+ Autocomplete,
36
+ SegmentedTabs,
37
+ Badge,
38
+ InputField,
39
+ Tag,
40
+ Toggle,
41
+ DatePicker,
42
+ Checkbox,
43
+ TablePagination,
44
+ LinkButton,
45
+ BottomNavBar,
46
+ ProgressBar,
47
+ SearchBar,
48
+ Slider,
49
+ TabMenu,
50
+ } from './components';
51
+ export type {
52
+ BoxProps,
53
+ TextProps,
54
+ CardProps,
55
+ ButtonProps,
56
+ ButtonVariant,
57
+ IconProps,
58
+ IconName,
59
+ DropdownSelectProps,
60
+ DropdownItem,
61
+ AutocompleteProps,
62
+ AutocompleteSize,
63
+ AutocompleteState,
64
+ SegmentedTabsProps,
65
+ SegmentedTabItem,
66
+ BadgeProps,
67
+ BadgeSize,
68
+ BadgeColor,
69
+ BadgeMode,
70
+ InputFieldProps,
71
+ InputFieldSize,
72
+ TagProps,
73
+ TagSize,
74
+ ToggleProps,
75
+ ToggleSize,
76
+ DatePickerProps,
77
+ CheckboxProps,
78
+ CheckboxSize,
79
+ CheckboxType,
80
+ TablePaginationProps,
81
+ LinkButtonProps,
82
+ BottomNavBarProps,
83
+ BottomNavBarItem,
84
+ ProgressBarProps,
85
+ SearchBarProps,
86
+ SliderProps,
87
+ TabMenuProps,
88
+ TabMenuItem,
89
+ } from './components';
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@nwartz/design-system",
3
+ "version": "0.1.0",
4
+ "description": "Shared design system tokens and components for rotta.am clients",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "tokens/",
18
+ "theme/",
19
+ "components/",
20
+ "index.ts",
21
+ "!**/*.test.ts",
22
+ "!**/*.test.tsx",
23
+ "!**/__mocks__"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "typecheck": "tsc --noEmit",
28
+ "lint": "eslint .",
29
+ "lint:fix": "eslint . --fix",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "peerDependencies": {
35
+ "phosphor-react-native": ">=2.0.0",
36
+ "react": ">=18",
37
+ "react-native": ">=0.70"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "phosphor-react-native": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "devDependencies": {
48
+ "@eslint/js": "^9.0.0",
49
+ "@types/react": "~19.2.2",
50
+ "eslint": "^9.0.0",
51
+ "eslint-config-prettier": "^10.0.0",
52
+ "eslint-plugin-security": "^4.0.0",
53
+ "eslint-plugin-sonarjs": "^4.0.0",
54
+ "globals": "^16.0.0",
55
+ "phosphor-react-native": "^3.0.6",
56
+ "prettier": "^3.0.0",
57
+ "react-native": "^0.87.1",
58
+ "tsup": "^8.0.0",
59
+ "typescript": "~5.7.0",
60
+ "typescript-eslint": "^8.0.0",
61
+ "vitest": "^4.0.0"
62
+ }
63
+ }
package/theme/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { ThemeProvider, useTheme } from './theme';
2
+ export type { ThemeContextValue, ThemeMode, ThemeProviderProps } from './theme';
@@ -0,0 +1,61 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from 'react';
2
+ import { useColorScheme } from 'react-native';
3
+
4
+ import { darkColors, lightColors, type ResolvedColors } from '../tokens/colors';
5
+
6
+ export type ThemeMode = 'light' | 'dark';
7
+
8
+ export interface ThemeContextValue {
9
+ mode: ThemeMode;
10
+ colors: ResolvedColors;
11
+ /** Convenience: is the current mode dark? */
12
+ isDark: boolean;
13
+ }
14
+
15
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
16
+
17
+ export interface ThemeProviderProps {
18
+ children: ReactNode;
19
+ /** Override the system color scheme. */
20
+ mode?: ThemeMode;
21
+ /** Override the full color set (e.g. from an org manifest). */
22
+ colors?: Partial<ResolvedColors>;
23
+ }
24
+
25
+ /**
26
+ * Wraps the app and provides the resolved color palette to all
27
+ * design-system components via `useTheme()`.
28
+ */
29
+ export function ThemeProvider({ children, mode: modeOverride, colors: colorOverrides }: ThemeProviderProps) {
30
+ const systemScheme = useColorScheme();
31
+ const mode: ThemeMode = modeOverride ?? (systemScheme === 'light' ? 'light' : 'dark');
32
+
33
+ const value = useMemo<ThemeContextValue>(
34
+ () => ({
35
+ mode,
36
+ isDark: mode === 'dark',
37
+ colors: { ...(mode === 'dark' ? darkColors : lightColors), ...colorOverrides } as ResolvedColors,
38
+ }),
39
+ [mode, colorOverrides],
40
+ );
41
+
42
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
43
+ }
44
+
45
+ /**
46
+ * Access the current theme. Must be used inside a `<ThemeProvider>`.
47
+ *
48
+ * ```tsx
49
+ * const { colors, mode } = useTheme();
50
+ * ```
51
+ */
52
+ export function useTheme(): ThemeContextValue {
53
+ const ctx = useContext(ThemeContext);
54
+
55
+ if (!ctx) {
56
+ // Graceful fallback when used outside a provider — default to dark mode.
57
+ return { mode: 'dark', isDark: true, colors: darkColors };
58
+ }
59
+
60
+ return ctx;
61
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Nwartz DS — Dark Mode color tokens.
3
+ *
4
+ * Source: Figma "Nwartz DS | Foundations Dark Mode"
5
+ * These are the base neutrals; brand-specific colors (primary, accent)
6
+ * are supplied per-org via the theme provider.
7
+ */
8
+
9
+ export const darkColors = {
10
+ /** App background — the deepest layer. */
11
+ backgroundScreen: '#131313',
12
+ /** Raised surfaces (cards, sheets, modals). */
13
+ shapePrimary: '#1A1A1A',
14
+ /** Inset surfaces, secondary containers, chips. */
15
+ shapeSecondary: '#2D2D2D',
16
+ /** Borders, dividers, disabled outlines. */
17
+ shapeTertiary: '#404040',
18
+ /** Primary text on dark backgrounds. */
19
+ textPrimary: '#FFFFFF',
20
+ /** Secondary / muted text. */
21
+ textSecondary: '#E1E1E1',
22
+ /** Tertiary / placeholder text. */
23
+ textTertiary: '#A0A0A0',
24
+ /** Brand primary — neon lime accent. */
25
+ primaryPure: '#CEFE00',
26
+ /** Brand primary — darker shade for hover/pressed. */
27
+ primaryMid: '#87AD00',
28
+ /** Destructive actions. */
29
+ dangerMid: '#BB4355',
30
+ } as const;
31
+
32
+ export const lightColors = {
33
+ backgroundScreen: '#F6F5F4',
34
+ shapePrimary: '#FAF9F7',
35
+ shapeSecondary: '#E9E8E7',
36
+ shapeTertiary: '#D9D8D7',
37
+ textPrimary: '#141414',
38
+ textSecondary: '#737271',
39
+ textTertiary: '#8E8D8B',
40
+ primaryPure: '#CEFE00',
41
+ primaryMid: '#87AD00',
42
+ dangerMid: '#BB4355',
43
+ } as const;
44
+
45
+ export type ColorToken = keyof typeof darkColors;
46
+
47
+ /** Semantic color set resolved by the active color mode. */
48
+ export type ResolvedColors = typeof darkColors;
49
+
50
+ /** Brand colors supplied per-org. */
51
+ export interface BrandColors {
52
+ primaryPure: string;
53
+ primaryMid: string;
54
+ onPrimary?: string;
55
+ }
@@ -0,0 +1,52 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ /**
4
+ * Nwartz DS — Elevation (shadow) tokens.
5
+ *
6
+ * Each level maps to a named token used across the system. On Android the
7
+ * numeric `elevation` property is used; on iOS/web the shadow properties
8
+ * are applied.
9
+ */
10
+
11
+ interface ElevationEntry {
12
+ elevation?: number;
13
+ shadowColor?: string;
14
+ shadowOffset?: { width: number; height: number };
15
+ shadowOpacity?: number;
16
+ shadowRadius?: number;
17
+ }
18
+
19
+ function makeElevation(
20
+ androidElevation: number,
21
+ iOSShadow: { radius: number; offsetX: number; offsetY: number; opacity: number },
22
+ ): ElevationEntry {
23
+ if (Platform.OS === 'android') {
24
+ return { elevation: androidElevation };
25
+ }
26
+
27
+ return {
28
+ shadowColor: '#101828',
29
+ shadowOffset: { width: iOSShadow.offsetX, height: iOSShadow.offsetY },
30
+ shadowOpacity: iOSShadow.opacity,
31
+ shadowRadius: iOSShadow.radius,
32
+ };
33
+ }
34
+
35
+ export const elevation: Record<string, ElevationEntry> = {
36
+ /** $elevation-level-xs — subtle lift for inline elements. */
37
+ xs: makeElevation(1, { radius: 2, offsetX: 0, offsetY: 1, opacity: 0.05 }),
38
+ /** $elevation-level-sm — cards at rest. */
39
+ sm: makeElevation(2, { radius: 3, offsetX: 0, offsetY: 1, opacity: 0.1 }),
40
+ /** $elevation-level-md — raised panels, dropdowns. */
41
+ md: makeElevation(4, { radius: 8, offsetX: 0, offsetY: 4, opacity: 0.1 }),
42
+ /** $elevation-level-lg — modals, floating sheets. */
43
+ lg: makeElevation(8, { radius: 16, offsetX: 0, offsetY: 12, opacity: 0.08 }),
44
+ /** $elevation-level-xl — bottom sheets, popover menus. */
45
+ xl: makeElevation(12, { radius: 24, offsetX: 0, offsetY: 20, opacity: 0.08 }),
46
+ /** $elevation-level-xxl — full-screen overlays. */
47
+ xxl: makeElevation(16, { radius: 48, offsetX: 0, offsetY: 24, opacity: 0.08 }),
48
+ /** $elevation-level-xxxl — deepest layer. */
49
+ xxxl: makeElevation(24, { radius: 64, offsetX: 0, offsetY: 32, opacity: 0.08 }),
50
+ };
51
+
52
+ export type ElevationToken = keyof typeof elevation;
@@ -0,0 +1,11 @@
1
+ export { darkColors, lightColors } from './colors';
2
+ export type { ColorToken, ResolvedColors, BrandColors } from './colors';
3
+ export { typography } from './typography';
4
+ export type { TypographyToken } from './typography';
5
+ export { elevation } from './elevation';
6
+ export type { ElevationToken } from './elevation';
7
+ export { spacing } from './spacing';
8
+ export type { SpacingToken } from './spacing';
9
+ export { radius } from './radius';
10
+ export type { RadiusToken } from './radius';
11
+ export { colorMap, spacingMap, radiusMap } from './mapping';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Token mapping: bridges the mobile app's legacy color token names to the
3
+ * design system's semantic token keys. Used when migrating mobile components
4
+ * from the old `useTheme()` to `@nwartz/design-system`'s `useTheme()`.
5
+ */
6
+ import type { ResolvedColors } from './colors';
7
+ import type { SpacingToken } from './spacing';
8
+ import type { RadiusToken } from './radius';
9
+
10
+ /** Mobile-app color key → design-system color key. */
11
+ export const colorMap: Record<string, keyof ResolvedColors> = {
12
+ background: 'backgroundScreen',
13
+ backgroundElement: 'shapeSecondary',
14
+ backgroundSelected: 'shapeSecondary',
15
+ surfaceLowest: 'shapePrimary',
16
+ surfaceHigh: 'shapeSecondary',
17
+ outline: 'shapeTertiary',
18
+ outlineVariant: 'shapeTertiary',
19
+ text: 'textPrimary',
20
+ textSecondary: 'textSecondary',
21
+ secondary: 'textSecondary',
22
+ secondaryContainer: 'shapeSecondary',
23
+ onSecondaryContainer: 'textSecondary',
24
+ success: 'primaryPure',
25
+ danger: 'dangerMid',
26
+ info: 'textSecondary',
27
+ warning: 'textSecondary',
28
+ } as const;
29
+
30
+ /** Mobile-app spacing value → design-system spacing token. */
31
+ export const spacingMap: Record<number, SpacingToken> = {
32
+ 2: 'xxs',
33
+ 4: 'xs',
34
+ 8: 'sm',
35
+ 12: 'md',
36
+ 16: 'lg',
37
+ 20: 'xl',
38
+ 24: '2xl',
39
+ 32: '3xl',
40
+ 40: '4xl',
41
+ 48: '5xl',
42
+ 64: '6xl',
43
+ 80: '7xl',
44
+ } as const;
45
+
46
+ /** Mobile-app radius value → design-system radius token. */
47
+ export const radiusMap: Record<number, RadiusToken> = {
48
+ 4: 'xs',
49
+ 8: 'sm',
50
+ 10: 'sm',
51
+ 12: 'md',
52
+ 14: 'md',
53
+ 16: 'lg',
54
+ 24: 'xl',
55
+ 32: 'xl',
56
+ 9999: 'full',
57
+ } as const;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Nwartz DS — Border radius tokens.
3
+ */
4
+ export const radius = {
5
+ /** 4px — tiny elements: badges, tags. */
6
+ xs: 4,
7
+ /** 8px — small controls: inputs, chips. */
8
+ sm: 8,
9
+ /** 12px — medium elements: tiles, list rows. */
10
+ md: 12,
11
+ /** 16px — cards, panels. */
12
+ lg: 16,
13
+ /** 24px — large cards, modals. */
14
+ xl: 24,
15
+ /** Full circle / pill shape. */
16
+ full: 9999,
17
+ } as const;
18
+
19
+ export type RadiusToken = keyof typeof radius;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Nwartz DS — Spacing scale.
3
+ *
4
+ * Based on a 4px base unit. Use these values for padding, margin, and gaps
5
+ * to maintain consistent rhythm across the UI.
6
+ */
7
+ export const spacing = {
8
+ /** 0px */
9
+ none: 0,
10
+ /** 2px — hairline gaps. */
11
+ xxs: 2,
12
+ /** 4px — tight inset (icon-to-label). */
13
+ xs: 4,
14
+ /** 8px — compact gaps, small padding. */
15
+ sm: 8,
16
+ /** 12px — default inner padding for small controls. */
17
+ md: 12,
18
+ /** 16px — standard card padding, list row gaps. */
19
+ lg: 16,
20
+ /** 20px — generous spacing. */
21
+ xl: 20,
22
+ /** 24px — section gaps, card outer padding. */
23
+ '2xl': 24,
24
+ /** 32px — large section spacing. */
25
+ '3xl': 32,
26
+ /** 40px — page-level horizontal padding. */
27
+ '4xl': 40,
28
+ /** 48px — hero spacing. */
29
+ '5xl': 48,
30
+ /** 64px — page-level vertical spacing. */
31
+ '6xl': 64,
32
+ /** 80px — maximum spacing. */
33
+ '7xl': 80,
34
+ } as const;
35
+
36
+ export type SpacingToken = keyof typeof spacing;
@@ -0,0 +1,53 @@
1
+ import { Platform, type TextStyle } from 'react-native';
2
+
3
+ /**
4
+ * Nwartz DS — Typography tokens.
5
+ *
6
+ * Font family references the Archivo typeface. On native, the font must be
7
+ * loaded (e.g. via expo-font) before rendering; the web falls back to the
8
+ * CSS @font-face or Google Fonts import.
9
+ */
10
+ const fontFamilyBase = Platform.select({
11
+ ios: 'Archivo',
12
+ android: 'Archivo',
13
+ default: 'Archivo, system-ui, sans-serif',
14
+ });
15
+
16
+ type FontWeight = TextStyle['fontWeight'];
17
+
18
+ /** Base typography tokens as a plain object (safe to spread into StyleSheet). */
19
+ export const typography: Record<string, TextStyle> = {
20
+ /** 56px / 900 / 1.15 — hero display, stat rings. */
21
+ display: {
22
+ fontFamily: fontFamilyBase,
23
+ fontSize: 56,
24
+ fontWeight: '900' as FontWeight,
25
+ lineHeight: 64,
26
+ },
27
+
28
+ /** 20px / 700 / 1.0 — section headings, card titles. */
29
+ heading: {
30
+ fontFamily: fontFamilyBase,
31
+ fontSize: 20,
32
+ fontWeight: '700' as FontWeight,
33
+ lineHeight: 20,
34
+ },
35
+
36
+ /** 16px / 400 / 1.5 — body copy, list items. */
37
+ body: {
38
+ fontFamily: fontFamilyBase,
39
+ fontSize: 16,
40
+ fontWeight: '400' as FontWeight,
41
+ lineHeight: 24,
42
+ },
43
+
44
+ /** 12px / 400 / 1.5 — captions, helper text, overlines. */
45
+ caption: {
46
+ fontFamily: fontFamilyBase,
47
+ fontSize: 12,
48
+ fontWeight: '400' as FontWeight,
49
+ lineHeight: 18,
50
+ },
51
+ };
52
+
53
+ export type TypographyToken = keyof typeof typography;