Package Exports
- @base-framework/atoms
- @base-framework/atoms/copilot.md
- @base-framework/atoms/package.json
Readme
Base Atoms
Version: 1.0.0
Overview
This documentation aims to guide the enhancement of component scalability and reusability within your projects through the use of atoms. Atoms are designed to function as the fundamental building blocks in a component-based architecture.
This module will add default atoms to your project.
Atom Scope
Within our component model, each component autonomously generates its own scope. When components are nested, unique scopes are established at each level. Atoms inherit the scope of their parent component, gaining access to the component's state and data, and enabling directive manipulation and event handling.
Global Reusability
Atoms are globally defined and reusable entities, mirroring the nested capabilities characteristic of other layout constructs.
Atom Types
Atoms can be instantiated using various methodologies:
Function Atoms
These atoms are instantiated with either standard functions or arrow functions, equipped with a props object to transfer properties to the atoms.
const Div = (props, children) => ({
...props,
children
});Atom Callbacks
Atoms may be created using the Atom function, which accepts a callback function as its sole parameter. The callback function is passed a props object and children array and returns an object containing the atom's layout.
const Button = Atom((props, children) => ({
tag: 'button',
...props,
children
}));Atom Nesting
Atoms should use composition to nest other atoms. This is achieved by passing the children array to the atoms args.
const SecondaryButton = Atom((props, children) => (Button({
...props,
class: 'secondary-btn',
children
}));Adding Event Listeners
Event listener callbacks within atoms accept two parameters: the originating event object and the "parent" component object in which the atom resides.
Accessing the Parent Component in an Atom
class Page extends Component
{
render()
{
return Div([
SecondaryButton({
/**
* This will add a click event listener to the button.
*
* @param {Event} event The event object
* @param {Component} parent The parent component object
* @returns {void}
*/
click(event, parent) =>
{
// Code to access the parent component
}
})
]);
}
}Utilization of Atoms
To leverage an atom, invoke its function and pass the requisite values via a props and children. The Atoms created with the Atom callback functions support passing optional props or children to the atom. The props object should always be first but if the atom does not require props, the children array or string can be passed as the first argument.
// props only
Div({class: 'text'});
// text child only
Div('test');
// array child only
Div([
Div('test')
]);
// props and text child
Div({class: 'text'}, 'test');
// props and array children
Div({class: 'text'}, [
Div('test'),
Div('test')
]);Example of Atom Utilization
SecondaryButton({
click(e) =>
{
// Handle the click event
}
})The implementation of atoms is aimed at enhancing the readability and modularity of extensive layouts.
Illustrative Example of a Complex Layout
Section([
Article({ class: 'post' }, [
Header([
H1('Title')
])
])
])Special Atoms
Special atoms are atoms that have been pre-configured with specific properties. These atoms are designed to simplify the creation of common components. This library has a few special atoms that can help create complex actions.
On Atom
The On atom allows a child to be added or removed based on a bindable data property. This atom makes the child not need to have an "onSet" property on a parent wrapper allowing for easier layouts with less code.
// The traditional way using a parent wrapper with an onSet property
Div({
class: "p-4 transition",
onState: ["loaded", (loaded) => (!loaded)
? SkeletonPost()
: Post(post)
]
})
// The new way using the On atom
Div({ class: "p-4 transition" }, [
On('loaded', (loaded) => (!loaded)
? SkeletonPost()
: Post(post))
])
// Or with a state
Div({ class: "p-4 transition" }, [
OnState('loaded', (loaded) => (!loaded)
? SkeletonPost()
: Post(post))
])
// Children can now be added dynamically without a wrapper
Div({ class: "p-4 transition" }, [
H1('Title'),
// this will add or remove this child based on the loaded state
On('loaded', (loaded) => (!loaded)
? SkeletonPost()
: Post(post))
])
The On, OnState, and OnRoute atoms support two overloads. The first overload is the data property to bind to and the second is the callback function that will be called when the data property changes.
On Atom Overloads
// This will watch to the component data, or context data, or component state in that order if the first two are not set in the component.
// data property, callback
On('loaded', (loaded) => (!loaded)
? SkeletonPost()
: Post(post))
The second overload allows for a custom data source to be used. This allows for more flexibility with the layouts.
// watching on the component route
// data source, data property, callback
On(this.route, 'loaded', (loaded) => (!loaded)
? SkeletonPost()
: Post(post))
// With the OnRoute Atom
OnRoute('loaded', (loaded) => (!loaded)
? SkeletonPost()
: Post(post))
The callback function will be called when the data property changes. The callback function will be called with the new value of the data property, the element that the watcher is using, and the parent component.
// the new data value, the element that the watcher is using, the parent component object
(loaded, element, parent) =>
{
// handle the loaded state
}OnLoad and OnStateLoad Atoms
The OnLoad and OnStateLoad atoms are specialized versions that automatically watch for a 'loaded' property. OnLoad watches component data/context/state, while OnStateLoad specifically watches component state.
// OnLoad - watches for 'loaded' property in component data/context/state
Div({ class: "content-container" }, [
H1('My Content'),
// Shows content when loaded is true, nothing when false
OnLoad((loaded, element, parent) =>
Div({ class: "loaded-content" }, [
P('Content has been loaded!')
])
),
// With a fallback for when not loaded
OnLoad(
(loaded, element, parent) =>
Div({ class: "loaded-content" }, [
P('Content loaded successfully!')
]),
// Fallback content when not loaded
Div({ class: "loading-spinner" }, 'Loading...')
)
])
// OnStateLoad - specifically watches component state
Div({ class: "dashboard" }, [
OnStateLoad((loaded, element, parent) =>
Dashboard({ data: parent.state.dashboardData })
)
])OnOpen and OnStateOpen Atoms
The OnOpen and OnStateOpen atoms watch for an 'open' property to show/hide content. Useful for modals, dropdowns, and collapsible content.
// OnOpen - watches for 'open' property in component data/context/state
Div({ class: "modal-container" }, [
Button({
click: (e, parent) => parent.data.open = true
}, 'Open Modal'),
// Modal only shows when open is true
OnOpen((open, element, parent) =>
Div({ class: "modal-overlay" }, [
Div({ class: "modal-content" }, [
H2('Modal Title'),
P('Modal content goes here'),
Button({
click: (e, parent) => parent.data.open = false
}, 'Close')
])
])
)
])
// OnStateOpen - specifically watches component state
Nav({ class: "navigation" }, [
Button({
click: (e, parent) => parent.state.open = !parent.state.open
}, 'Toggle Menu'),
OnStateOpen((open, element, parent) =>
Ul({ class: "nav-menu" }, [
Li(A({ href: "/home" }, 'Home')),
Li(A({ href: "/about" }, 'About')),
Li(A({ href: "/contact" }, 'Contact'))
])
)
])If and IfState Atoms
The If and IfState atoms allow conditional rendering based on exact value matches. They take a property name, expected value, and callback function.
// If - watches component data/context/state for exact value match
Div({ class: "user-profile" }, [
H1('User Profile'),
// Show admin panel only when role equals 'admin'
If('role', 'admin', (role, element, parent) =>
Div({ class: "admin-panel" }, [
H2('Admin Controls'),
Button('Manage Users'),
Button('System Settings')
])
),
// Show different content based on subscription status
If('subscription', 'premium', (subscription, element, parent) =>
Div({ class: "premium-features" }, [
P('Premium Features Available'),
Button('Advanced Analytics'),
Button('Priority Support')
])
),
If('subscription', 'basic', (subscription, element, parent) =>
Div({ class: "upgrade-prompt" }, [
P('Upgrade to Premium for more features'),
Button('Upgrade Now')
])
)
])
// IfState - specifically watches component state for exact matches
Div({ class: "game-interface" }, [
IfState('gameStatus', 'playing', (status, element, parent) =>
Div({ class: "game-controls" }, [
Button('Pause Game'),
Button('Save Progress')
])
),
IfState('gameStatus', 'paused', (status, element, parent) =>
Div({ class: "pause-menu" }, [
Button('Resume Game'),
Button('Main Menu'),
Button('Quit Game')
])
),
IfState('gameStatus', 'gameOver', (status, element, parent) =>
Div({ class: "game-over" }, [
H2('Game Over!'),
P(`Final Score: ${parent.state.score}`),
Button('Play Again'),
Button('Main Menu')
])
)
])
// If with custom data source
Div({ class: "weather-widget" }, [
If(parent.weatherData, 'condition', 'sunny', (condition, element, parent) =>
Div({ class: "sunny-weather" }, [
Icon('sun'),
P('Perfect weather for outdoor activities!')
])
)
])Use Parent Atom
The UseParent atom allows for the parent component to be accessed in a child atom. This atom is useful when a child atom needs to access the parent component.
UseParent((parent) =>
{
// access the parent component
return Div({
class: parent.state.loaded ? 'loaded' : 'loading'
});
})Responsive Breakpoint Atoms
The responsive breakpoint atoms provide Tailwind CSS-like mobile-first responsive rendering. These atoms conditionally render content based on the current window size, following the mobile-first approach where each breakpoint renders when the screen size matches OR is larger.
Available Breakpoint Atoms
OnXs- 0px+ (always renders, useful for mobile-only content)OnSm- 640px+ (small devices and larger)OnMd- 768px+ (medium devices and larger)OnLg- 1024px+ (large devices and larger)OnXl- 1280px+ (extra large devices and larger)On2Xl- 1536px+ (2x extra large devices and larger)
Usage Examples
// Mobile-first navigation - show different nav styles for different screen sizes
Div({ class: "navigation" }, [
// Mobile navigation (always shows on xs, hidden on sm+)
OnXs(() =>
Div({ class: "mobile-nav" }, [
Button({ class: "hamburger" }, '☰'),
Div({ class: "mobile-menu hidden" }, [
A({ href: "/home" }, 'Home'),
A({ href: "/about" }, 'About')
])
])
),
// Tablet and desktop navigation (shows on md+)
OnMd(() =>
Nav({ class: "desktop-nav" }, [
Ul({ class: "flex space-x-4" }, [
Li(A({ href: "/home" }, 'Home')),
Li(A({ href: "/about" }, 'About')),
Li(A({ href: "/contact" }, 'Contact'))
])
])
)
])
// Responsive grid layouts
Div({ class: "content-area" }, [
// Mobile layout (1 column)
OnSm(() =>
Div({ class: "grid grid-cols-1 gap-4" }, [
Card({ title: "Mobile Card 1" }),
Card({ title: "Mobile Card 2" })
])
),
// Desktop layout (3 columns)
OnLg(() =>
Div({ class: "grid grid-cols-3 gap-6" }, [
Card({ title: "Desktop Card 1" }),
Card({ title: "Desktop Card 2" }),
Card({ title: "Desktop Card 3" })
])
)
])
// Access current window width in callback
OnXl((width, element, parent) => {
return Div({ class: "analytics-panel" }, [
H2('Large Screen Analytics'),
P(`Screen width: ${width}px`),
Chart({ width: width - 100 })
]);
})
// Conditional content based on screen size
Div({ class: "hero-section" }, [
H1('Welcome'),
// Show detailed hero content only on larger screens
OnLg(() =>
Div({ class: "hero-details" }, [
P('Detailed description with more information...'),
Button({ class: "cta-large" }, 'Get Started Today')
])
),
// Show simplified content on smaller screens
OnSm(() =>
Button({ class: "cta-mobile" }, 'Start')
)
])How Responsive Atoms Work
The responsive atoms use a global Data object that tracks the current window size and breakpoint. When the window is resized:
- The window width is measured
- The appropriate breakpoint name is determined (xs, sm, md, lg, xl, 2xl)
- The global
sizeData.sizeproperty is updated - All responsive atoms automatically re-evaluate and show/hide content accordingly
This system provides:
- Efficient Performance: Single global resize listener for all responsive atoms
- Mobile-First: Each breakpoint shows on matching size AND larger (Tailwind approach)
- Automatic Cleanup: No memory leaks, listeners are properly managed
- SSR Safe: Works in server-side rendering environments
Breakpoint Reference
| Breakpoint | Min Width | CSS Equivalent | Use Case |
|---|---|---|---|
| OnXs | 0px | (always) | Mobile phones |
| OnSm | 640px | @media (min-width: 640px) | Large phones, small tablets |
| OnMd | 768px | @media (min-width: 768px) | Tablets |
| OnLg | 1024px | @media (min-width: 1024px) | Laptops, desktops |
| OnXl | 1280px | @media (min-width: 1280px) | Large desktops |
| On2Xl | 1536px | @media (min-width: 1536px) | Extra large screens |
Semantic Device Atoms
For simplified responsive design, use semantic device atoms that group breakpoints logically:
OnPhone- Phone-sized devices (xs + sm: 0-767px)OnTablet- Tablet-sized devices (md: 768-1023px)OnDesktop- Desktop-sized devices (lg + xl + 2xl: 1024px+)
// Clean semantic responsive layout
Div({ class: "app-layout" }, [
// Mobile layout for phones
OnPhone(() =>
Div({ class: "mobile-stack" }, [
MobileHeader(),
MobileNav(),
MobileContent()
])
),
// Tablet layout
OnTablet(() =>
Div({ class: "tablet-grid" }, [
TabletHeader(),
Div({ class: "tablet-columns" }, [
TabletSidebar(),
TabletContent()
])
])
),
// Desktop layout
OnDesktop(() =>
Div({ class: "desktop-layout" }, [
DesktopHeader(),
Div({ class: "desktop-main" }, [
DesktopSidebar(),
DesktopContent(),
DesktopAside()
])
])
)
])Exact Breakpoint Atoms
For precise control, use exact breakpoint atoms that only render on specific screen sizes:
OnXsOnly- Only xs (0-639px)OnSmOnly- Only sm (640-767px)OnMdOnly- Only md (768-1023px)OnLgOnly- Only lg (1024-1279px)OnXlOnly- Only xl (1280-1535px)On2XlOnly- Only 2xl (1536px+)
// Precise breakpoint targeting
Div({ class: "responsive-banner" }, [
// Different banner for each exact screen size
OnXsOnly(() =>
Div({ class: "xs-banner" }, "Optimized for small phones")
),
OnSmOnly(() =>
Div({ class: "sm-banner" }, "Perfect for large phones")
),
OnMdOnly(() =>
Div({ class: "md-banner" }, "Great tablet experience")
),
OnLgOnly(() =>
Div({ class: "lg-banner" }, "Desktop ready")
),
OnXlOnly(() =>
Div({ class: "xl-banner" }, "Large screen optimized")
),
On2XlOnly(() =>
Div({ class: "2xl-banner" }, "Ultra-wide display")
)
])Responsive Atom Comparison
| Type | Behavior | Use Case |
|---|---|---|
Mobile-First (OnSm, OnMd, etc.) |
Shows on breakpoint AND larger | Progressive enhancement, adding features for larger screens |
Semantic (OnPhone, OnTablet, OnDesktop) |
Shows only on device category | Clean device-specific layouts |
Exact (OnSmOnly, OnMdOnly, etc.) |
Shows only on specific breakpoint | Precise responsive control, unique layouts per size |
Contributing
Contributions to Base Framework are welcome. Follow these steps to contribute:
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Commit your changes with clear, descriptive messages.
- Push your branch and submit a pull request.
- Before contributing, read our CONTRIBUTING.md for coding standards and community guidelines.
License
Base Atoms are licensed under the MIT License. See the LICENSE file for details.