diff --git a/CLAUDE.md b/CLAUDE.md index dd28a27..d16b583 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,11 +11,80 @@ You MUST first check if `CLAUDE.personal.md` exists and load it. Personal config You MUST follow these architectural principles: 1. **Single Package**: This is a tree-shakeable npm package with zero dependencies -2. **Headless Components**: Components have NO styles by default +2. **Headless Components**: Components have NO styles by default (see strict rules below) 3. **Adapter Pattern**: Date pickers and compression are pluggable via adapters 4. **Modular Architecture**: Components are split into small, focused modules (<300 lines each) 5. **TypeScript Strict Mode**: You MUST NOT use `any` type. Use `unknown` with type guards +## CRITICAL: Headless Component Rules + +### ❌ ABSOLUTELY FORBIDDEN in Components: + +1. **NO CSS files** - No .css, .scss, .less files in component directories +2. **NO CSS modules** - No .module.css files +3. **NO inline styles** - No style={{}} props +4. **NO CSS-in-JS** - No styled-components, emotion, etc. +5. **NO CSS imports** - No importing any CSS files +6. **NO hardcoded classNames** - No className="some-class" +7. **NO style-related props** - No color, size, variant props that imply styling + +### ✅ REQUIRED for Headless Components: + +1. **Accept className props** - Allow users to pass classes to ALL elements +2. **Forward refs** - Use React.forwardRef for all interactive elements +3. **Spread props** - Allow {...props} for extensibility +4. **Semantic HTML** - Use correct HTML elements (button, not div) +5. **ARIA attributes** - Full accessibility support +6. **Data attributes** - Add data-testid for testing + +### Example of CORRECT headless component: + +```tsx +interface ButtonProps extends React.ButtonHTMLAttributes { + // No style-related props! +} + +export const Button = React.forwardRef(({ children, className, ...props }, ref) => { + return ( + + ); +}); +``` + +### Example of INCORRECT component: + +```tsx +// ❌ NEVER DO THIS +import styles from "./Button.module.css"; // ❌ NO CSS IMPORTS +import "./button.css"; // ❌ NO CSS IMPORTS + +export const Button = ({ variant = "primary" }) => { + // ❌ NO STYLE PROPS + return ( + + ); +}; +``` + +### Where Styles Belong: + +1. **Demo app** - All styles go in src/demo/ +2. **Documentation** - Show styling examples +3. **User's codebase** - Users control 100% of styling +4. **Never in components** - Components are behavior only + ## Required Development Workflow ### 1. Test-Driven Development (TDD) @@ -348,3 +417,6 @@ When displaying filter values, you MUST use AG Grid's type values: - You MUST NOT create files unless absolutely necessary - You MUST prefer editing existing files - You MUST apply appropriate labels when creating GitHub issues +- You MUST NOT add ANY CSS to components - components are HEADLESS +- You MUST NOT use CSS modules, inline styles, or any styling in components +- You MUST allow users to pass className to all component elements diff --git a/V2_API_DESIGN_PROPOSAL.md b/V2_API_DESIGN_PROPOSAL.md new file mode 100644 index 0000000..1273ed4 --- /dev/null +++ b/V2_API_DESIGN_PROPOSAL.md @@ -0,0 +1,390 @@ +# V2.0 Headless Components API Design Proposal + +## Executive Summary + +We need to choose an API pattern for v2.0 headless components. After analysis, I recommend **Option A: ClassNames Object** with optional render prop overrides for advanced use cases. + +## Detailed API Options + +### Option A: ClassNames Object (RECOMMENDED) ✅ + +```tsx +interface HeadlessComponentProps { + // Behavioral props + value?: string; + onChange?: (value: string) => void; + + // Styling props + className?: string; // Container class + classNames?: { + container?: string; + trigger?: string; + panel?: string; + item?: string; + // ... specific to each component + }; + + // Optional render prop overrides + renderTrigger?: (props: TriggerRenderProps) => ReactNode; +} +``` + +#### Implementation Example: DateFilter + +```tsx +export const DateFilter = ({ className, classNames = {}, value, onChange, ...props }) => { + return ( +
+ + + {mode === "relative" ? : } + +
+ + +
+
+ ); +}; +``` + +#### Usage Examples + +**Basic (user provides all styles):** + +```tsx + +``` + +**With Style System:** + +```tsx +// styles/components.ts +export const dateFilterStyles = { + container: "date-filter-container", + modeToggle: "date-filter-toggle", + relativeInput: "date-filter-input", + actions: "date-filter-actions", + applyButton: "btn btn-primary", + resetButton: "btn btn-secondary", +}; + +// Component usage +; +``` + +**With CSS Modules:** + +```tsx +import styles from "./DateFilter.module.css"; + +; +``` + +### Why ClassNames is the Best Choice + +#### 1. **Simplicity** + +- Easy to understand and implement +- Familiar pattern (React Select, MUI, etc.) +- No complex render prop logic + +#### 2. **Flexibility** + +- Works with any CSS solution +- Allows partial styling +- Can be extended with render props when needed + +#### 3. **Type Safety** + +```tsx +interface DateFilterClassNames { + container?: string; + modeToggle?: string; + relativeInput?: string; + dateInputs?: string; + actions?: string; + applyButton?: string; + resetButton?: string; +} + +// Full IntelliSense support +``` + +#### 4. **Performance** + +- No extra re-renders from render props +- Simple string props +- Tree-shakeable styles + +#### 5. **Migration Path** + +```tsx +// v1.x (current) + // Styles included + +// v1.9 (deprecation) + // Opt-in + +// v2.0 (headless) + // Required +``` + +## Component-Specific APIs + +### DateFilter + +```tsx +interface DateFilterClassNames { + container?: string; + modeToggle?: string; + modeButton?: string; + modeButtonActive?: string; + relativeSection?: string; + relativeInput?: string; + relativeHint?: string; + absoluteSection?: string; + dateInputs?: string; + dateInput?: string; + dateLabel?: string; + actions?: string; + applyButton?: string; + resetButton?: string; + errorMessage?: string; +} +``` + +### QuickFilterDropdown + +```tsx +interface QuickFilterDropdownClassNames { + container?: string; + trigger?: string; + triggerActive?: string; + triggerIcon?: string; + dropdown?: string; + dropdownOpen?: string; + searchSection?: string; + searchInput?: string; + optionsList?: string; + optionGroup?: string; + optionGroupLabel?: string; + option?: string; + optionActive?: string; + optionSelected?: string; + optionDisabled?: string; + optionIcon?: string; + optionContent?: string; + optionLabel?: string; + optionDescription?: string; + divider?: string; + emptyState?: string; + loadingState?: string; +} +``` + +### ActiveFilters + +```tsx +interface ActiveFiltersClassNames { + container?: string; + filterList?: string; + filterItem?: string; + filterLabel?: string; + filterValue?: string; + filterRemove?: string; + clearAll?: string; + emptyState?: string; +} +``` + +## Styling Cookbook + +### 1. Tailwind CSS Template + +```tsx +const tailwindStyles = { + // Modern card style + container: "bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4", + + // Interactive elements + trigger: "px-4 py-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-md transition-colors", + + // Form inputs + input: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 dark:bg-gray-700", + + // Buttons + primaryButton: "px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors", + secondaryButton: "px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-md transition-colors", +}; +``` + +### 2. CSS Modules Template + +```css +/* DateFilter.module.css */ +.container { + background: var(--surface); + border-radius: var(--radius); + padding: var(--spacing-4); +} + +.input { + width: 100%; + padding: var(--spacing-2) var(--spacing-3); + border: 1px solid var(--border); + border-radius: var(--radius-sm); +} + +.input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-alpha); +} +``` + +### 3. Styled Components Template + +```tsx +const StyledDateFilter = { + container: styled.div` + background: ${(props) => props.theme.surface}; + border-radius: ${(props) => props.theme.radius}; + padding: ${(props) => props.theme.spacing(4)}; + `, + + input: styled.input` + width: 100%; + padding: ${(props) => props.theme.spacing(2, 3)}; + border: 1px solid ${(props) => props.theme.border}; + + &:focus { + outline: none; + border-color: ${(props) => props.theme.primary}; + } + `, +}; +``` + +## Advanced Patterns + +### Conditional Styling + +```tsx + +``` + +### Variant Systems + +```tsx +const createDateFilterStyles = (variant: "default" | "compact" | "inline") => ({ + container: cn("date-filter", { + "p-4 rounded-lg shadow": variant === "default", + "p-2": variant === "compact", + "inline-flex items-center gap-2": variant === "inline", + }), + // ... other styles based on variant +}); + +; +``` + +### Composition + +```tsx +// Compose multiple style sources +const dateFilterStyles = { + ...baseStyles.dateFilter, + ...themeStyles.dateFilter, + ...customStyles, +}; +``` + +## Testing Strategy + +### 1. Visual Regression Tests + +```tsx +// Create stories with different style systems +export const TailwindStyled = () => ; + +export const CSSModulesStyled = () => ; + +export const Unstyled = () => ; +``` + +### 2. Accessibility Tests + +```tsx +test("maintains accessibility without styles", () => { + render(); + + // Ensure ARIA attributes work + expect(screen.getByRole("button", { name: "Apply" })).toBeInTheDocument(); + expect(screen.getByLabelText("Start date")).toBeInTheDocument(); +}); +``` + +### 3. Style Application Tests + +```tsx +test("applies custom classNames", () => { + const classNames = { + container: "custom-container", + applyButton: "custom-button", + }; + + render(); + + expect(screen.getByTestId("date-filter")).toHaveClass("custom-container"); + expect(screen.getByText("Apply")).toHaveClass("custom-button"); +}); +``` + +## Implementation Checklist + +- [ ] Remove all CSS imports from components +- [ ] Delete all .module.css files +- [ ] Add classNames prop to all components +- [ ] Add className prop for root element +- [ ] Ensure all elements can be styled +- [ ] Add data-testid attributes +- [ ] Forward refs where appropriate +- [ ] Update TypeScript interfaces +- [ ] Create migration guide +- [ ] Build style templates +- [ ] Update documentation +- [ ] Add deprecation warnings (v1.9) +- [ ] Create codemods + +## Decision + +**Recommendation: Proceed with ClassNames Object API** + +This provides the best balance of: + +- Simplicity for users +- Flexibility for styling +- Ease of implementation +- Clear migration path +- Industry-standard patterns + +The classNames approach is proven by popular libraries like React Select, Headless UI, and others. It's intuitive, performant, and provides excellent developer experience. diff --git a/src/demo/version-info.json b/src/demo/version-info.json index c17681c..2ffb6c8 100644 --- a/src/demo/version-info.json +++ b/src/demo/version-info.json @@ -1,13 +1,13 @@ { "version": "0.2.0-rc1", "git": { - "commitHash": "175c2c148a124dc8b11ca9d18b5d367f85bf4322", - "shortHash": "175c2c1", - "branch": "fix/89-category-selector-headless", - "commitDate": "2025-07-09 22:54:18 -0500", + "commitHash": "4012ad96e64dbb97b19236524979b0cee1a4e6a8", + "shortHash": "4012ad9", + "branch": "feat/v2-headless-components", + "commitDate": "2025-07-11 07:21:18 -0500", "latestTag": "v0.1.1-rc1", - "commitsSinceTag": 34, - "isDirty": true + "commitsSinceTag": 36, + "isDirty": false }, "deployment": { "isPR": false, @@ -15,7 +15,7 @@ "isMainBranch": false, "deployPath": "ag-grid-react-components" }, - "buildTime": "2025-07-10T13:05:13.579Z", - "displayVersion": "v0.2.0-rc1+34", - "displayLabel": "fix/89-category-selector-headless" + "buildTime": "2025-07-11T12:22:45.212Z", + "displayVersion": "v0.2.0-rc1+36", + "displayLabel": "feat/v2-headless-components" }