npm package: @ronnydrori/jupiter-ui
Description: Jupiter Design System - Plug and play UI components with Mantine and Emotion
Latest version: 1.0.5

# Jupiter UI Design System

A comprehensive design system built with React, TypeScript, Mantine v7, and Storybook. This library provides a complete set of design tokens, themes, and reusable components for building consistent user interfaces.

## 🏗️ Architecture

### Directory Structure

```
libs/jupiter-ui/
├── .storybook/          # Storybook configuration
│   ├── main.ts          # Storybook main config
│   ├── preview.tsx      # Global decorators & theme setup
│   └── preview.css      # Preview styling
├── src/
│   ├── design-system/
│   │   ├── tokens/      # Design tokens (spacing, typography, etc.)
│   │   ├── theme/       # Theme system & providers
│   │   ├── components/  # React components
│   │   ├── hooks/       # Custom React hooks
│   │   └── utils/       # Utility functions
│   └── index.ts         # Main export file
└── package.json
```

## 🛠️ Technology Choices

This design system was built with the following technologies, chosen for their balance of ease of use, customization, and team familiarity:

### UI Foundation: Mantine v7
- **Why Mantine?** Easy integration with 100+ pre-built components, flexible theme API with CSS variables, and 40+ included hooks. Provides good customization without fighting defaults, and excellent TypeScript support.
- **Benefits:** Faster development with ready-to-use components and extensible theming.

### CSS Strategy: Emotion
- **Why Emotion?** Zero learning curve for our CSS-in-JS experienced team. Works seamlessly with Mantine, provides type-safe styling, dynamic theming capabilities, and co-located styles with components.
- **Benefits:** Full JavaScript power for styling, theme access via props, and easy refactoring.

### File Organization: Feature-Based Structure
- **Why Feature-Based?** Components are self-contained in their own folders, making it easy to add, modify, or remove features without affecting others. Scales well for medium-sized component libraries.
- **Benefits:** Intuitive organization, easy maintenance, and clear boundaries between components.

### Icons: Lucide + Phosphor
- **Why These Libraries?** Both provide excellent style control (size, color, stroke), MIT license, and tree-shaking support. Lucide for general UI icons, Phosphor for weight variants when needed.
- **Benefits:** Consistent iconography, small bundle size (~200-250b per icon), and flexible styling.

### Overall Stack Rationale
- **Balanced Approach:** Prioritizes speed of development while maintaining design control and customization.
- **Team Fit:** Leverages existing CSS-in-JS expertise to minimize learning curve.
- **Scalability:** Feature-based organization supports growth from 20-40 components to more.
- **Modern Standards:** TypeScript-first, accessible components, and responsive design.
- **Dynamic Theming:** Supports runtime palette loading via environment variables for flexible theming (e.g., yanai.json, rotem.json).

## � Design Tokens

All design tokens are defined in `src/design-system/tokens/`:

- **Spacing** (`spacing.ts`) - Consistent spacing scale (0.25rem - 20rem)
- **Typography** (`typography.ts`) - Font families, sizes, weights, line heights
- **Radius** (`radius.ts`) - Border radius values (xs, sm, md, lg, xl)
- **Borders** (`borders.ts`) - Border width values
- **Shadows** (`shadows.ts`) - Box shadow definitions (xs, sm, md, lg, xl)
- **Breakpoints** (`breakpoints.ts`) - Responsive breakpoints (xs, sm, md, lg, xl)

### Example Token File: `spacing.ts`

```typescript
import type { TokenScaleKey } from './types';

export const spacing = {
  0: '0px',
  1: '4px',
  2: '8px',
  3: '12px',
  4: '16px',
  5: '20px',
  6: '24px',
  7: '28px',
  8: '32px',
  9: '36px',
} as const;

export const semanticSpacing: Partial<Record<TokenScaleKey, string>> = {
  xs: spacing[1], // 4px
  sm: spacing[2], // 8px
  md: spacing[4], // 16px
  lg: spacing[6], // 24px
  xl: spacing[8], // 32px
} as const;

export type SpacingToken = keyof typeof spacing;
export type SemanticSpacingToken = keyof typeof semanticSpacing;
```

## 🎭 Theme System

### Theme Structure

The theme system is located in `src/design-system/theme/`:

- **types.ts** - TypeScript types for theme structure
- **theme.ts** - Core theme creator using design tokens
- **mantine.ts** - Mantine theme configuration
- **ThemeProvider.tsx** - React context provider for theme management

### Dynamic Palette Loading

The design system supports dynamic color palette loading at runtime:

- **usePalette Hook**: Loads color palettes asynchronously from JSON files
- **Environment Variable**: Set `VITE_PROJECT_NAME` to specify the project name (e.g., `yanai` or `rotem`)
- **Palette Path**: Fetches from `/color-palettes/${VITE_PROJECT_NAME}.json`
- **Fallback**: Defaults to `defaultPalette.ts` if no environment variable is set or loading fails

```typescript
// Example: Loading a palette
const colors = usePalette(); // Loads from /color-palettes/${VITE_PROJECT_NAME}.json or defaults to defaultPalette
```

### Using Themes

```typescript
import { JupiterThemeProvider } from '@libraries/jupiter-ui';

function App() {
  return (
    <JupiterThemeProvider>
      {/* Your app */}
    </JupiterThemeProvider>
  );
}
```

### Theme Context Hook

```typescript
import { useThemeContext } from '@libraries/jupiter-ui';

function MyComponent() {
  const { mode, toggleMode, setMode } = useThemeContext();
  // mode: 'light' | 'dark'
  // toggleMode: () => void
  // setMode: (mode: ThemeMode) => void
}
```

## 🧩 Components

### Component Guidelines

All components are located in `src/design-system/components/[ComponentName]/`:

1. **No index.ts files** - Import directly from component files
2. **Component file**: `ComponentName.tsx`
3. **Test file**: `ComponentName.test.tsx` - **REQUIRED** (enforced by ESLint)
4. **Stories file**: `ComponentName.stories.tsx`
5. **Export from main index**: Update `src/index.ts`
6. **Set `displayName`** on exported React components and higher-order components (e.g., `ComponentName.displayName = 'ComponentName'`; internal helper components don't need this)

> ⚠️ **Testing Requirement**: Every component must have a corresponding test file. This is enforced by ESLint's `require-test-file` rule and will prevent commits without tests.

### Component Structure

```typescript
// JpButton.tsx
import { Button as MantineButton } from '@mantine/core';
import type { ButtonProps as MantineButtonProps } from '@mantine/core';
import { buttonVariants, type JupiterVariant } from '../utils/variant-factory';

export interface JpButtonProps extends Omit<MantineButtonProps, 'variant'> {
  variant?: JupiterVariant;
}

export function JpButton({ variant = 'primary', ...props }: JpButtonProps) {
  return (
    <MantineButton
      variant={buttonVariants[variant] ?? buttonVariants.primary}
      {...props}
    />
  );
}

// Required: set the display name for better DX in React DevTools, Storybook, and logs
JpButton.displayName = 'JpButton';
```

### Custom Styling with Emotion (Function Syntax)

Always use Emotion's function form for `styled` components, not tagged template strings. This ensures better type safety, prop filtering, and consistent patterns across the library.

```typescript
import styled from '@emotion/styled';
// Optional: filter invalid DOM props when styling host elements
// import isPropValid from '@emotion/is-prop-valid';
import { JpButton, useThemeContext } from '@libraries/jupiter-ui';

// Create a styled button with custom styles (function approach)
const StyledButton = styled(JpButton, { label: 'styled-button' })(() => ({
  backgroundColor: 'hotpink',
  color: 'white',
  '&:hover': {
    backgroundColor: 'deeppink',
  },
  // Use static selectors to style inner elements
  '& .mantine-Button-label': {
    fontWeight: 'bold',
    textTransform: 'uppercase',
  },
}));

// Create a themed button using design tokens (function approach)
const ThemedButton = styled(JpButton, { label: 'themed-button' })(({ theme }) => ({
  backgroundColor: '#3b82f6',
  color: 'white',
  borderRadius: theme?.radius?.md || '0.5rem',
  padding: `${theme?.spacing?.md || '0.75rem'} ${theme?.spacing?.lg || '1rem'}`,
  fontWeight: theme?.typography?.fontWeights?.semibold || 600,
  '&:hover': {
    backgroundColor: '#2563eb',
    transform: 'translateY(-1px)',
    boxShadow: theme?.shadows?.md || '0 4px 6px rgba(0,0,0,0.1)',
  },
  // Style inner elements
  '& .mantine-Button-label': {
    fontFamily: theme?.typography?.fontFamilies?.body || 'system-ui',
  },
}));


function Demo() {
  return (
    <div>
      <StyledButton>Styled with Emotion</StyledButton>
      <ThemedButton>
        Themed Button
      </ThemedButton>
    </div>
  );
}
```

**Key Points**:
- Use Emotion's function syntax: `styled(Component, options)(props => styles)`
- Do not use tagged template strings (e.g., ``styled.div`...``, ``styled(Button)`...``)
- Use Mantine's class selectors (e.g., `.mantine-Button-label`) to target inner elements
- Access theme tokens via `props.theme` (pass theme from `useThemeContext()`)
- Use transient props (`$propName`) to avoid passing custom props to DOM
- All Mantine component props still work on styled components
- Styled components can be used in Storybook stories

### Storybook Stories

```typescript
// ComponentName.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';

const meta = {
  title: 'Components/ComponentName',
  component: ComponentName,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline', 'ghost'],
    },
  },
} satisfies Meta<typeof ComponentName>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Component Text',
  },
};
```

## 📚 Storybook

### Configuration

- **Version**: v8.6.15
- **Framework**: React + Vite
- **Addons**:
  - `storybook-addon-mantine` - Mantine theme integration
  - Standard Storybook addons (essentials, interactions, etc.)



### Running Storybook

```bash
npm run storybook
```

Storybook will be available at http://localhost:6006

## 🔧 Technology Stack

- **React** v18.3.1
- **TypeScript** v5.5.4 (strict mode)
- **Mantine** v7.15.1 (UI component library)
- **Emotion** v11.13.5 (CSS-in-JS styling)
- **Storybook** v8.6.14
- **Vite** v5.4.11 (build tool)
- **Vitest** v3.2.4 (testing framework)
- **Lucide + Phosphor** (icons)

## 📝 Coding Conventions

### Imports

- Use named exports (not default exports except for Storybook meta)
- Import components directly: `import { Button } from './components/Button/Button'`
- No intermediate index files in component folders

### TypeScript

- Use explicit types for all public APIs
- Export interface definitions alongside components
- Use `type` for type aliases, `interface` for object shapes
- No `any` types allowed

### File Naming

- Components: PascalCase (e.g., `Button.tsx`)
- Stories: `ComponentName.stories.tsx`
- Utils/Tokens: camelCase (e.g., `colors.ts`)

### Component Variants

Use semantic variant names that describe intent, not implementation:
- ✅ `primary`, `secondary`, `outline`, `ghost`
- ❌ `filled`, `light`, `subtle` (Mantine internal variants)

Map semantic variants to Mantine variants inside components.

## 🎯 Best Practices

### Theme Provider

- Always wrap components in `JupiterThemeProvider` when using `useThemeContext()`
- ThemeProvider provides theme context and token access to all components

### Mantine Integration

- Extend Mantine components, don't replace them
- Override Mantine's theme through `createAppTheme()` function
- Use Mantine's utility functions (e.g., `rem()`, `em()`)

### State Management

- Avoid `useEffect` for syncing props to state
- Use render-time state updates: `if (prop !== state) setState(prop)`
- This prevents cascading renders and React act() warnings

## 🚀 Development Workflow

1. **Add new tokens** in `src/design-system/tokens/`
2. **Update theme** in `src/design-system/theme/` if needed
3. **Create component** in `src/design-system/components/[Name]/`
4. **Add stories** in same folder as component
5. **Export from main** in `src/index.ts`
6. **Test in Storybook** with `npm run storybook`
7. **Commit** using conventional commits format

## 📦 Export Pattern

All exports go through `src/index.ts`:

```typescript
// Tokens
export * from './design-system/tokens/spacing';
export * from './design-system/tokens/radius';
export * from './design-system/tokens/borders';
export * from './design-system/tokens/typography';
export * from './design-system/tokens/shadows';
export * from './design-system/tokens/breakpoints';

// Theme
export * from './design-system/theme/types';
export * from './design-system/theme/mantine';
export {
  JupiterThemeProvider,
  useThemeContext,
} from './design-system/theme/ThemeProvider';
export type { JupiterThemeProviderProps } from './design-system/theme/ThemeProvider';

// Utils
export {
  buttonVariants,
  inputVariants,
  selectVariants,
  checkboxVariants,
} from './design-system/utils/variant-factory';
export type { JupiterVariant } from './design-system/utils/variant-factory';

// Components
export { JpButton } from './design-system/components/Button/JpButton';
export type { JpButtonProps } from './design-system/components/Button/JpButton';

// Hooks
export * from './design-system/hooks/usePalette';
```

##  Scripts

```bash
# Development
npm run storybook          # Start Storybook on localhost:6006
npm run build-storybook    # Build static Storybook

# Linting
npm run lint              # Run ESLint
npm run lint:fix          # Fix ESLint issues

# Type Checking
npm run typecheck         # Run TypeScript compiler check

# Testing
npm run test              # Run unit tests with Vitest
npm run test-storybook    # Run storybook tests in the cli
```
### 📖 Official Documentation

- **Storybook Interaction Tests**  
  https://storybook.js.org/docs/writing-tests/interaction-testing

- **Storybook Vitest Addon**  
  https://storybook.js.org/docs/8/writing-tests/test-addon

## 🔒 ESLint Rules

This library enforces strict rules to maintain quality and plug-and-play architecture:

### TypeScript Rules
- ❌ `@typescript-eslint/no-explicit-any`: No `any` types allowed
- ✅ `@typescript-eslint/consistent-type-imports`: Prefer type imports for better tree-shaking
- ✅ `@typescript-eslint/prefer-nullish-coalescing`: Use `??` over `||` for null/undefined checks
- ✅ `@typescript-eslint/prefer-optional-chain`: Use optional chaining (`?.`) instead of manual checks
- ⚠️ `@typescript-eslint/no-non-null-assertion`: Warn on non-null assertions (`!`)

### Import Restrictions
- ❌ No imports from other monorepo libraries (`@libraries/*` except `@libraries/jupiter-ui`)
- ❌ No imports from apps (`apps/*`)
- ❌ No imports from other libs (`libs/*`)
- ❌ No relative imports outside `src/` directory
- ✅ Only allowed external dependencies: `@mantine/*`, `@emotion/*`, `@phosphor-icons/react`, `lucide-react`, `vitest`, `react`, `react-dom`, `@testing-library/*`, `@storybook/*`, `@types/*`

### Component Requirements
- ✅ Every component must have a corresponding `.test.tsx` file (enforced by `test-file/require-test-file` rule)
- ✅ Set `displayName` on exported React components for better DX in DevTools and logs

### React Rules
- ✅ All React recommended rules
- ✅ React Hooks recommended rules
- ✅ JSX runtime rules

### Other
- ✅ Fully type-safe
- ✅ Plug-and-play architecture

## 🤝 Contributing

When adding components:

1. Create component folder in `src/design-system/components/[Name]/`
2. Create `Name.tsx` with component implementation
3. Create `Name.stories.tsx` with comprehensive stories
4. Export component and types from `src/index.ts`
5. Ensure no external monorepo dependencies
6. Follow semantic variant naming
7. Add proper TypeScript types

## 📖 Additional Resources

- [Mantine Documentation](https://mantine.dev/)
- [Storybook Documentation](https://storybook.js.org/)