Skip to content

Commit

Permalink
add keyframes function that lazily injects the keyframes into the s…
Browse files Browse the repository at this point in the history
…heet and return a unique name
  • Loading branch information
sastan committed Jan 25, 2022
1 parent 1800dec commit a415d38
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 15 deletions.
7 changes: 7 additions & 0 deletions .changeset/famous-candles-brake.md
@@ -0,0 +1,7 @@
---
'@twind/website': patch
'@twind/cdn': patch
'twind': patch
---

add `keyframes` function that lazily injects the keyframes into the sheet and return a unique name
118 changes: 116 additions & 2 deletions documentation/40-reference/README.md
Expand Up @@ -14,6 +14,19 @@
- inline shortcut: style are generated as defined by twind — same as if they where used alone
- `~(underline font-bold)` -> `~(underline,font-bold)`
- `Link~(underline font-bold)` -> `Link#abcdef`
- comments
- multi line: `/* .. */`
- single line: `//` — should **not** be used directly in a class attribute, use `cx` to _clear_ the class names before

## CSS (strings and objects)

- `&`: for nested selectors ([CSS Nesting Module](https://tabatkins.github.io/specs/css-nesting/))
- `label`: for a more readable class names [Emotion › Labels](https://emotion.sh/docs/labels)
- `theme(...)`: access theme values using dot notation; can be used in arbitrary values as well ([Tailwind CSS › Functions & Directives › theme()](https://tailwindcss.com/docs/functions-and-directives#theme))
- `@layer ...`: tell Twind which _bucket_ a set of custom styles belong to ([Tailwind CSS › Functions & Directives › layer](https://tailwindcss.com/docs/functions-and-directives#layer) & [Cascade Layers (CSS @layer) spec](https://www.bram.us/2021/09/15/the-future-of-css-cascade-layers-css-at-layer/))
- The following layer exist in the given order: `defaults`, `base`, `components`, `shortcuts`, `utilities`, `overrides`
- `@apply`: inline any existing utility classes ([Tailwind CSS › Functions & Directives › apply](https://tailwindcss.com/docs/functions-and-directives#apply))
- `@media screen(...)`: create media queries that reference breakpoints by name instead of duplicating their values ([Tailwind CSS › Functions & Directives › screen()](https://tailwindcss.com/docs/functions-and-directives#screen))

## API

Expand All @@ -28,10 +41,37 @@ Observe class attributes to inject styles

### _Library Mode_

use `tw` to inject styles
use `tw` or `tx` to inject styles

- `twind(config, sheet?): Twind`: creates a custom twind instance (`tw`)
- `observe(tw, target?)`: observes all class attributes and inject there styles into the DOM

Recommended custom twind pattern:

```js
import {
twind,
cssom,
virtual,
tx as tx$,
injectGlobal as injectGlobal$,
keyframes as keyframes$,
} from 'twind'

import config from './twind.config'

export const tw = /* @__PURE__ */ twind(
config,
// IS_SSR: `typeof document == 'undefined'` or `import.meta.env.SSR` (vite)
// IS_PROD: `proces.env.NODE_ENV == 'production'` or `import.meta.env.PROD` (vite)
IS_SSR ? virtual() : IS_PROD ? cssom() : dom(),
)

export const tx = /* @__PURE__ */ tx$.bind(tw)
export const injectGlobal = /* @__PURE__ */ injectGlobal$.bind(tw)
export const keyframes = /* @__PURE__ */ keyframes$.bind(tw)
```

- `observe(tw, target?)`: observes all class attributes and injects the styles into the DOM

### Twind instance — `tw`

Expand All @@ -50,8 +90,65 @@ use `tw` to inject styles

- `defineConfig(config)`: define a configuration object for `setup` or `twind`
- `tx(...args)`: creates a class name from the given arguments and injects the styles (like `tw(cx(...args))`)

- `tx.bind(tw)`: binds the `tx` function to a custom twind instance; returns a new `tx` function

```js
import { tx } from 'twind'

const className = tx`
color: red;
font-size: 12px;
`
```

- `injectGlobal(...args)`: injects the given styles into the base layer

- `injectGlobal.bind(tw)`: binds the `injectGlobal` function to a custom twind instance; returns a new `injectGlobal` function

```js
import { injectGlobal } from 'twind'

injectGlobal`
@font-face {
font-family: "Operator Mono";
src: url("../fonts/Operator-Mono.ttf");
}
body {
margin: 0;
}
`
```

- `keyframes(...args)`: lazily injects the keyframes into the sheet and return a unique name

- `keyframes.Name(tw)`: lazily injects the named keyframes into the sheet and return a unique name
- `keyframes.bind(tw)`: binds the `keyframes` function to a custom twind instance; returns a new `keyframes` function

```js
import { keyframes, css, tx } from 'twind'

const fadeIn = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
}
`

// if in _Shim Mode_
const fadeInClass = css`
animation: 1s ${fadeIn} ease-out;
`

// if in _Library Mode_
const fadeInClass = tx`
animation: 1s ${fadeIn} ease-out;
`
```

### Helper functions

These generate class names but do **not** inject styles.
Expand Down Expand Up @@ -105,3 +202,20 @@ Used to update an html string with styles.
},
})
```

## Browser Support

In general, Twind is designed for and tested on the latest stable versions of Chrome, Firefox, Edge, and Safari. It does not support any version of IE, including IE 11.

For automatic vendor prefixing include the [@twind/preset-autoprefix](https://www.npmjs.com/package/@twind/preset-autoprefix) preset.

For more details see [Tailwind CSS › Browser Support](https://tailwindcss.com/docs/browser-support).

The following JS APIs may need polyfills:

- [Array.flatMap](https://caniuse.com/mdn-javascript_builtins_array_flatmap)
- Edge<79, Firefox<62, Chrome<69, Safari<12, Opera<56
- [polyfill](https://www.npmjs.com/package/array-flat-polyfill)
- [Math.imul](https://caniuse.com/mdn-javascript_builtins_math_imul)
- Firefox<20, Chrome<28, Safari<7, Opera<16
- [polyfill](https://www.npmjs.com/package/math.imul)
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -37,20 +37,20 @@
"name": "twind",
"path": "packages/twind/dist/twind.esnext.js",
"brotli": true,
"limit": "6.4kb"
"limit": "6.6kb"
},
{
"name": "twind (setup)",
"path": "packages/twind/dist/twind.esnext.js",
"import": "{ setup }",
"brotli": true,
"limit": "4.35kb"
"limit": "4.4kb"
},
{
"name": "@twind/cdn",
"path": "packages/cdn/dist/cdn.esnext.js",
"brotli": true,
"limit": "14.6kb"
"limit": "14.65kb"
},
{
"name": "@twind/tailwind",
Expand Down
2 changes: 1 addition & 1 deletion packages/cdn/package.json
Expand Up @@ -44,7 +44,7 @@
"name": "@twind/cdn",
"path": "dist/cdn.esnext.js",
"brotli": true,
"limit": "14.6kb"
"limit": "14.65kb"
}
],
"dependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/twind/package.json
Expand Up @@ -44,21 +44,21 @@
"name": "twind",
"path": "dist/twind.esnext.js",
"brotli": true,
"limit": "6.4kb"
"limit": "6.6kb"
},
{
"name": "twind (setup)",
"path": "dist/twind.esnext.js",
"import": "{ setup }",
"brotli": true,
"limit": "4.35kb"
"limit": "4.4kb"
},
{
"name": "twind (twind + cssom)",
"path": "dist/twind.esnext.js",
"import": "{ twind, cssom }",
"brotli": true,
"limit": "4kb"
"limit": "4.05kb"
}
],
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/twind/src/index.ts
Expand Up @@ -13,6 +13,7 @@ export * from './define-config'
export * from './extract'
export * from './inject-global'
export * from './inline'
export * from './keyframes'
export * from './nested'
export * from './observe'
export * from './rules'
Expand Down
8 changes: 4 additions & 4 deletions packages/twind/src/inject-global.ts
@@ -1,4 +1,4 @@
import type { CSSObject, CSSValue } from './types'
import type { CSSNested, CSSObject, CSSValue } from './types'

import { tw as tw$ } from './runtime'
import { astish } from './internal/astish'
Expand All @@ -11,7 +11,7 @@ import { css } from './css'
*/
export function injectGlobal(
this: ((tokens: string) => string) | undefined | void,
style: CSSObject | string,
style: CSSNested | string,
): void

export function injectGlobal(
Expand All @@ -22,14 +22,14 @@ export function injectGlobal(

export function injectGlobal(
this: ((tokens: string) => string) | undefined | void,
strings: CSSObject | string | TemplateStringsArray,
strings: CSSNested | string | TemplateStringsArray,
...interpolations: readonly CSSValue[]
): void {
const tw = typeof this == 'function' ? this : tw$

tw(
css({
'@layer base': astish(strings, interpolations),
'@layer base': astish(strings as CSSObject, interpolations),
} as CSSObject),
)
}
73 changes: 73 additions & 0 deletions packages/twind/src/keyframes.ts
@@ -0,0 +1,73 @@
import type { CSSObject, CSSValue, StringLike } from './types'

import { escape, hash } from './utils'
import { tw as tw$ } from './runtime'
import { astish } from './internal/astish'
import { css } from './css'

export interface KeyframesFunction {
(style: CSSObject | string): StringLike

(strings: TemplateStringsArray, ...interpolations: readonly CSSValue[]): StringLike

bind(thisArg?: ((tokens: string) => string) | undefined | void): Keyframes & {
[label: string]: KeyframesFunction
}
}

export type Keyframes = KeyframesFunction & {
[label: string]: KeyframesFunction
}

export const keyframes = /* @__PURE__ */ bind()

function bind(thisArg: ((tokens: string) => string) | undefined | void): Keyframes {
return new Proxy(
function keyframes(
strings: CSSObject | string | TemplateStringsArray,
...interpolations: readonly CSSValue[]
): StringLike {
return keyframes$(thisArg, '', strings, interpolations)
} as Keyframes,
{
get(target, name) {
if (name === 'bind') {
return bind
}

return function namedKeyframes(
strings: CSSObject | string | TemplateStringsArray,
...interpolations: readonly CSSValue[]
): StringLike {
return keyframes$(thisArg, name as string, strings, interpolations)
}
},
},
)
}

function keyframes$(
thisArg: ((tokens: string) => string) | undefined | void,
name: string,
strings: CSSObject | string | TemplateStringsArray,
interpolations: readonly CSSValue[],
): StringLike {
const ast = astish(strings, interpolations)

const keyframeName = escape(name + hash(JSON.stringify([name, ast])))

const tw = typeof thisArg == 'function' ? thisArg : tw$

// lazy inject keyframes
return {
toString() {
tw(
css({
[`@keyframes ${keyframeName}`]: astish(strings, interpolations),
} as unknown as CSSObject),
)

return keyframeName
},
} as StringLike
}
2 changes: 1 addition & 1 deletion packages/twind/src/nested.ts
Expand Up @@ -12,7 +12,7 @@ function nested(marker: string): Nested {
return nested$('', strings, interpolations)
} as Nested,
{
get: function (target, name) {
get(target, name) {
return function namedNested(
strings: TemplateStringsArray | Class,
...interpolations: Class[]
Expand Down

0 comments on commit a415d38

Please sign in to comment.