🔥 UI components for Meteor-Blaze and TailwindCSS 🔥
This project is partially inspired by shacdn, radix-ui and headless ui and brings a set of opinionated, yet flexibly changeable UI components on the table.
- 🔥 awesome UI components out of the box
- 🔥 simple to get started
- 🔥 supports variants; allows for custom variants
- 🔥 performant reactive attribute compilation
- 🔥 register your own components
- 🔥 builtin light/dark theme support
BlazeUI is not related to https://www.blazeui.com/
generated with https://github.com/thlorenz/doctoc
$ meteor add blazeui:components$ meteor npm install --save \
tailwindcss \
autoprefixer \
postcss \
postcss-load-config \
class-variance-authority \
tailwindcss-animate \
clsx \
tailwind-merge \
@blazeui/theme-milkywayThis looks like a lot, so let's see what these packages are for:
- tailwindcss - the tailwind library
- autoprefixer - required to
- postcss - required to drop non-necessary css
- postcss-load-config - required to load config from tailwind config
- class-variance-authority - resolve variations of component styles
- tailwindcss-animate - more animation tools for tailwind
- clsx - assign classes with truthy/falsy values
- tailwind-merge - deduplicate class names for components
- @blazeui/theme-milkyway - the default blazeui theme
You need to provide a tailwind.config.js config file for your project.
As a staring point, you can use the config from the Milkyway theme:
Expand to view the `tailwind.config.js`
const { fontFamily } = require("tailwindcss/defaultTheme")
const milkyway = require("@blazeui/theme-milkyway")
/** @type {import('tailwindcss').Config} */
module.exports = {
...milkyway,
content: [
"./imports/ui/**/*.{js,jsx,ts,tsx,html}",
'./client/*.{js,html}',
'.meteor/local/build/programs/web*/**/*.js',
'./node_modules/@fortawesome/fontawesome-free/css/all.css',
]
}Next, you need a postcss.config.js config file. You can use this as a starter:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
excludedMeteorPackages: []
}For static import of all components, use:
import 'meteor/blazeui:components/all'this will instantly make all components available but also increases the bundle size.
If you need to be careful about bundle size, you may use the dynamic import way:
const { BlazeUI } = await import('meteor/jkuester:blazeui/core/BlazeUI.js')
const { Badge } = await import('meteor/jkuester:blazeui/components/badge/Badge.js')
BlazeUI.register(Badge)BlazeUI provides a default theme, which you can
import in client/main.js via
import '@blazeui/theme-milkyway/milkyway.css'This theme is zero config and looks great out of the box.
You also can also override the root variables
To do so, open your client/main.css (or .scss) file and provide
the root variables for the variants of the components:
Expand to view the main css file
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--radius: 0.5rem;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
font-family: Arial, Helvetica, sans-serif;
}
}Assuming your components are available you can use them by their respective names. Every component allows for a content block:
Fore more examples and recipes, view https://blazeui.meteorapp.com/components
BlazeUI is flexible at its core, enabling you fine-grained customization.
The library comes with default variants for the components. If that's not enough, you can easily extend variants:
import { BlazeUI, Badge } from 'meteor/jkuester:blazeui/static'
BlazeUI.variants({
ctx: Badge,
type: 'size',
values: {
xs: "text-xs",
sm: "text-sm",
base: "text-base",
lg: "text-lg p-2",
xl: "text-xl p-3",
},
default: 'xs'
})You can use this function also to change the default for an
existing variant or extend/override the variant itself.
First, you need to have a template defined for your component:
In your js, you can then register it like this:
import { ReactiveDict } from 'meteor/reactive-dict'
import { BlazeUI } from 'meteor/blazeui:core'
import './Hello.html'
const Hello = {
/**
* Required, must exactly match the name of the Template!
*/
name: 'Hello',
/**
* Optional, the base class that is always applied.
* Can be overridden.
*/
class: 'p-1 bg-primary text-primary-foreground transition-all ease-in-out',
/**
* Optional, a RactiveDict that can be used to manage internal
* state. You can also use a custom reactive data source as long
* as implements the methods of ReactiveDict (get, set, all etc.).
*/
state: new ReactiveDict({ active: false }),
/**
* Optional function if you need to resolve attributes for the component
* with awareness of the state.
* Reactive: This method gets called, when props or state change.
*
* @param props {object} the object, returned by {Template.currentData()}
* @param state {object|undefined} present, when {state} is defined on the component.
* @param api {BlazeUI} the BlazeUI top-level api is always passed down to components.
* @return {{role: string, class: string}}
*/
attributes ({ props, state, api }) {
const { class:className, ...rest } = props
const { merge } = api.styles()
const { active } = state
return {
role: 'button',
class: merge(
Hello.class,
active ? 'text-4xl' : 'text-xs',
className
),
...rest
}
},
/**
* This is passed to the Template's `onCreated` method.
* Note, that state is only passed, when being defined on this
* component.
* @param state {object?}
*/
onCreated ({ state }) {
const instance = this
instance.state = state
},
helpers: {
active() {
return Template.instance().state.get('active')
}
},
events: {
'click div' (e, t) {
// this results in 'attributes' being called right after
t.state.set('active', !t.state.get('active'))
}
}
}
BlazeUI.register(Hello)You can change any component's default styles by simply overriding its class property
or its variants.
BlazeUI is designed to relieve you from the burden of implementing state-sharing between components and their children.
Sometimes you can simply forward the state as props of the child, but this quickly results in so-called "prop-drilling", where a re prop is passed down multiple levels of children.
Instead you can use a context that parents share with their children.
The parent simply needs to use the blazeui_contetx helper to provide
it to all children:
A child component can use this shared state via the api
parameter in the state function:
export const MyComponent = {
name: 'MyComponent',
class: 'p-4 border rounded-md',
state: ({ instance }) => {
// attach state to the instance
// so the blazeui_context helper
// will pick it up.
// convention: it must be named "state"
// and exist as property of the instance
instance.state = new ReactiveDict({
active: false
})
return instance.state
},
attributes ({ props, state, api }) {
const { merge } = api.styles()
const active = state.get('active')
return {
data-active: active, // this also updates when children change the state
class: merge(MyComponent.class, props.class)
}
}
}
export const MyComponentTrigger = {
name: 'MyComponentTrigger',
class: 'border bg-primary rounded-md font-semibold text-primary-foreground',
state: ({ instance, api }) => {
// this will automatically pick up the state from the parent
// no matter which level of depth this child is curently located.
const { useFromContext } = api.state()
return useFromContext({ instance, key: 'MyComponentContext' })
},
events: {
'click button' (e, t) {
// toggle active
t.state.set({ active: !t.state.get('active') })
}
}
}MIT, see license file