Created
July 28, 2025 19:21
-
-
Save theklr/19c04e107c97c259f51fab2004828838 to your computer and use it in GitHub Desktop.
Revisions
-
theklr created this gist
Jul 28, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.