UNPKG

@base-framework/atoms

Version:

This will add default atoms to the base framework.

429 lines (350 loc) 13.6 kB
# Base Atoms - AI Coding Instructions ## Architecture Overview This is an npm package that provides atomic HTML components for the Base Framework. It exports reusable, composable atoms that serve as building blocks for component-based architecture. **Key Dependencies:** - `@base-framework/base`: Core framework providing `Atom`, `Builder`, and `dataBinder` - Built as ESM module with TypeScript declarations generated from JSDoc ## CRITICAL: What Atoms Return **Atoms return OBJECTS, NOT DOM elements.** This is fundamental to understanding Base Atoms: ```javascript // ✅ CORRECT - Atoms return objects const element = Div({ class: 'container' }); // Returns { class: 'container', tag: 'div' } // ❌ WRONG - Do NOT treat atoms as DOM elements const element = Div({ class: 'container' }); element.appendChild(child); // ERROR: Objects don't have appendChild // ✅ CORRECT - Children go in the atom definition const element = Div({ class: 'container' }, [ Span('child text') ]); ``` **The Base Framework's Builder converts these objects into DOM elements during rendering.** ## Import Patterns ### Available Imports ```javascript // Core framework imports (from @base-framework/base package) import { Atom, Builder, dataBinder, Data, Component } from '@base-framework/base'; // HTML element atoms (from this package) import { Div, Span, Button, Input, Form, Section, Article } from '@base-framework/atoms'; // Conditional rendering atoms (from this package) import { On, OnState, OnRoute, If, IfState, OnLoad, OnStateLoad, OnOpen, OnStateOpen } from '@base-framework/atoms'; // Responsive atoms (from this package) import { OnXs, OnSm, OnMd, OnLg, OnXl, On2Xl } from '@base-framework/atoms'; import { OnXsOnly, OnSmOnly, OnMdOnly, OnLgOnly, OnXlOnly, On2XlOnly } from '@base-framework/atoms'; import { OnPhone, OnTablet, OnDesktop } from '@base-framework/atoms'; // Utility atoms (from this package) import { UseParent } from '@base-framework/atoms'; ``` ### Import Rules - **NEVER** import `Div`, `Span`, `Button` etc. from `@base-framework/base` - they come from `@base-framework/atoms` - **ALWAYS** import `Atom`, `Builder`, `dataBinder`, `Data`, `Component` from `@base-framework/base` - When creating new atom files, import `Atom` from `@base-framework/base`, not from atoms package ## Core Patterns ### Atom Creation Patterns Two primary patterns for creating atoms: 1. **Simple Function Atoms** (for basic HTML elements): ```javascript const Meta = (props) => ({ ...props, tag: 'meta' }); ``` 2. **Atom Wrapper Functions** (for composable atoms): ```javascript const Div = Atom((props, children) => Tag(props, children)); ``` ### Component Composition Atoms use composition over inheritance. Children are passed as arrays: ```javascript const SecondaryButton = Atom((props, children) => Button({ ...props, class: 'secondary-btn', children })); ``` ### Special Atoms Architecture Located in `/src/on/` and `/src/use/` directories: - **On Atoms**: Conditional rendering based on data binding (`On`, `OnState`, `OnRoute`) - **Responsive Atoms**: Mobile-first breakpoint rendering (`OnXs`, `OnSm`, `OnMd`, `OnLg`, `OnXl`, `On2Xl`) - **UseParent**: Provides access to parent component context - Use **comment placeholders** to maintain DOM position during dynamic updates #### Responsive Breakpoint System The responsive atoms (`OnXs`, `OnSm`, `OnMd`, `OnLg`, `OnXl`, `On2Xl`) use a Data-based size tracking system: - Global `sizeData` object tracks current breakpoint and window width - Single resize listener updates all responsive atoms efficiently - Mobile-first approach: each breakpoint renders on matching size AND larger - Tailwind CSS compatible breakpoints (640px, 768px, 1024px, 1280px, 1536px) - Located in `/src/on/on-size.js` with re-exports in `/src/on/on.js` ## Development Workflows ### Build Process ```bash npm run build # Builds dist/ with esbuild + generates TypeScript declarations npm run prepublishOnly # Pre-publish build step ``` ### File Structure - `src/atoms.js`: Main export file with all HTML element atoms - `src/on/on.js`: Dynamic conditional rendering atoms - `src/on/on-size.js`: Responsive breakpoint atoms and size tracking system - `src/use/use.js`: Parent component access utilities - `src/comment.js`: Comment placeholder utility ## Code Conventions ### JSDoc Documentation All atoms require comprehensive JSDoc with proper type annotations: ```javascript /** * Creates a button element. * * @param {object} props - Properties for the element. * @param {array} children - Children elements. * @returns {object} - Returns an object representing the element. */ ``` ### Event Handling Pattern Event callbacks receive `(event, parent)` parameters for parent component access: ```javascript Button({ click(event, parent) { // Access parent component here } }) ``` ### Flexible Argument Handling Atoms support multiple call patterns: - Props only: `Div({class: 'text'})` - Children only: `Div('text')` or `Div([childrenArray])` - Both: `Div({class: 'text'}, children)` ## Common Mistakes to Avoid ### ❌ MISTAKE 1: Treating Atoms as DOM Elements ```javascript // ❌ WRONG - Atoms return objects, not DOM elements const div = Div(); div.appendChild(child); // ERROR div.innerHTML = 'text'; // ERROR document.body.appendChild(div); // ERROR // ✅ CORRECT - Let the framework handle DOM operations const div = Div({ class: 'container' }, [ Span('child') ]); // Framework's Builder will create actual DOM elements ``` ### ❌ MISTAKE 2: Forgetting Children Arrays ```javascript // ❌ WRONG - Multiple children without array Div({ class: 'container' }, Span('one'), Span('two') ); // ✅ CORRECT - Wrap multiple children in array Div({ class: 'container' }, [ Span('one'), Span('two') ]); ``` ### ❌ MISTAKE 3: Wrong Event Handler Signature ```javascript // ❌ WRONG - Missing parent parameter Button({ click(event) { // Can't access parent component } }); // ✅ CORRECT - Include both event and parent parameters Button({ click(event, parent) { // Now you can access parent.data, parent.state, etc. parent.data.count++; } }); ``` ### ❌ MISTAKE 4: Wrong Conditional Atom Returns ```javascript // ❌ WRONG - Returning undefined or not returning atom objects On('loaded', (loaded) => { if (loaded) { Content(); // Missing return statement } }); // ❌ WRONG - Returning plain objects without tag On('loaded', (loaded) => { return { text: 'loaded' }; // Missing tag property }); // ✅ CORRECT - Always return atom objects or null On('loaded', (loaded) => { if (loaded) { return Content(); // Return the atom } return null; // Or return null when condition not met }); // ✅ CORRECT - Using ternary for cleaner code On('loaded', (loaded) => loaded ? Content() : Loader()); ``` ### ❌ MISTAKE 5: Not Spreading Props ```javascript // ❌ WRONG - Losing all props passed to atom const CustomButton = Atom((props, children) => ({ tag: 'button', class: 'custom-btn', children })); // ✅ CORRECT - Spread props to preserve all properties const CustomButton = Atom((props, children) => ({ tag: 'button', ...props, // Spread props to preserve everything class: `custom-btn ${props.class || ''}`, // Merge classes if needed children })); // ✅ ALSO CORRECT - Use Button atom and spread props const CustomButton = Atom((props, children) => Button({ ...props, class: `custom-btn ${props.class || ''}`, children })); ``` ### ❌ MISTAKE 6: Missing Tag Property ```javascript // ❌ WRONG - Creating element without tag const MyElement = (props) => ({ ...props, class: 'my-element' }); // ✅ CORRECT - Include tag property for custom elements const MyElement = (props) => ({ tag: 'div', // Must specify the HTML tag ...props, class: 'my-element' }); // ✅ BETTER - Use existing atom as base const MyElement = Atom((props, children) => Div({ ...props, class: 'my-element', children })); ``` ### ❌ MISTAKE 7: Wrong Responsive Atom Usage ```javascript // ❌ WRONG - Not returning anything from callback OnMd(() => { Div('content'); // Missing return }); // ❌ WRONG - Trying to use like a wrapper component OnMd({ Div('content') // Not a function callback }); // ✅ CORRECT - Pass callback that returns atoms OnMd((size, parent) => { return Div({ class: 'desktop-content' }, [ H1('Desktop View'), P('This appears on medium screens and larger') ]); }); // ✅ CORRECT - Shorter syntax OnMd(() => Div('Desktop content')); ``` ### ❌ MISTAKE 8: Incorrect Data Binding in On Atoms ```javascript // ❌ WRONG - Passing undefined data source On(undefined, 'loaded', (loaded) => Content()); // ❌ WRONG - Wrong property name On('isLoaded', (loaded) => Content()); // Property might be 'loaded' not 'isLoaded' // ✅ CORRECT - Let On atom find data source automatically On('loaded', (loaded) => loaded ? Content() : Loader()); // ✅ CORRECT - Explicitly specify data source On(parent.data, 'loaded', (loaded) => loaded ? Content() : Loader()); // ✅ CORRECT - Use specific atom types OnState('loaded', (loaded) => loaded ? Content() : Loader()); OnRoute('path', (path) => path === '/home' ? Home() : null); ``` ## Critical Implementation Details ### Dynamic Rendering (On Atoms) - Use comment placeholders to maintain DOM insertion points - Handle previous element cleanup in `updateLayout` functions - Support data binding to component data, context, or state ### Responsive Breakpoint Implementation - Responsive atoms use Data object for efficient size tracking - Single window resize listener manages all breakpoint updates - Mobile-first rendering: each breakpoint shows on current size AND larger - Automatic cleanup prevents memory leaks when components unmount - Server-side rendering safe with window existence checks ### Base Framework Integration - Always import `Atom` from `@base-framework/base` - Use `Builder.build()` and `Builder.removeNode()` for DOM manipulation - Leverage `dataBinder` for reactive data connections - Use `Data` class for global state management (e.g., responsive size tracking) ### TypeScript Support - Enable `allowJs: true` and `checkJs: true` in tsconfig.json - Generate declarations with `emitDeclarationOnly: true` - Map Base Framework types in `paths` configuration When working with this codebase, focus on maintaining the established patterns for atom creation, JSDoc documentation, and the flexible argument handling that allows atoms to work seamlessly within the Base Framework ecosystem. The responsive breakpoint atoms demonstrate how to integrate with the Base Framework's Data system for efficient global state management. ## Quick Reference Guide ### Golden Rules 1. **Atoms return objects, not DOM elements** 2. **Always wrap multiple children in arrays** 3. **Event handlers need `(event, parent)` signature** 4. **Conditional atom callbacks must return atom objects or null** 5. **Always spread `...props` to preserve properties** 6. **Custom elements need a `tag` property** 7. **Import HTML atoms from `@base-framework/atoms`** 8. **Import `Atom`, `Builder`, `Data` from `@base-framework/base`** ### Most Common Patterns #### Creating a Basic Atom ```javascript import { Atom } from '@base-framework/base'; const MyAtom = Atom((props, children) => ({ tag: 'div', ...props, children })); ``` #### Using Atoms with Children ```javascript // Single child (string or atom) Div('text') Div(Span('text')) // Multiple children (array) Div([ Span('one'), Span('two') ]) // With props and children Div({ class: 'container' }, [ Span('child') ]) ``` #### Event Handlers ```javascript Button({ click(event, parent) { parent.data.count++; } }) ``` #### Conditional Rendering ```javascript // Auto-detect data source On('loaded', (loaded) => loaded ? Content() : Loader()) // Specific data source OnState('loaded', (loaded) => loaded ? Content() : null) OnRoute('path', (path) => path === '/home' ? Home() : null) // With boolean check OnLoad((loaded) => Dashboard()) OnOpen(() => Modal()) // With exact value match If('status', 'active', () => ActiveContent()) IfState('mode', 'edit', () => Editor()) ``` #### Responsive Design ```javascript // Mobile-first (shows on breakpoint AND larger) OnMd(() => Div('Desktop navigation')) // Semantic devices OnPhone(() => Div('Mobile layout')) OnTablet(() => Div('Tablet layout')) OnDesktop(() => Div('Desktop layout')) // Exact breakpoint only OnMdOnly(() => Div('Only on medium screens')) ``` ### Debugging Checklist When atoms don't work as expected, check: - [ ] Are you returning objects from atom functions? - [ ] Are multiple children wrapped in an array? - [ ] Does your custom element have a `tag` property? - [ ] Are you spreading `...props` to preserve properties? - [ ] Do event handlers have `(event, parent)` signature? - [ ] Are conditional callbacks returning atoms or null? - [ ] Are imports coming from the correct packages?