diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index ddd21553..db2c1ecd 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,4 +1,4 @@ -name: Node.js CI +name: Publish Docs Site on: push: @@ -7,6 +7,8 @@ on: jobs: gh-pages: runs-on: ubuntu-latest + permissions: + contents: write strategy: matrix: node-version: [20.x] @@ -17,10 +19,12 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: yarn - - run: yarn install --frozen-lockfile - - run: yarn test --coverage + cache-dependency-path: site/yarn.lock + - run: yarn --cwd site --frozen-lockfile + - run: yarn --cwd site build + - run: touch site/out/.nojekyll - name: Publish to gh-pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./coverage/lcov-report + publish_dir: ./site/out diff --git a/.gitignore b/.gitignore index 4add131b..1762f017 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ lerna-debug.log /dist/ _book/ /demo/ + +# Static docs site artifacts +site/.next/ +site/out/ +site/node_modules/ diff --git a/site/app/[[...mdxPath]]/page.jsx b/site/app/[[...mdxPath]]/page.jsx new file mode 100644 index 00000000..9d06599f --- /dev/null +++ b/site/app/[[...mdxPath]]/page.jsx @@ -0,0 +1,50 @@ +import { generateStaticParamsFor, importPage } from 'nextra/pages' +import { notFound } from 'next/navigation' +import { useMDXComponents as getMDXComponents } from '../../mdx-components' + +const isAssetLikePath = (mdxPath = []) => + mdxPath.some((segment) => segment.includes('.')) + +export const generateStaticParams = generateStaticParamsFor('mdxPath') + +export async function generateMetadata(props) { + const params = await props.params + const mdxPath = params?.mdxPath ?? [] + + if (isAssetLikePath(mdxPath)) { + return {} + } + + try { + const { metadata } = await importPage(mdxPath) + return metadata + } catch { + return {} + } +} + +const Wrapper = getMDXComponents().wrapper + +export default async function Page(props) { + const params = await props.params + const mdxPath = params?.mdxPath ?? [] + + if (isAssetLikePath(mdxPath)) { + notFound() + } + + let page + try { + page = await importPage(mdxPath) + } catch { + notFound() + } + + const { default: MDXContent, toc, metadata, sourceCode } = page + + return ( + + + + ) +} \ No newline at end of file diff --git a/site/app/favicon.svg b/site/app/favicon.svg new file mode 100644 index 00000000..18bfbe79 --- /dev/null +++ b/site/app/favicon.svg @@ -0,0 +1,4 @@ + + + n + diff --git a/site/app/globals.css b/site/app/globals.css new file mode 100644 index 00000000..8273f008 --- /dev/null +++ b/site/app/globals.css @@ -0,0 +1,220 @@ +/* nano-css docs - Professional theme overrides */ + +:root { + --nano-accent: #0a0a0a; + --nano-muted: #666; + --nano-border: #e5e5e5; + --nano-surface: #fafafa; + --nano-brand: #0969da; +} + +.dark { + --nano-accent: #fafafa; + --nano-muted: #999; + --nano-border: #2a2a2a; + --nano-surface: #111; + --nano-brand: #58a6ff; +} + +/* Landing page hero */ +.nano-hero { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 6rem 1.5rem 4rem; + min-height: 70vh; +} + +.nano-hero h1 { + font-size: clamp(3rem, 8vw, 5.5rem); + font-weight: 800; + letter-spacing: -0.04em; + line-height: 1; + margin: 0; + color: var(--nano-accent); +} + +.nano-hero .nano-tagline { + font-size: clamp(1.1rem, 2.5vw, 1.4rem); + color: var(--nano-muted); + margin-top: 1.25rem; + font-weight: 400; + letter-spacing: -0.01em; + max-width: 540px; +} + +.nano-hero .nano-version { + display: inline-block; + margin-top: 1.5rem; + padding: 0.25rem 0.75rem; + font-size: 0.8rem; + font-weight: 500; + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + color: var(--nano-muted); + border: 1px solid var(--nano-border); + border-radius: 999px; + background: var(--nano-surface); +} + +.nano-cta { + display: flex; + gap: 0.75rem; + margin-top: 2.5rem; + flex-wrap: wrap; + justify-content: center; +} + +.nano-cta a { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.7rem 1.5rem; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 500; + text-decoration: none; + transition: all 150ms ease; +} + +.nano-cta .primary { + background: var(--nano-accent); + color: var(--nano-surface); +} +.nano-cta .primary:hover { + opacity: 0.85; +} + +.nano-cta .secondary { + border: 1px solid var(--nano-border); + color: var(--nano-accent); + background: transparent; +} +.nano-cta .secondary:hover { + background: var(--nano-surface); + border-color: var(--nano-muted); +} + +/* Stats bar */ +.nano-stats { + display: flex; + gap: 3rem; + margin-top: 4rem; + flex-wrap: wrap; + justify-content: center; +} + +.nano-stat { + text-align: center; +} + +.nano-stat .value { + display: block; + font-size: 1.8rem; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--nano-accent); + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; +} + +.nano-stat .label { + display: block; + font-size: 0.85rem; + color: var(--nano-muted); + margin-top: 0.25rem; +} + +/* Feature grid */ +.nano-features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; + max-width: 960px; + margin: 4rem auto; + padding: 0 1.5rem; +} + +.nano-feature { + padding: 1.75rem; + border: 1px solid var(--nano-border); + border-radius: 12px; + background: var(--nano-surface); + transition: border-color 150ms ease; +} +.nano-feature:hover { + border-color: var(--nano-muted); +} + +.nano-feature h3 { + font-size: 1.05rem; + font-weight: 600; + margin: 0 0 0.5rem; + color: var(--nano-accent); +} + +.nano-feature p, +.nano-feature-text { + font-size: 0.9rem; + color: var(--nano-muted); + margin: 0; + line-height: 1.6; +} + +/* Install snippet */ +.nano-install { + max-width: 480px; + margin: 0 auto 4rem; + text-align: center; +} + +.nano-install code { + display: inline-block; + padding: 0.6rem 1.25rem; + font-size: 0.9rem; + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + background: var(--nano-surface); + border: 1px solid var(--nano-border); + border-radius: 8px; + color: var(--nano-accent); +} + +/* Footer section on landing */ +.nano-footer-section { + text-align: center; + padding: 3rem 1.5rem 5rem; + color: var(--nano-muted); + font-size: 0.85rem; +} + +.nano-footer-section a { + color: var(--nano-brand); + text-decoration: none; +} +.nano-footer-section a:hover { + text-decoration: underline; +} + +/* Quick start code block on landing */ +.nano-quickstart { + max-width: 560px; + margin: 0 auto 4rem; + text-align: left; +} + +.nano-quickstart h2 { + text-align: center; + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 1.5rem; + color: var(--nano-accent); + letter-spacing: -0.02em; +} + +/* Separator line */ +.nano-separator { + max-width: 960px; + margin: 0 auto; + border: none; + border-top: 1px solid var(--nano-border); +} diff --git a/site/app/layout.jsx b/site/app/layout.jsx new file mode 100644 index 00000000..3aec4af4 --- /dev/null +++ b/site/app/layout.jsx @@ -0,0 +1,55 @@ +import { Footer, Layout, Navbar } from 'nextra-theme-docs' +import { Head } from 'nextra/components' +import { getPageMap } from 'nextra/page-map' +import 'nextra-theme-docs/style.css' +import './globals.css' + +export const metadata = { + title: { + default: 'nano-css — Tiny CSS-in-JS Library', + template: '%s — nano-css', + }, + description: + 'nano-css is a tiny 5th generation CSS-in-JS library with a ~0.5 Kb core and 40+ addons. Framework agnostic, isomorphic, and fast.', +} + +const navbar = ( + + nano-css + + } + projectLink="https://github.com/streamich/nano-css" + /> +) + +const footer = ( + +) + +export default async function RootLayout({ children }) { + return ( + + + + + + + {children} + + + + ) +} diff --git a/site/content/_meta.js b/site/content/_meta.js new file mode 100644 index 00000000..dae9cdfe --- /dev/null +++ b/site/content/_meta.js @@ -0,0 +1,22 @@ +export default { + index: { + title: 'Home', + type: 'page', + theme: { + layout: 'full', + timestamp: false, + breadcrumb: false, + pagination: false, + toc: false, + sidebar: false, + }, + }, + docs: { + title: 'Documentation', + type: 'page', + }, + github_link: { + title: 'GitHub', + href: 'https://github.com/streamich/nano-css', + }, +} diff --git a/site/content/docs/_meta.js b/site/content/docs/_meta.js new file mode 100644 index 00000000..ef35dde5 --- /dev/null +++ b/site/content/docs/_meta.js @@ -0,0 +1,7 @@ +export default { + 'getting-started': 'Getting Started', + core: 'Core API', + styling: 'Styling Components', + addons: 'Addons', + advanced: 'Advanced', +} diff --git a/site/content/docs/addons/_meta.js b/site/content/docs/addons/_meta.js new file mode 100644 index 00000000..3df9c493 --- /dev/null +++ b/site/content/docs/addons/_meta.js @@ -0,0 +1,17 @@ +export default { + atoms: 'atoms', + emmet: 'emmet', + nesting: 'nesting', + keyframes: 'keyframes()', + prefixer: 'prefixer', + stable: 'stable', + unitless: 'unitless', + important: '!important', + global: ':global', + array: 'array', + snake: 'snake', + tachyons: 'tachyons', + stylis: 'stylis', + units: '.units', + sourcemaps: 'sourcemaps', +} diff --git a/site/content/docs/addons/array.mdx b/site/content/docs/addons/array.mdx new file mode 100644 index 00000000..a8e9e79b --- /dev/null +++ b/site/content/docs/addons/array.mdx @@ -0,0 +1,45 @@ +--- +title: array +--- + +# array + +Allows specifying multiple values for the same property and merging multiple CSS objects. + +## Multiple Values + +```js +nano.put('.foo', { + display: ['flex', '-webkit-flex'], +}); +``` + +```css +.foo { + display: flex; + display: -webkit-flex; +} +``` + +## Merging Objects + +```js +nano.put('.bar', [ + { color: 'red' }, + { border: '1px solid red' }, +]); +``` + +```css +.bar { + color: red; + border: 1px solid red; +} +``` + +## Installation + +```js +import { addon as addonArray } from 'nano-css/addon/array'; +addonArray(nano); +``` diff --git a/site/content/docs/addons/atoms.mdx b/site/content/docs/addons/atoms.mdx new file mode 100644 index 00000000..c7a10c73 --- /dev/null +++ b/site/content/docs/addons/atoms.mdx @@ -0,0 +1,69 @@ +--- +title: atoms +--- + +# atoms + +The `atoms` addon provides shorthand property names for CSS declarations, improving DX and reducing bundle size. + +```js +const className = nano.rule({ + bdt: '1px solid red', // border-top + col: 'blue', // color + ta: 'center', // text-align +}); +``` + +## Atom List + +| Atom | CSS Property | +|------|-------------| +| `d` | `display` | +| `mar` | `margin` | +| `mart` | `margin-top` | +| `marr` | `margin-right` | +| `marb` | `margin-bottom` | +| `marl` | `margin-left` | +| `pad` | `padding` | +| `padt` | `padding-top` | +| `padr` | `padding-right` | +| `padb` | `padding-bottom` | +| `padl` | `padding-left` | +| `bd` | `border` | +| `bdt` | `border-top` | +| `bdr` | `border-right` | +| `bdb` | `border-bottom` | +| `bdl` | `border-left` | +| `bdrad` | `border-radius` | +| `col` | `color` | +| `op` | `opacity` | +| `bg` | `background` | +| `bgc` | `background-color` | +| `fz` | `font-size` | +| `fs` | `font-style` | +| `fw` | `font-weight` | +| `ff` | `font-family` | +| `lh` | `line-height` | +| `bxz` | `box-sizing` | +| `cur` | `cursor` | +| `ov` | `overflow` | +| `pos` | `position` | +| `ls` | `list-style` | +| `ta` | `text-align` | +| `td` | `text-decoration` | +| `fl` | `float` | +| `w` | `width` | +| `h` | `height` | +| `trs` | `transition` | +| `out` | `outline` | +| `vis` | `visibility` | +| `ww` | `word-wrap` | +| `con` | `content` | +| `z` | `z-index` | + +## Installation + +```js +import { addon as addonAtoms } from 'nano-css/addon/atoms'; +addonAtoms(nano); +``` diff --git a/site/content/docs/addons/emmet.mdx b/site/content/docs/addons/emmet.mdx new file mode 100644 index 00000000..7c043926 --- /dev/null +++ b/site/content/docs/addons/emmet.mdx @@ -0,0 +1,26 @@ +--- +title: emmet +--- + +# emmet + +A superset of the `atoms` addon using [Emmet](https://emmet.io/) abbreviations. + +```js +const className = nano.rule({ + c: '#fafafa', // color + bgc: '#424242', // background-color + bd: '1px solid #424242', // border +}); +``` + +> **Warning:** Do not use both `atoms` and `emmet` at the same time — some abbreviations may conflict. + +See the [Emmet cheatsheet](https://docs.emmet.io/cheat-sheet/) (CSS section) for all abbreviations. + +## Installation + +```js +import { addon as addonEmmet } from 'nano-css/addon/emmet'; +addonEmmet(nano); +``` diff --git a/site/content/docs/addons/global.mdx b/site/content/docs/addons/global.mdx new file mode 100644 index 00000000..149b50df --- /dev/null +++ b/site/content/docs/addons/global.mdx @@ -0,0 +1,42 @@ +--- +title: ":global" +--- + +# :global + +Allows emitting global CSS styles without nesting. + +## Using `:global` Selector + +```js +nano.put('.foo', { + color: 'red', + ':global': { + '.global_class': { + border: '1px solid red', + }, + }, +}); +``` + +```css +.foo { color: red; } +.global_class { border: 1px solid red; } +``` + +## Using `global()` Function + +```js +nano.global({ + '.global_class': { + border: '1px solid red', + }, +}); +``` + +## Installation + +```js +import { addon as addonGlobal } from 'nano-css/addon/global'; +addonGlobal(nano); +``` diff --git a/site/content/docs/addons/important.mdx b/site/content/docs/addons/important.mdx new file mode 100644 index 00000000..49c5fbaa --- /dev/null +++ b/site/content/docs/addons/important.mdx @@ -0,0 +1,14 @@ +--- +title: "!important" +--- + +# !important + +Adds `!important` to every style declaration. + +## Installation + +```js +import { addon as addonImportant } from 'nano-css/addon/important'; +addonImportant(nano); +``` diff --git a/site/content/docs/addons/index.mdx b/site/content/docs/addons/index.mdx new file mode 100644 index 00000000..c600a69c --- /dev/null +++ b/site/content/docs/addons/index.mdx @@ -0,0 +1,82 @@ +--- +title: Addons Overview +asIndexPage: true +--- + +# Addons + +nano-css ships with a single pre-installed addon (`put()`). All other features are provided through addons. Each addon is a function that extends the renderer. + +## Installation Pattern + +All addons are in `nano-css/addon/` and export an `addon` named function: + +```js +import { addon as addonRule } from 'nano-css/addon/rule'; + +addonRule(nano); +nano.rule({ color: 'red' }); +``` + +## Available Addons + +### Core API + +| Addon | Description | +|-------|-------------| +| [`put()`](/docs/core/put) | Inject CSS by selector — pre-installed | +| [`rule()`](/docs/core/rule) | Generate class names from CSS objects | +| [`drule()`](/docs/core/drule) | Dynamic rules with runtime overrides | +| [`sheet()`](/docs/core/sheet) | Lazy collection of rules | +| [`dsheet()`](/docs/core/dsheet) | Dynamic sheets with runtime overrides | + +### Styling Components + +| Addon | Description | +|-------|-------------| +| [`jsx()`](/docs/styling/jsx) | Styled components for virtual DOM | +| [`style()`](/docs/styling/style) | Styled components with dynamic templates | +| [`styled()()`](/docs/styling/styled) | Familiar styled-components API | +| [`Component`](/docs/styling/component) | React Component base class | +| [`@css`](/docs/styling/decorator) | Decorator for class components | +| [`useStyles()`](/docs/styling/use-styles) | Styles as second component argument | +| [`withStyles()`](/docs/styling/with-styles) | HOC that injects styles prop | +| [`hyperstyle()`](/docs/styling/hyperstyle) | Styled hyperscript function | + +### Utilities + +| Addon | Description | +|-------|-------------| +| [`atoms`](/docs/addons/atoms) | CSS property shorthands | +| [`emmet`](/docs/addons/emmet) | Emmet-style abbreviations | +| [`nesting`](/docs/addons/nesting) | Advanced nesting with `&` | +| [`keyframes()`](/docs/addons/keyframes) | `@keyframes` support | +| [`prefixer`](/docs/addons/prefixer) | Vendor prefix auto-detection | +| [`stable`](/docs/addons/stable) | Deterministic class name hashing | +| [`unitless`](/docs/addons/unitless) | Auto-add `px` to numeric values | +| [`!important`](/docs/addons/important) | Add `!important` to all declarations | +| [`:global`](/docs/addons/global) | Emit global styles | +| [`array`](/docs/addons/array) | Multiple values for declarations | +| [`snake`](/docs/addons/snake) | Method chaining for CSS objects | +| [`tachyons`](/docs/addons/tachyons) | Tachyons-style utilities | +| [`stylis`](/docs/addons/stylis) | Write CSS as strings | +| [`.units`](/docs/addons/units) | Unit helper functions | +| [`sourcemaps`](/docs/addons/sourcemaps) | CSS source maps in dev | + +### Advanced + +| Addon | Description | +|-------|-------------| +| [`hydrate()`](/docs/advanced/hydrate) | Client-side CSS re-hydration | +| [`virtual`](/docs/advanced/virtual) | Atomic CSS splitting | +| [`cssom`](/docs/advanced/cssom) | CSSOM rule creation | +| [`vcssom`](/docs/advanced/vcssom) | Virtual CSSOM diffing | +| [`spread()`](/docs/advanced/spread) | Data attribute selectors | +| [`extract`](/docs/advanced/extract) | Build-time CSS extraction | +| [`limit`](/docs/advanced/limit) | Server-side size limit | +| [`amp`](/docs/advanced/amp) | AMP compatibility | +| [`rtl`](/docs/advanced/rtl) | Right-to-left style flipping | +| [`googleFont()`](/docs/advanced/google-font) | Load Google Fonts | +| [`animate/*`](/docs/advanced/animations) | Animation presets | +| [`reset/*`](/docs/advanced/resets) | CSS reset collections | +| [`reset-font`](/docs/advanced/reset-font) | Font reset styles | diff --git a/site/content/docs/addons/keyframes.mdx b/site/content/docs/addons/keyframes.mdx new file mode 100644 index 00000000..be719699 --- /dev/null +++ b/site/content/docs/addons/keyframes.mdx @@ -0,0 +1,51 @@ +--- +title: keyframes() +--- + +# keyframes() + +Adds `@keyframes` support to nano-css. + +## Inline Keyframes + +Define keyframes directly in a rule: + +```js +const className = nano.rule({ + animation: 'my-animation 2s', + '@keyframes my-animation': { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(359deg)' }, + }, +}); +``` + +## Named Keyframes + +Generate a unique animation name automatically: + +```js +const animation = nano.keyframes({ + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(359deg)' }, +}); + +const className = nano.rule({ + animation: `${animation} 5s`, +}); +``` + +## Configuration + +```js +addonKeyframes(nano, { + prefixes: ['-webkit-', '-moz-', '-o-', ''], +}); +``` + +## Installation + +```js +import { addon as addonKeyframes } from 'nano-css/addon/keyframes'; +addonKeyframes(nano); +``` diff --git a/site/content/docs/addons/nesting.mdx b/site/content/docs/addons/nesting.mdx new file mode 100644 index 00000000..53a954be --- /dev/null +++ b/site/content/docs/addons/nesting.mdx @@ -0,0 +1,50 @@ +--- +title: nesting +--- + +# nesting + +Extends the built-in nesting with comma-separated selectors and the `&` parent selector. + +## Comma-Separated Selectors + +```js +nano.put('.foo', { + '.bar, .baz': { + color: 'red', + }, +}); +``` + +```css +.foo .bar, .foo .baz { + color: red; +} +``` + +## Parent Selector `&` + +```js +nano.put('.foo', { + color: 'red', + '&:hover': { + color: 'blue', + }, + '.global_class &': { + color: 'green', + }, +}); +``` + +```css +.foo { color: red; } +.foo:hover { color: blue; } +.global_class .foo { color: green; } +``` + +## Installation + +```js +import { addon as addonNesting } from 'nano-css/addon/nesting'; +addonNesting(nano); +``` diff --git a/site/content/docs/addons/prefixer.mdx b/site/content/docs/addons/prefixer.mdx new file mode 100644 index 00000000..2f3b70d8 --- /dev/null +++ b/site/content/docs/addons/prefixer.mdx @@ -0,0 +1,27 @@ +--- +title: prefixer +--- + +# prefixer + +Auto-prefixes styles with vendor prefixes using `inline-style-prefixer`. + +```js +nano.put('.foo', { + flex: 1, +}); +``` + +```css +.foo { + -webkit-flex: 1; + flex: 1; +} +``` + +## Installation + +```js +import { addon as addonPrefixer } from 'nano-css/addon/prefixer'; +addonPrefixer(nano); +``` diff --git a/site/content/docs/addons/snake.mdx b/site/content/docs/addons/snake.mdx new file mode 100644 index 00000000..97c88467 --- /dev/null +++ b/site/content/docs/addons/snake.mdx @@ -0,0 +1,54 @@ +--- +title: snake +--- + +# snake + +Create CSS objects using method chaining. + +```js +const css = nano.s.col('red').bdrad('5px').bgWhite.mar('10px').pointer.obj; +// → { color: 'red', 'border-radius': '5px', backgroundColor: '#fff', +// margin: '10px', cursor: 'pointer' } +``` + +## Built-in Properties + +| Property | Result | +|----------|--------| +| `.rel` | `position: relative` | +| `.abs` | `position: absolute` | +| `.bgWhite` | `backgroundColor: '#fff'` | +| `.bgBlack` | `backgroundColor: '#000'` | +| `.pointer` | `cursor: pointer` | +| `.bold`, `.b` | `fontWeight: bold` | +| `.italic`, `.i` | `fontStyle: italic` | +| `.underline`, `.u` | `textDecoration: underline` | + +All `atoms` shorthands are available as methods: + +```js +nano.s.bg('pink').pad('20px').col('red'); +``` + +## Pseudo Selectors + +```js +nano.s.hover(nano.s.col('red')).focus(nano.s.col('yellow')); +``` + +## Evaluating + +Get the CSS object with `.obj`, or inject and get class names: + +```jsx +
text
+
text
+``` + +## Installation + +```js +import { addon as addonSnake } from 'nano-css/addon/snake'; +addonSnake(nano); +``` diff --git a/site/content/docs/addons/sourcemaps.mdx b/site/content/docs/addons/sourcemaps.mdx new file mode 100644 index 00000000..57e74f39 --- /dev/null +++ b/site/content/docs/addons/sourcemaps.mdx @@ -0,0 +1,22 @@ +--- +title: sourcemaps +--- + +# sourcemaps + +Generates CSS source maps in development mode for easier debugging. + +> **Warning:** Do not use in production. Gate it behind an environment check: + +```js +if (process.env.NODE_ENV !== 'production') { + addonSourcemaps(nano); +} +``` + +## Installation + +```js +import { addon as addonSourcemaps } from 'nano-css/addon/sourcemaps'; +addonSourcemaps(nano); +``` diff --git a/site/content/docs/addons/stable.mdx b/site/content/docs/addons/stable.mdx new file mode 100644 index 00000000..830c11a8 --- /dev/null +++ b/site/content/docs/addons/stable.mdx @@ -0,0 +1,18 @@ +--- +title: stable +--- + +# stable + +Ensures deterministic class name generation across JavaScript environments by using `fastest-stable-stringify` instead of `JSON.stringify`. + +This is important for server-side rendering — without it, class names generated on the server may not match the client. + +## Installation + +```js +import { addon as addonStable } from 'nano-css/addon/stable'; +addonStable(nano); +``` + +> **Tip:** If you name all your rules explicitly (e.g. `rule(css, 'MyButton')`), you don't need this addon. diff --git a/site/content/docs/addons/stylis.mdx b/site/content/docs/addons/stylis.mdx new file mode 100644 index 00000000..43411981 --- /dev/null +++ b/site/content/docs/addons/stylis.mdx @@ -0,0 +1,25 @@ +--- +title: stylis +--- + +# stylis + +Write CSS as strings using the [Stylis](https://github.com/thysultan/stylis.js) preprocessor. + +```js +const className = nano.rule(` + color: red; + &:hover { + color: blue; + } +`); +``` + +> **Note:** Some other addons may not work when using Stylis string syntax. + +## Installation + +```js +import { addon as addonStylis } from 'nano-css/addon/stylis'; +addonStylis(nano); +``` diff --git a/site/content/docs/addons/tachyons.mdx b/site/content/docs/addons/tachyons.mdx new file mode 100644 index 00000000..45ed14b3 --- /dev/null +++ b/site/content/docs/addons/tachyons.mdx @@ -0,0 +1,22 @@ +--- +title: tachyons +--- + +# tachyons + +Adds [Tachyons](https://tachyons.io/) utility rules to the `snake` chain. + +```jsx +
+ Hello world +
+``` + +Installs the `snake` addon automatically. + +## Installation + +```js +import { addon as addonTachyons } from 'nano-css/addon/tachyons'; +addonTachyons(nano); +``` diff --git a/site/content/docs/addons/unitless.mdx b/site/content/docs/addons/unitless.mdx new file mode 100644 index 00000000..8c0a9ac3 --- /dev/null +++ b/site/content/docs/addons/unitless.mdx @@ -0,0 +1,30 @@ +--- +title: unitless +--- + +# unitless + +Automatically appends `px` to numeric values for properties that require units. + +```js +nano.put('.foo', { + width: 50, + zIndex: 10, +}); +``` + +```css +.foo { + width: 50px; + z-index: 10; +} +``` + +Properties like `opacity`, `z-index`, `flex`, etc. remain unitless. + +## Installation + +```js +import { addon as addonUnitless } from 'nano-css/addon/unitless'; +addonUnitless(nano); +``` diff --git a/site/content/docs/addons/units.mdx b/site/content/docs/addons/units.mdx new file mode 100644 index 00000000..ca7907d6 --- /dev/null +++ b/site/content/docs/addons/units.mdx @@ -0,0 +1,31 @@ +--- +title: ".units" +--- + +# .units + +Helper functions for CSS units. + +```js +nano.px(36); // '36px' +nano.em(1); // '1em' +nano.pct(25); // '25%' +nano.inch(3); // '3in' +``` + +Also available on the `.units` property: + +```js +nano.units.px(36); +nano.units.em(1); +nano.units.pct(25); +``` + +Supports all CSS units. Special names: `.pct()` for `%` and `.inch()` for `in`. + +## Installation + +```js +import { addon as addonUnits } from 'nano-css/addon/units'; +addonUnits(nano); +``` diff --git a/site/content/docs/advanced/_meta.js b/site/content/docs/advanced/_meta.js new file mode 100644 index 00000000..2dfe733d --- /dev/null +++ b/site/content/docs/advanced/_meta.js @@ -0,0 +1,16 @@ +export default { + ssr: 'Server-Side Rendering', + hydrate: 'hydrate()', + virtual: 'virtual', + cssom: 'cssom', + vcssom: 'vcssom', + spread: 'spread()', + extract: 'extract', + limit: 'limit', + amp: 'amp', + rtl: 'rtl', + 'google-font': 'googleFont()', + animations: 'Animations', + resets: 'CSS Resets', + 'reset-font': 'reset-font', +} diff --git a/site/content/docs/advanced/amp.mdx b/site/content/docs/advanced/amp.mdx new file mode 100644 index 00000000..2e168563 --- /dev/null +++ b/site/content/docs/advanced/amp.mdx @@ -0,0 +1,26 @@ +--- +title: amp +--- + +# amp + +Enforces AMP style sheet restrictions: + +- Limits style sheet size to 50 Kb +- Warns/removes `!important` modifiers +- Warns/removes banned declarations (`behavior`, `-moz-binding`) +- Warns/removes reserved selectors (`.-amp-*`, `i-amp-*`) + +```js +import { addon as addonAmp } from 'nano-css/addon/amp'; + +addonAmp(nano); + +// With options +addonAmp(nano, { + limit: 50000, + removeImportant: true, + removeReserved: true, + removeBanned: true, +}); +``` diff --git a/site/content/docs/advanced/animations.mdx b/site/content/docs/advanced/animations.mdx new file mode 100644 index 00000000..fc06d9d2 --- /dev/null +++ b/site/content/docs/advanced/animations.mdx @@ -0,0 +1,36 @@ +--- +title: Animations +--- + +# Animations + +Pre-built CSS animation addons. Install any to get a class name and keyframes. + +## Available Animations + +| Addon | Effect | +|-------|--------| +| `animate/fadeIn` | Fade in (opacity 0 → 1) | +| `animate/fadeInDown` | Fade in from above | +| `animate/fadeInScale` | Fade in with scale | +| `animate/fadeOut` | Fade out (opacity 1 → 0) | +| `animate/fadeOutScale` | Fade out with scale | + +## Usage + +```js +import { addon as addonFadeIn } from 'nano-css/addon/animate/fadeIn'; +addonFadeIn(nano); +``` + +Use as a class name: + +```html +
Hello world!
+``` + +Or as a custom animation: + +```html +
Hello world!
+``` diff --git a/site/content/docs/advanced/cssom.mdx b/site/content/docs/advanced/cssom.mdx new file mode 100644 index 00000000..03e9b0f7 --- /dev/null +++ b/site/content/docs/advanced/cssom.mdx @@ -0,0 +1,29 @@ +--- +title: cssom +--- + +# cssom + +Creates `CSSRule` objects in the browser for direct style manipulation. + +```js +const rule = nano.createRule('.my-component'); +rule.style.color = 'red'; +rule.style.setProperty('color', 'green'); +``` + +## With Media Queries + +```js +const rule = nano.createRule( + '.my-component', + '@media only screen and (max-width: 600px)' +); +``` + +## Installation + +```js +import { addon as addonCssom } from 'nano-css/addon/cssom'; +addonCssom(nano); +``` diff --git a/site/content/docs/advanced/extract.mdx b/site/content/docs/advanced/extract.mdx new file mode 100644 index 00000000..e64a55bd --- /dev/null +++ b/site/content/docs/advanced/extract.mdx @@ -0,0 +1,19 @@ +--- +title: extract +--- + +# extract + +Enables CSS extraction into external style sheets at build time by: + +- Eagerly injecting styles from `sheet()` (normally lazy) +- Rendering `jsx()` and `style()` components with default props + +This is a low-level building block for build-time extraction workflows. + +## Installation + +```js +import { addon as addonExtract } from 'nano-css/addon/extract'; +addonExtract(nano); +``` diff --git a/site/content/docs/advanced/google-font.mdx b/site/content/docs/advanced/google-font.mdx new file mode 100644 index 00000000..1b3a7049 --- /dev/null +++ b/site/content/docs/advanced/google-font.mdx @@ -0,0 +1,32 @@ +--- +title: googleFont() +--- + +# googleFont() + +Loads fonts from Google Fonts. + +```ts +googleFont(name: string, widths?: number | number[], subsets?: string | string[]) +``` + +## Example + +```js +nano.googleFont('Roboto Slab', [400, 700], 'cyrillic'); +``` + +Then use it: + +```js +const className = nano.rule({ + fontFamily: '"Roboto Slab", sans', +}); +``` + +## Installation + +```js +import { addon as addonGoogleFont } from 'nano-css/addon/googleFont'; +addonGoogleFont(nano); +``` diff --git a/site/content/docs/advanced/hydrate.mdx b/site/content/docs/advanced/hydrate.mdx new file mode 100644 index 00000000..55afa113 --- /dev/null +++ b/site/content/docs/advanced/hydrate.mdx @@ -0,0 +1,48 @@ +--- +title: hydrate() +--- + +# hydrate() + +Re-hydrates CSS styles generated on the server. + +## Setup + +Add the `nano-css` id to your server-rendered style sheet: + +```js +html += ``; +``` + +Provide the style sheet element when creating the renderer: + +```js +const nano = create({ + sh: typeof document === 'object' + ? document.getElementById('nano-css') + : null, +}); +``` + +nano-css will not inject rules that are already present. + +## Manual Hydration + +Hydrate any external stylesheet: + +```html + +``` + +```js +nano.hydrate(document.getElementById('extracted-css')); +``` + +> Does not currently hydrate media queries or animation keyframes. + +## Installation + +```js +import { addon as addonHydrate } from 'nano-css/addon/hydrate'; +addonHydrate(nano); +``` diff --git a/site/content/docs/advanced/limit.mdx b/site/content/docs/advanced/limit.mdx new file mode 100644 index 00000000..e248fcf2 --- /dev/null +++ b/site/content/docs/advanced/limit.mdx @@ -0,0 +1,17 @@ +--- +title: limit +--- + +# limit + +Limits the total size of server-side generated CSS in bytes. + +```js +import { addon as addonLimit } from 'nano-css/addon/limit'; + +// Default: 50,000 bytes (50 Kb — Google AMP maximum) +addonLimit(nano); + +// Custom limit +addonLimit(nano, 100000); +``` diff --git a/site/content/docs/advanced/reset-font.mdx b/site/content/docs/advanced/reset-font.mdx new file mode 100644 index 00000000..2bf21906 --- /dev/null +++ b/site/content/docs/advanced/reset-font.mdx @@ -0,0 +1,16 @@ +--- +title: reset-font +--- + +# reset-font + +Injects global styles for better font rendering: + +- Sets a clean sans-serif font stack +- Enables font smoothing (`-webkit-font-smoothing: antialiased`) +- Normalizes text size adjustment + +```js +import { addon as addonResetFont } from 'nano-css/addon/reset-font'; +addonResetFont(nano); +``` diff --git a/site/content/docs/advanced/resets.mdx b/site/content/docs/advanced/resets.mdx new file mode 100644 index 00000000..ee7b0d0a --- /dev/null +++ b/site/content/docs/advanced/resets.mdx @@ -0,0 +1,28 @@ +--- +title: CSS Resets +--- + +# CSS Resets + +Install any reset addon to inject a CSS reset on the page. + +## Available Resets + +- `reset/EricMeyer` +- `reset/EricMeyerCondensed` +- `reset/Minimalistic` +- `reset/Minimalistic2` +- `reset/Minimalistic3` +- `reset/PoorMan` +- `reset/ShaunInman` +- `reset/Siolon` +- `reset/Tantek` +- `reset/Tripoli` +- `reset/Universal` + +## Example + +```js +import { addon as addonReset } from 'nano-css/addon/reset/EricMeyerCondensed'; +addonReset(nano); +``` diff --git a/site/content/docs/advanced/rtl.mdx b/site/content/docs/advanced/rtl.mdx new file mode 100644 index 00000000..49535f33 --- /dev/null +++ b/site/content/docs/advanced/rtl.mdx @@ -0,0 +1,28 @@ +--- +title: rtl +--- + +# rtl + +Flips all styles for right-to-left (RTL) layout support. + +```js +nano.put('.foo', { + textAlign: 'left', + marginLeft: '100px', +}); +``` + +```css +.foo { + text-align: right; + margin-right: 100px; +} +``` + +## Installation + +```js +import { addon as addonRtl } from 'nano-css/addon/rtl'; +addonRtl(nano); +``` diff --git a/site/content/docs/advanced/spread.mdx b/site/content/docs/advanced/spread.mdx new file mode 100644 index 00000000..0bc67ab7 --- /dev/null +++ b/site/content/docs/advanced/spread.mdx @@ -0,0 +1,26 @@ +--- +title: spread() +--- + +# spread() + +Like `rule()`, but returns an object with data attributes that can be spread. + +```jsx +const rule = nano.spread({ color: 'red' }); + +
Hello world!
+``` + +Can also be stringified for a class name: + +```jsx +
Hello world!
+``` + +## Installation + +```js +import { addon as addonSpread } from 'nano-css/addon/spread'; +addonSpread(nano); +``` diff --git a/site/content/docs/advanced/ssr.mdx b/site/content/docs/advanced/ssr.mdx new file mode 100644 index 00000000..a8aef8d8 --- /dev/null +++ b/site/content/docs/advanced/ssr.mdx @@ -0,0 +1,29 @@ +--- +title: Server-Side Rendering +--- + +# Server-Side Rendering + +In non-browser environments, nano-css accumulates CSS as a string in the `raw` property instead of injecting into the DOM. + +```js +html += ``; +``` + +## Re-hydrating + +Install the [hydrate](/docs/advanced/hydrate) addon and provide your style sheet element: + +```js +const nano = create({ + sh: typeof document === 'object' + ? document.getElementById('nano-css') + : null, +}); +``` + +nano-css will skip CSS rules that are already present in the style sheet. + +## External Style Sheet Extraction + +Use the [extract](/docs/advanced/extract) addon to extract styles into an external CSS file at build time. diff --git a/site/content/docs/advanced/vcssom.mdx b/site/content/docs/advanced/vcssom.mdx new file mode 100644 index 00000000..c1aac290 --- /dev/null +++ b/site/content/docs/advanced/vcssom.mdx @@ -0,0 +1,47 @@ +--- +title: vcssom +--- + +# vcssom + +Virtual CSSOM for efficient CSS diffing. The `.diff()` method computes differences and only adds or removes necessary rules. + +## VRule + +```js +const rule = new nano.VRule('.my-class'); + +rule.diff({ color: 'red', 'font-weight': 'bold' }); +rule.diff({ color: 'blue' }); // removes font-weight, updates color +rule.del(); // remove from CSSOM +``` + +## VSheet + +```js +const sheet = new nano.VSheet(); + +sheet.diff({ + '': { + '.my-class': { color: 'red' }, + '.my-class:hover': { color: 'blue' }, + }, + '@media only screen and (max-width: 600px)': { + '.my-class': { fontWeight: 'bold' }, + }, +}); + +sheet.diff({}); // removes all CSS +``` + +## Installation + +Requires the `cssom` addon: + +```js +import { addon as addonCssom } from 'nano-css/addon/cssom'; +import { addon as addonVcssom } from 'nano-css/addon/vcssom'; + +addonCssom(nano); +addonVcssom(nano); +``` diff --git a/site/content/docs/advanced/virtual.mdx b/site/content/docs/advanced/virtual.mdx new file mode 100644 index 00000000..7b8d3037 --- /dev/null +++ b/site/content/docs/advanced/virtual.mdx @@ -0,0 +1,47 @@ +--- +title: virtual +--- + +# virtual + +Splits CSS rules into **atomic single declarations**, each assigned its own class name and reused across rules. + +```js +const classNames1 = nano.rule({ + color: 'red', + border: '1px solid red', + textAlign: 'center', +}); +// → '_a _b _c' + +const classNames2 = nano.rule({ + border: '1px solid red', +}); +// → '_b' (reused!) +``` + +## `.atomic()` + +```js +nano.atomic('&', 'color:red'); // _a +nano.atomic('&:hover', 'color:blue'); // _b +nano.atomic('&', 'color:red'); // _a (cached) +``` + +## `.virtual()` + +```js +const classNames = nano.virtual('&', { + color: 'red', + border: '1px solid red', + textAlign: 'center', +}); +// → '_a _b _c' +``` + +## Installation + +```js +import { addon as addonVirtual } from 'nano-css/addon/virtual'; +addonVirtual(nano); +``` diff --git a/site/content/docs/core/_meta.js b/site/content/docs/core/_meta.js new file mode 100644 index 00000000..91831719 --- /dev/null +++ b/site/content/docs/core/_meta.js @@ -0,0 +1,7 @@ +export default { + put: 'put()', + rule: 'rule()', + drule: 'drule()', + sheet: 'sheet()', + dsheet: 'dsheet()', +} diff --git a/site/content/docs/core/drule.mdx b/site/content/docs/core/drule.mdx new file mode 100644 index 00000000..5dad50af --- /dev/null +++ b/site/content/docs/core/drule.mdx @@ -0,0 +1,68 @@ +--- +title: drule() +--- + +# drule() + +`drule()` (*Dynamic Rule*) is similar to `rule()` but allows adding style overrides at render time. + +```js +const classNames = nano.drule({ + border: '1px solid #888', + color: '#888', +}); +``` + +## Static Usage + +When called without arguments, it behaves like `rule()`: + +```jsx + + +``` + +## Dynamic Overrides + +Pass a CSS object to add overrides on the fly: + +```jsx + +``` + +## Example + +A button that changes color based on props: + +```jsx +const classNames = nano.drule({ + border: '1px solid #888', + color: '#fff', +}); + +const Button = ({ children, primary }) => ( + +); +``` + +## Installation + +Requires `rule` and `cache` addons: + +```js +import { create } from 'nano-css'; +import { addon as addonCache } from 'nano-css/addon/cache'; +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonDrule } from 'nano-css/addon/drule'; + +const nano = create(); +addonCache(nano); +addonRule(nano); +addonDrule(nano); +``` diff --git a/site/content/docs/core/dsheet.mdx b/site/content/docs/core/dsheet.mdx new file mode 100644 index 00000000..a670c682 --- /dev/null +++ b/site/content/docs/core/dsheet.mdx @@ -0,0 +1,60 @@ +--- +title: dsheet() +--- + +# dsheet() + +`dsheet()` (*Dynamic Sheet*) combines `sheet()` with dynamic overrides, similar to what `drule()` does for `rule()`. + +```js +const styles = nano.dsheet({ + input: { + border: '1px solid grey', + }, + button: { + border: '1px solid red', + color: 'red', + }, +}); +``` + +## Usage + +Access styles statically or with overrides: + +```jsx +{/* Static */} + + + +{/* With dynamic overrides */} + +``` + +## Naming + +```js +const styles = nano.dsheet(cssMap, 'ContactForm'); +``` + +## Installation + +Requires `cache`, `rule`, and `sheet` addons: + +```js +import { addon as addonCache } from 'nano-css/addon/cache'; +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonSheet } from 'nano-css/addon/sheet'; +import { addon as addonDsheet } from 'nano-css/addon/dsheet'; + +addonCache(nano); +addonRule(nano); +addonSheet(nano); +addonDsheet(nano); +``` diff --git a/site/content/docs/core/put.mdx b/site/content/docs/core/put.mdx new file mode 100644 index 00000000..b1374148 --- /dev/null +++ b/site/content/docs/core/put.mdx @@ -0,0 +1,90 @@ +--- +title: put() +--- + +# put() + +`put()` is the only addon that comes pre-installed with nano-css. It injects CSS given a selector and a CSS-like object. + +```js +nano.put('.foo', { + color: 'red', + ':hover': { + color: 'blue', + }, +}); +``` + +```html +
Hover me!
+``` + +## CSS-like Objects + +The second argument is a **CSS-like object** that maps directly to CSS: + +```js +{ + color: 'red', + ':hover': { + color: 'blue', + }, +} +``` + +Equivalent CSS: + +```css +.selector { + color: red; +} +.selector:hover { + color: blue; +} +``` + +## Nesting + +`put()` supports arbitrarily deep nesting out of the box: + +```js +nano.put('.card', { + div: { + span: { + ':hover': { + color: 'blue', + }, + }, + }, +}); +``` + +Result: + +```css +.card div span:hover { + color: blue; +} +``` + +> For advanced nesting with `&` selectors and comma separation, install the [nesting](/docs/addons/nesting) addon. + +## Property Syntax + +Both kebab-case and camelCase property names are supported: + +```js +// kebab-case +nano.put('.a', { 'text-decoration': 'none' }); + +// camelCase +nano.put('.b', { textDecoration: 'none' }); +``` + +## At-Rules + +Pass an at-rule string as the third argument: + +```js +nano.put('.responsive', { fontSize: '14px' }, '@media (max-width: 600px)'); +``` diff --git a/site/content/docs/core/rule.mdx b/site/content/docs/core/rule.mdx new file mode 100644 index 00000000..ed03b059 --- /dev/null +++ b/site/content/docs/core/rule.mdx @@ -0,0 +1,59 @@ +--- +title: rule() +--- + +# rule() + +`rule()` is a wrapper around `put()` that automatically generates a unique class name and returns it. + +```js +const className = nano.rule({ + color: 'tomato', + ':hover': { + color: 'blue', + }, +}); +``` + +```html +
Hello world!
+``` + +## Semantic Names + +Provide an optional name as the second argument for readable class names: + +```js +const className = nano.rule(css, 'RedText'); +// → ' pfx-RedText' +``` + +> If you name all your rules explicitly, you don't need the [stable](/docs/addons/stable) addon. + +## Leading Space + +nano-css always returns class names with a **leading space**, making concatenation easy: + +```jsx +const otherClass = 'foo'; +const className = rule(css); + +
Hello world!
+// →
+``` + +## Stable Class Names + +By default, class names are generated using `JSON.stringify`. For stable hashes across environments (important for SSR), install the [stable](/docs/addons/stable) addon. + +## Installation + +```js +import { create } from 'nano-css'; +import { addon } from 'nano-css/addon/rule'; + +const nano = create(); +addon(nano); + +const { rule } = nano; +``` diff --git a/site/content/docs/core/sheet.mdx b/site/content/docs/core/sheet.mdx new file mode 100644 index 00000000..14b65a6f --- /dev/null +++ b/site/content/docs/core/sheet.mdx @@ -0,0 +1,48 @@ +--- +title: sheet() +--- + +# sheet() + +`sheet()` lets you define multiple rules at once. Unlike `rule()`, styles are **lazily injected** — CSS is only added to the DOM when a class name is first accessed. + +```js +const styles = nano.sheet({ + input: { + border: '1px solid grey', + }, + button: { + border: '1px solid red', + color: 'red', + }, +}); +``` + +```jsx + + +``` + +> **Lazy injection:** CSS is not injected when `sheet()` is called. It's injected when you first access `styles.input` or `styles.button`. This means unused styles never make it to the DOM. + +## Semantic Names + +Name your sheet for readable class names: + +```js +const styles = sheet(cssMap, 'ContactForm'); + +console.log(styles.input); // → ' pfx-ContactForm-input' +``` + +## Installation + +Requires the `rule` addon: + +```js +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonSheet } from 'nano-css/addon/sheet'; + +addonRule(nano); +addonSheet(nano); +``` diff --git a/site/content/docs/getting-started/_meta.js b/site/content/docs/getting-started/_meta.js new file mode 100644 index 00000000..c004fa1e --- /dev/null +++ b/site/content/docs/getting-started/_meta.js @@ -0,0 +1,5 @@ +export default { + installation: 'Installation', + configuration: 'Configuration', + presets: 'Presets', +} diff --git a/site/content/docs/getting-started/configuration.mdx b/site/content/docs/getting-started/configuration.mdx new file mode 100644 index 00000000..03b4b285 --- /dev/null +++ b/site/content/docs/getting-started/configuration.mdx @@ -0,0 +1,69 @@ +--- +title: Configuration +--- + +# Configuration + +The `create()` function accepts an optional configuration object. + +```js +import { create } from 'nano-css'; + +const nano = create(config); +``` + +## Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `pfx` | `string` | `'_'` | Prefix added to all generated class and animation names | +| `h` | `function` | — | Hyperscript function from your virtual DOM library (e.g. `React.createElement`). Required for `jsx()`, `style()`, `styled()`, and `component` addons | +| `sh` | `HTMLElement` | — | DOM style sheet element for re-hydrating server-rendered styles | +| `verbose` | `boolean` | `false` | Enable verbose console output in development mode | + +## Examples + +### With React + +```js +import { createElement } from 'react'; +import { create } from 'nano-css'; + +const nano = create({ + pfx: 'my-company-', + h: createElement, +}); +``` + +### With Preact + +```js +import { h } from 'preact'; +import { create } from 'nano-css'; + +const nano = create({ + h: h, +}); +``` + +### With Server-Side Rendering + +```js +const nano = create({ + sh: typeof document === 'object' + ? document.getElementById('nano-css') + : null, +}); +``` + +## Built-in API + +The renderer object created by `create()` includes these properties out of the box (no addons required): + +| Property | Description | +|----------|-------------| +| `nano.put(selector, css, atrule?)` | Inject CSS by selector and CSS-like object | +| `nano.putRaw(rawCss)` | Inject a raw CSS string | +| `nano.raw` | Accumulated CSS string (useful for server-side rendering) | +| `nano.client` | `true` when running in a browser environment | +| `nano.pfx` | The configured class name prefix | diff --git a/site/content/docs/getting-started/installation.mdx b/site/content/docs/getting-started/installation.mdx new file mode 100644 index 00000000..7d8bfc50 --- /dev/null +++ b/site/content/docs/getting-started/installation.mdx @@ -0,0 +1,79 @@ +--- +title: Installation +--- + +# Installation + +Install nano-css via npm: + +```bash +npm i nano-css +``` + +## Basic Usage + +Create a renderer and start injecting styles: + +```js +import { create } from 'nano-css'; + +const nano = create(); + +nano.put('.test', { + color: 'red', + border: '1px solid red', +}); +``` + +```html +
Hello world!
+``` + +## Recommended Setup + +For a real project, create a dedicated `nano.js` file that configures and exports your renderer: + +```js +import { createElement } from 'react'; +import { create } from 'nano-css'; +import { addon as addonCache } from 'nano-css/addon/cache'; +import { addon as addonStable } from 'nano-css/addon/stable'; +import { addon as addonNesting } from 'nano-css/addon/nesting'; +import { addon as addonAtoms } from 'nano-css/addon/atoms'; +import { addon as addonKeyframes } from 'nano-css/addon/keyframes'; +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonSheet } from 'nano-css/addon/sheet'; +import { addon as addonJsx } from 'nano-css/addon/jsx'; + +const nano = create({ + pfx: 'my-app-', + h: createElement, +}); + +addonCache(nano); +addonStable(nano); +addonNesting(nano); +addonAtoms(nano); +addonKeyframes(nano); +addonRule(nano); +addonSheet(nano); +addonJsx(nano); + +const { rule, sheet, jsx, keyframes } = nano; + +export { nano, rule, sheet, jsx, keyframes }; +``` + +> **Tip:** Use [presets](/docs/getting-started/presets) to skip the manual addon setup. + +## Addon Installation Pattern + +All addons live in the `nano-css/addon/` folder and export an `addon` named export. An addon is a function that receives the renderer object: + +```js +import { addon as addonRule } from 'nano-css/addon/rule'; + +addonRule(nano); + +nano.rule({ color: 'red' }); +``` diff --git a/site/content/docs/getting-started/presets.mdx b/site/content/docs/getting-started/presets.mdx new file mode 100644 index 00000000..8f9ac353 --- /dev/null +++ b/site/content/docs/getting-started/presets.mdx @@ -0,0 +1,54 @@ +--- +title: Presets +--- + +# Presets + +Presets are pre-configured bundles of addons that set up nano-css for common use cases. Instead of manually installing each addon, use a preset to get started quickly. + +## Available Presets + +### `sheet` + +Installs `rule()`, `sheet()`, `stable`, `nesting`, `atoms`, `keyframes`, and `sourcemaps` addons. + +```js +import { preset } from 'nano-css/preset/sheet'; + +const { rule, sheet } = preset(); + +export { rule, sheet }; +``` + +### `vdom` + +Everything in the `sheet` preset, plus the `jsx()` addon for virtual DOM libraries. Requires providing an `h` function in the configuration. + +```js +import { createElement } from 'react'; +import { preset } from 'nano-css/preset/vdom'; + +const { rule, sheet, jsx } = preset({ h: createElement }); + +export { rule, sheet, jsx }; +``` + +### `react` + +Everything in the `vdom` preset, plus `snake`, `style()`, `styled()()`, and `decorator` addons. Automatically uses `React.createElement` as the hyperscript function. + +```js +import { preset } from 'nano-css/preset/react'; + +const { rule, sheet, jsx, styled } = preset(); + +export { rule, sheet, jsx, styled }; +``` + +## Comparison + +| Preset | Addons Included | +|--------|----------------| +| **sheet** | stable, nesting, atoms, keyframes, rule, sheet, sourcemaps | +| **vdom** | *sheet* + jsx (requires `h` in config) | +| **react** | *vdom* + snake, style, styled, decorator (auto-sets `h`) | diff --git a/site/content/docs/index.mdx b/site/content/docs/index.mdx new file mode 100644 index 00000000..1cb5474f --- /dev/null +++ b/site/content/docs/index.mdx @@ -0,0 +1,60 @@ +--- +title: Introduction +asIndexPage: true +--- + +# Introduction + +**nano-css** is a tiny 5th generation CSS-in-JS library. It has a very small core of only ~0.5 Kb and provides all additional features through a modular addon system. + +## Why nano-css? + +- **Tiny footprint** — ~0.5 Kb core vs ~15 Kb for styled-components +- **Framework agnostic** — works with React, Preact, Vue.js, or standalone +- **Isomorphic** — server and browser with stable class names and re-hydration +- **Fast** — uses `.insertRule()` and style caching for high performance +- **Modular** — pick only the addons you need, keep your bundle small +- **Full-featured** — `@media` queries, `@keyframes`, vendor prefixes, nesting, and more + +## How It Works + +nano-css follows a simple pattern: create a renderer, then extend it with addons. + +```js +import { create } from 'nano-css'; +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonAtoms } from 'nano-css/addon/atoms'; + +const nano = create(); +addonRule(nano); +addonAtoms(nano); + +const className = nano.rule({ + col: 'red', // atoms shorthand for "color" + bdrad: '4px', // atoms shorthand for "border-radius" +}); +``` + +Each addon is a function that receives the renderer object and extends it with new methods or behavior. + +## Architecture + +The library is organized into several layers: + +| Layer | Examples | Description | +|-------|----------|-------------| +| **Core** | `put()`, `putRaw()` | The minimal renderer — inject CSS by selector | +| **Rules** | `rule()`, `drule()`, `sheet()`, `dsheet()` | Generate class names from CSS objects | +| **Styling** | `jsx()`, `style()`, `styled()` | Create styled components for virtual DOM libraries | +| **Utilities** | `atoms`, `nesting`, `keyframes`, `prefixer` | Enhance CSS authoring with shortcuts and features | +| **Advanced** | `virtual`, `vcssom`, `hydrate`, `extract` | Performance optimizations and build-time tools | + +## Getting Started + +Head to the [Installation](/docs/getting-started/installation) guide to set up nano-css in your project, +or explore the [Addons](/docs/addons) to see what's available. + +## Recommended Setup + +For most projects, we recommend using a [preset](/docs/getting-started/presets) or setting up a +dedicated configuration file. See the [Installation](/docs/getting-started/installation) guide for details. diff --git a/site/content/docs/styling/_meta.js b/site/content/docs/styling/_meta.js new file mode 100644 index 00000000..afb00f4e --- /dev/null +++ b/site/content/docs/styling/_meta.js @@ -0,0 +1,10 @@ +export default { + jsx: 'jsx()', + style: 'style()', + styled: 'styled()()', + component: 'Component', + decorator: '@css Decorator', + 'use-styles': 'useStyles()', + 'with-styles': 'withStyles()', + hyperstyle: 'hyperstyle()', +} diff --git a/site/content/docs/styling/component.mdx b/site/content/docs/styling/component.mdx new file mode 100644 index 00000000..716cea0f --- /dev/null +++ b/site/content/docs/styling/component.mdx @@ -0,0 +1,60 @@ +--- +title: Component +--- + +# Component + +The `component` addon provides a `Component` base class for creating styled React class components. + +```jsx +const { Component } = nano; + +class Button extends Component { + static css = { + display: 'inline-block', + borderRadius: '3px', + padding: '0.5rem 0', + margin: '0.5rem 1rem', + width: '11rem', + background: 'transparent', + color: 'white', + border: '2px solid white', + }; + + render() { + return ; + } +} +``` + +## Dynamic Styles + +Use a `css()` method for styles that depend on props: + +```jsx +class Button extends Component { + css() { + return { + background: this.props.primary ? 'blue' : 'grey', + }; + } + + render() { + return ; + } +} +``` + +## Installation + +Requires `cache` and `rule` addons: + +```js +import { addon as addonCache } from 'nano-css/addon/cache'; +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonComponent } from 'nano-css/addon/component'; + +addonCache(nano); +addonRule(nano); +addonComponent(nano); +``` diff --git a/site/content/docs/styling/decorator.mdx b/site/content/docs/styling/decorator.mdx new file mode 100644 index 00000000..2203b746 --- /dev/null +++ b/site/content/docs/styling/decorator.mdx @@ -0,0 +1,84 @@ +--- +title: "@css Decorator" +--- + +# @css Decorator + +The `decorator` addon exposes a `@css` decorator for styling React class components. + +```jsx +const { css } = nano; + +@css({ + display: 'inline-block', + borderRadius: '3px', + padding: '0.5rem 0', + color: 'white', + border: '2px solid white', +}) +class Button extends React.Component { + render() { + return ; + } +} +``` + +## Usage Patterns + +### Simple Decorator with Static CSS + +```jsx +@css +class Button extends React.Component { + static css = { color: 'red' }; + + render() { + return +``` + +> **Note:** You must provide the hyperscript function `h` when creating the nano-css instance: +> +> ```js +> import { createElement } from 'react'; +> const nano = create({ h: createElement }); +> ``` + +## Naming + +```js +const Button = nano.jsx('button', css, 'MyButton'); +``` + +## Special Props + +All props are passed through to the underlying element, except: + +| Prop | Description | +|------|-------------| +| `css` | CSS-like object of dynamic style overrides | +| `$as` | Overwrite the underlying element type | +| `$ref` | Pass a ref to the underlying element | + +```jsx +{/* Dynamic styles */} + + +{/* Change element type */} + +{/* renders: Click me! */} +``` + +## Composition + +Use components as types for composition: + +```js +const BaseButton = nano.jsx('button', { + color: 'red', + border: '1px solid red', +}); + +const SmallButton = nano.jsx(BaseButton, { + fontSize: '11px', +}); +``` + +## Changing Element Type + +```js +const Link = (props) => Button({ ...props, $as: 'a' }); + +Click me! +// renders: Click me! +``` + +## Installation + +Requires `cache` and `rule` addons: + +```js +import { addon as addonCache } from 'nano-css/addon/cache'; +import { addon as addonRule } from 'nano-css/addon/rule'; +import { addon as addonJsx } from 'nano-css/addon/jsx'; + +addonCache(nano); +addonRule(nano); +addonJsx(nano); +``` diff --git a/site/content/docs/styling/style.mdx b/site/content/docs/styling/style.mdx new file mode 100644 index 00000000..3d5e2d60 --- /dev/null +++ b/site/content/docs/styling/style.mdx @@ -0,0 +1,69 @@ +--- +title: style() +--- + +# style() + +`style()` creates styled components with support for dynamic style templates. + +```jsx +const Button = nano.style('button', { + display: 'inline-block', + borderRadius: '3px', + padding: '0.5rem 0', + margin: '0.5rem 1rem', + width: '11rem', + color: 'white', + border: '2px solid white', +}, (props) => ({ + background: props.primary ? 'blue' : 'grey', + fontSize: props.small ? '12px' : '16px', +})); + + +``` + +## Signature + +```ts +style(type, css, dynamicCss?, name?) +``` + +| Parameter | Description | +|-----------|-------------| +| `type` | String tag name or component to style | +| `css` | Static CSS-like object | +| `dynamicCss` | Optional function `(props) => cssObject` for dynamic styles | +| `name` | Optional semantic component name | + +## Filtering Props + +Styled components pass all props through. To prevent custom props from reaching the DOM, you can whitelist or blacklist: + +**Blacklist approach:** + +```jsx +const ButtonBase = ({ primary, small, ...rest }) =>