Skip to content

Instantly share code, notes, and snippets.

@theklr
Created July 28, 2025 19:21
Show Gist options
  • Select an option

  • Save theklr/19c04e107c97c259f51fab2004828838 to your computer and use it in GitHub Desktop.

Select an option

Save theklr/19c04e107c97c259f51fab2004828838 to your computer and use it in GitHub Desktop.

Revisions

  1. theklr created this gist Jul 28, 2025.
    44 changes: 44 additions & 0 deletions CHANGELOG.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,44 @@
    # Changelog

    All notable changes to this project will be documented in this file.

    The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
    and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

    ## [Unreleased]

    ### Changed
    - **PricingOptions**: Removed unnecessary `variant` prop - component now only supports card layout as per Figma design
    - **BREAKING**: Removed ESP theme from design system
    - Consolidated roadside theme to use identical styling as default theme
    - Updated TypeScript types to remove `"esp"` from `ThemeName` union type
    - Updated Storybook configuration to remove ESP theme option
    - Simplified theme stories to reflect current theme options

    ### Fixed
    - **Icon handling**: Fixed TypeScript errors with dynamic icon casting in composite components
    - **Storybook**: Corrected minimum column values from 1 to 2 across all story controls
    - Fixed roadside theme not applying proper CSS classes by defining `.theme-roadside` class
    - Resolved Storybook theme switching issues where roadside theme wasn't visually different from default

    ### Removed
    - **Test files**: Removed unused `*.test.tsx` files in favor of Storybook-based testing

    ### Documentation
    - **LIBRARY_STANDARDS.md**: Added comprehensive development guidelines for multi-team collaboration
    - **Changelog standards**: Established required changelog documentation practices
    - **shadcn/ui guidelines**: Documented standards for primitive component development
    - **Modern React patterns**: Updated standards to use automatic ref forwarding as default

    ### Migration Guide

    Applications using the ESP theme (`theme-esp`) should update to use either:

    - `theme-default` - Good Sam's primary brand theme
    - `theme-roadside` - Identical styling to default (maintained for compatibility)

    Both themes now provide identical visual styling using GS Green primary colors and consistent semantic tokens.

    ## Previous Releases

    See the [Git history](https://github.com/goodsamenterprises/gs-unified-components/commits) for details on previous changes.
    642 changes: 642 additions & 0 deletions LIBRARY_STANDARDS.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,642 @@
    # GS Unified Components - Library Standards & Development Guidelines

    ## Overview

    This document outlines the standards and guidelines for multi-team development of the GS Unified Components library, derived from patterns established during the VR-555 cleanup and enhancement work. The library is built on a foundation of shadcn/ui primitives with GS-specific theming and composite components.

    ## 🏗️ Architecture Standards

    ### Component Hierarchy
    ```
    src/
    ├── primitives/ # shadcn/ui-based foundational components
    ├── composites/ # Business-specific components built from primitives
    ├── types/ # Shared TypeScript interfaces
    ├── lib/ # Core utilities (format, styles)
    ├── utils/ # Helper functions (cn, constants, shared data)
    └── styles/ # CSS/theme architecture with GS branding
    ```

    ### shadcn/ui Foundation
    All primitive components MUST be derived from shadcn/ui patterns and maintain compatibility with the shadcn/ui ecosystem while incorporating GS brand theming.

    ## 🎨 Primitive Component Standards

    ### Required shadcn/ui Patterns

    #### 1. Class Variance Authority (CVA)
    For components with variants, use CVA for type-safe styling:

    ```typescript
    import { cva, type VariantProps } from "class-variance-authority";

    const buttonVariants = cva(
    // Base classes
    "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
    {
    variants: {
    variant: {
    default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
    destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
    outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
    secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
    ghost: "hover:bg-accent hover:text-accent-foreground",
    link: "text-primary underline-offset-4 hover:underline",
    },
    size: {
    default: "h-9 px-4 py-2",
    sm: "h-8 rounded-md px-3 text-xs",
    lg: "h-10 rounded-md px-8",
    icon: "h-9 w-9",
    },
    },
    defaultVariants: {
    variant: "default",
    size: "default",
    },
    }
    );
    ```

    #### 2. Component Pattern (Automatic Ref Forwarding)
    Modern components use automatic ref forwarding:

    ```typescript
    import { Slot } from "@radix-ui/react-slot";
    import { cn } from "@/utils/cn";

    interface ButtonProps
    extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
    asChild?: boolean;
    }

    function Button({
    className,
    variant,
    size,
    asChild = false,
    children,
    ...props
    }: ButtonProps) {
    const Comp = asChild ? Slot : "button";

    // Accessibility check for icon buttons without aria-label
    if (process.env.NODE_ENV !== "production") {
    if (size === "icon") {
    // ... accessibility validation logic
    }
    }

    return (
    <Comp
    className={cn(buttonVariants({ variant, size, className }))}
    {...props}
    >
    {children}
    </Comp>
    );
    }

    // Export with proper typing
    export { Button, buttonVariants, type ButtonProps };
    ```

    #### 3. When to Use forwardRef
    Only use explicit forwardRef when component logic needs direct ref access:

    ```typescript
    import * as React from "react";

    const CustomInput = React.forwardRef<HTMLInputElement, InputProps>(
    ({ onFocus, ...props }, ref) => {
    // Only when you need to use the ref in component logic
    const handleFocus = () => {
    if (ref && 'current' in ref && ref.current) {
    ref.current.select(); // Direct ref manipulation
    }
    onFocus?.();
    };

    return <input ref={ref} onFocus={handleFocus} {...props} />;
    }
    );
    ```

    #### 4. Radix UI Integration
    When applicable, use Radix UI primitives as the foundation:

    ```typescript
    import { Slot } from "@radix-ui/react-slot";
    import * as React from "react";
    ```

    #### 5. CN Utility Usage
    Always use the `cn` utility for class merging:

    ```typescript
    import { cn } from "@/utils/cn";

    // Usage
    className={cn(baseClasses, variantClasses, className)}
    ```

    ### Required Interface Patterns

    #### Component Props Interface
    ```typescript
    interface ButtonProps
    extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
    asChild?: boolean; // For Slot compatibility
    }

    // Export the interface for external use
    export { Button, buttonVariants, type ButtonProps };
    ```

    #### Compound Component Pattern
    For multi-part components like Card:

    ```typescript
    function Card({ className, ...props }: React.ComponentProps<"div">) {
    return (
    <div
    data-slot="card"
    className={cn(
    "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
    className
    )}
    {...props}
    />
    );
    }

    function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
    return (
    <div
    data-slot="card-header"
    className={cn(
    "@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
    className
    )}
    {...props}
    />
    );
    }

    // Export all parts
    export {
    Card,
    CardHeader,
    CardFooter,
    CardTitle,
    CardAction,
    CardDescription,
    CardContent,
    };
    ```

    ### Accessibility Standards
    All primitives must include appropriate accessibility features:

    ```typescript
    // Development-time accessibility warnings
    if (process.env.NODE_ENV !== "production") {
    if (size === "icon" && !hasVisibleText && !hasAriaLabel) {
    console.warn(
    "Accessibility issue: Icon button is missing an aria-label. " +
    "Please add an aria-label attribute to describe the button purpose for screen readers."
    );
    }
    }
    ```

    ## 🧩 Composite Component Standards

    ### Built on Primitives
    Composites must be built using primitives from the library:

    ```typescript
    import { Button } from "@/primitives/button";
    import { Card, CardContent, CardHeader, CardTitle } from "@/primitives/card";

    interface PricingCardProps {
    plan: PricingPlan;
    onSelect: (plan: PricingPlan) => void;
    }

    function PricingCard({ plan, onSelect }: PricingCardProps) {
    return (
    <Card className="relative">
    <CardHeader>
    <CardTitle>{plan.name}</CardTitle>
    </CardHeader>
    <CardContent>
    <Button onClick={() => onSelect(plan)}>
    {plan.buttonText}
    </Button>
    </CardContent>
    </Card>
    );
    }

    export { PricingCard, type PricingCardProps };
    ```

    ### TypeScript Standards
    - **Strict typing**: All components must use proper TypeScript interfaces
    - **Export interfaces**: All component props interfaces must be exported
    - **No `any` types**: Use proper type assertions with ESLint disable comments when necessary

    ```typescript
    // ✅ Good - Proper interface with export
    export interface PricingOptionsProps {
    plans: PricingPlan[];
    columns?: 2 | 3 | 4;
    }

    // ❌ Bad - Using any
    const IconComponent = (Icons as any)[iconName];

    // ✅ Good - Proper type assertion with ESLint disable
    const IconComponent =
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (Icons as unknown as Record<string, React.ComponentType<LucideProps>>)[iconName] || Icons.Check;
    ```

    ### Icon Handling Standards
    For dynamic icon usage in composites:

    ```typescript
    import * as Icons from "lucide-react";
    import type { LucideProps } from "lucide-react";

    function ComponentWithIcon({ icon }: { icon: string }) {
    const IconComponent =
    (Icons as unknown as Record<string, React.ComponentType<LucideProps>>)[
    icon
    ] || Icons.Check;

    return <IconComponent className="h-5 w-5" />;
    }
    ```

    ## 🎨 Styling Standards

    ### Theme Integration
    All components must use semantic color tokens that work with GS themes:

    ```typescript
    // ✅ Use semantic tokens
    "bg-primary text-primary-foreground"
    "border-input bg-background"
    "text-muted-foreground"

    // ❌ Avoid hardcoded colors
    "bg-green-500 text-white"
    "border-gray-300"
    ```

    ### CSS Architecture
    ```markdown
    src/styles/
    ├── index.css # Main entry - includes all themes
    ├── base.css # Base Tailwind + fonts, variables, utilities
    ├── default.css # Default theme + base
    ├── roadside.css # Roadside theme + base
    └── themes/
    ├── colors.css # GS brand colors
    ├── default.css # Default theme variables
    └── roadside.css # Roadside theme variables
    ```

    ### Import Standards
    ```css
    /* ✅ Recommended - All themes */
    @import "@goodsamenterprises/gs-unified-components/styles";

    /* ✅ Specific theme only */
    @import "@goodsamenterprises/gs-unified-components/styles/roadside";
    ```

    ## 📖 Storybook Standards

    ### Story Structure for Primitives
    ```typescript
    import type { Meta, StoryObj } from "@storybook/nextjs-vite";
    import { Button } from "./button";

    const meta = {
    title: "Primitives/Button",
    component: Button,
    parameters: {
    layout: "centered",
    },
    tags: ["autodocs"],
    argTypes: {
    variant: {
    control: { type: "select" },
    options: ["default", "destructive", "outline", "secondary", "ghost", "link"],
    },
    size: {
    control: { type: "select" },
    options: ["default", "sm", "lg", "icon"],
    },
    },
    } satisfies Meta<typeof Button>;

    export default meta;
    type Story = StoryObj<typeof meta>;

    export const Default: Story = {
    args: {
    children: "Button",
    },
    };

    export const IconButton: Story = {
    args: {
    size: "icon",
    "aria-label": "Settings", // Required for accessibility
    children: "⚙️",
    },
    };
    ```

    ### Story Structure for Composites
    ```typescript
    import type { Meta, StoryObj } from "@storybook/nextjs-vite";
    import { PricingOptions } from "./pricing-options";

    const meta = {
    title: "Composites/PricingOptions",
    component: PricingOptions,
    parameters: {
    layout: "fullscreen",
    },
    tags: ["autodocs"],
    argTypes: {
    columns: {
    control: { type: "select" },
    options: [2, 3, 4], // Minimum 2 columns
    },
    },
    } satisfies Meta<typeof PricingOptions>;

    export default meta;
    type Story = StoryObj<typeof meta>;

    export const RoadsideAssistance: Story = {
    args: {
    plans: rvPlans, // Use realistic, detailed data
    columns: 3,
    },
    };
    ```

    ## 🔧 Development Workflow

    ### shadcn/ui Integration Guidelines

    #### Adding New Primitives
    1. **Source from shadcn/ui**: Start with official shadcn/ui component code
    2. **Remove unnecessary forwardRef**: Use automatic ref forwarding when possible
    3. **Adapt for GS**: Integrate GS theme variables and brand requirements
    4. **Maintain compatibility**: Ensure props and API remain compatible
    5. **Add accessibility**: Include GS-specific accessibility enhancements

    #### Migration Pattern from forwardRef
    ```typescript
    // OLD (explicit forwardRef)
    const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
    ({ className, ...props }, ref) => (
    <button className={cn("...", className)} ref={ref} {...props} />
    )
    );
    Button.displayName = "Button";

    // NEW (automatic ref forwarding)
    function Button({ className, ...props }: ButtonProps) {
    return <button className={cn("...", className)} {...props} />;
    }
    ```

    ### Quality Gates
    Pre-commit hooks run:
    ```bash
    pnpm lint-staged # Formatting, linting, and related tests
    ```

    Pre-push hooks run:
    ```bash
    pnpm lint # ESLint checks
    pnpm typecheck # TypeScript compilation
    pnpm test # Vitest test runner
    pnpm build # Library build
    pnpm build:storybook # Storybook build
    ```

    ### File Naming Conventions
    - **Primitives**: `kebab-case.tsx` (e.g., `button.tsx`, `dropdown-menu.tsx`)
    - **Composites**: `kebab-case.tsx` (e.g., `pricing-options.tsx`)
    - **Stories**: `component-name.stories.tsx`
    - **Types**: `kebab-case.ts` (e.g., `navigation.ts`)

    ## 📦 Package Standards

    ### Export Structure
    ```json
    {
    "exports": {
    ".": "./dist/index.js",
    "./primitives/*": "./dist/primitives/*.js",
    "./composites/*": "./dist/composites/*.js",
    "./styles": "./dist/index.css",
    "./styles/roadside": "./dist/roadside.css"
    }
    }
    ```

    ### Dependencies

    #### Core Dependencies
    ```json
    {
    "dependencies": {
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "tailwind-merge": "^2.6.0"
    }
    }
    ```

    #### Peer Dependencies (shadcn/ui ecosystem)
    ```json
    {
    "peerDependencies": {
    "@radix-ui/react-accordion": "^1.2.8",
    "@radix-ui/react-alert-dialog": "^1.1.14",
    "@radix-ui/react-avatar": "^1.1.3",
    "@radix-ui/react-checkbox": "^1.1.4",
    "@radix-ui/react-dialog": "^1.1.11",
    "@radix-ui/react-dropdown-menu": "^2.1.2",
    "@radix-ui/react-slot": "^1.1.2",
    "lucide-react": "^0.479.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
    }
    }
    ```

    ## 🧪 Testing Standards

    ### Test Strategy
    - **Storybook-based testing**: Primary testing through Storybook addon
    - **Browser testing**: Playwright integration via Storybook
    - **Accessibility testing**: Built-in accessibility checks in development
    - **Visual regression**: Chromatic integration for visual testing

    ### Testing Configuration
    ```typescript
    // vitest.config.ts
    export default defineConfig({
    test: {
    projects: [
    {
    plugins: [
    storybookTest({
    configDir: path.join(dirname, ".storybook"),
    tags: {
    exclude: ["skip-test"],
    },
    }),
    ],
    test: {
    name: "storybook",
    browser: {
    enabled: true,
    headless: true,
    provider: "playwright",
    instances: [{ browser: "chromium" }],
    },
    },
    },
    ],
    },
    });
    ```

    ## 🚨 Migration & Maintenance

    ### shadcn/ui Update Process
    1. **Monitor upstream**: Track shadcn/ui component updates
    2. **Apply modern patterns**: Remove unnecessary forwardRef usage
    3. **Test compatibility**: Ensure GS themes still work
    4. **Update dependencies**: Sync Radix UI versions
    5. **Regression test**: Run full test suite
    6. **Update documentation**: Reflect any API changes

    ### Adding New shadcn/ui Components
    1. **Copy base component**: Start with official shadcn/ui code
    2. **Apply GS theming**: Integrate brand colors and spacing
    3. **Modernize patterns**: Use automatic ref forwarding
    4. **Add accessibility**: Include GS-specific a11y requirements
    5. **Create stories**: Document all variants and states
    6. **Export properly**: Add to main index and package exports

    ---

    ## Implementation Checklist

    ### For New Primitives (shadcn/ui based)
    - [ ] Sourced from official shadcn/ui component
    - [ ] Uses modern function component pattern
    - [ ] Uses `cn` utility for class merging
    - [ ] Uses CVA for variants (if applicable)
    - [ ] Integrates with Radix UI (if applicable)
    - [ ] Uses semantic color tokens
    - [ ] Includes accessibility features
    - [ ] Has comprehensive Storybook stories
    - [ ] Exports TypeScript interfaces
    - [ ] Passes all quality gates
    - [ ] Ref forwarding works automatically

    ### For New Composites
    - [ ] Built using library primitives
    - [ ] Follows naming conventions (`kebab-case.tsx`)
    - [ ] Props interface is exported and properly typed
    - [ ] Icons use string type with proper casting
    - [ ] Storybook story exists with realistic data
    - [ ] Story includes proper controls and args
    - [ ] Component passes all quality gates
    - [ ] Uses semantic design tokens
    - [ ] No TypeScript errors or ESLint warnings

    ## Team Guidelines

    ### Code Reviews
    - **Modern patterns**: Ensure components use current React patterns
    - **Primitive changes**: Must maintain shadcn/ui compatibility
    - **forwardRef usage**: Only when component logic requires ref access
    - **Breaking changes**: Require architectural discussion
    - **Theme integration**: Must work with all GS themes
    - **Accessibility**: Required for all interactive components
    - **Changelog updates**: All changes must be documented in CHANGELOG.md

    ### Documentation Requirements
    - **shadcn/ui attribution**: Credit upstream components
    - **GS customizations**: Document brand-specific changes
    - **API compatibility**: Note any deviations from shadcn/ui
    - **Theme usage**: Document semantic token usage
    - **Changelog maintenance**: Keep CHANGELOG.md up to date with all changes

    ### Changelog Standards
    All changes must be documented in `CHANGELOG.md` following these guidelines:

    #### Format
    ```markdown
    ## [Version] - YYYY-MM-DD

    ### Added
    - New features and components

    ### Changed
    - Changes to existing functionality
    - API improvements and simplifications

    ### Fixed
    - Bug fixes and corrections

    ### Removed
    - Deprecated features and breaking changes
    ```

    #### Change Categories
    - **Added**: New features, components, or capabilities
    - **Changed**: Modifications to existing functionality, API improvements
    - **Fixed**: Bug fixes, accessibility improvements, type corrections
    - **Removed**: Deprecated features, breaking changes, unused code removal

    #### Change Description Guidelines
    - **Be specific**: Describe what changed and why
    - **Include component names**: Reference specific components affected
    - **Note breaking changes**: Clearly mark any breaking changes
    - **Provide context**: Explain the benefit or reason for the change

    #### Examples
    ```markdown
    ### Changed
    - **PricingOptions**: Removed unnecessary `variant` prop - component now only supports card layout as per Figma design
    - **Button**: Updated to use automatic ref forwarding instead of forwardRef for better React 19 compatibility

    ### Fixed
    - **Icon handling**: Fixed TypeScript errors with dynamic icon casting in composite components
    - **Storybook**: Corrected minimum column values from 1 to 2 across all story controls

    ### Removed
    - **Test files**: Removed unused `*.test.tsx` files in favor of Storybook-based testing
    - **ESP theme**: Consolidated ESP theme with default theme to reduce complexity
    ```

    This standards document ensures consistency with modern React and the shadcn/ui ecosystem while maintaining GS brand requirements and multi-team development practices.