Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow style component to be used as plugin #159

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export interface Style<Variants> {
* ```
* <br />
*/
(props?: StyleProps<Variants>): Directive<CSSRules>
(props?: string | string[] | StyleProps<Variants>): Directive<CSSRules>

/**
* CSS Selector associated with the current component.
Expand Down Expand Up @@ -180,6 +180,21 @@ const buildMediaRule = (key: string, value: undefined | StyleToken): CSSRules =>
[key[0] == '@' ? key : `@screen ${key}`]: typeof value == 'string' ? apply(value) : value,
})

/**
* Convert parts array into valid props for a twind/style function
* - Kebab Case (tailwind like): x-foo=bar-camelCase
* - Snake Case: x-foo=bar_camelCase & x-foo=bar_camel-case
* - BEM modifer: x-foo=bar--camelCase & x-foo=bar--camel-case
* - URL like: x-foo=bar&camelCase & x-foo=bar&camel-case
* e.g. foo=bar--baz --> { foo: 'bar', 'baz': true }
*/
const makeProps = (parts: string[]): Record<string, unknown> =>
parts.reduce((props, part) => {
const [key, value = true] = part.split('=', 2)
props[key] = value
return props
}, {} as Record<string, unknown>)

const createStyle = <Variants, BaseVariants>(
config: StyleConfig<Variants, BaseVariants> = {},
base?: Style<BaseVariants>,
Expand All @@ -191,7 +206,15 @@ const createStyle = <Variants, BaseVariants>(
const selector = (base || '') + '.' + id

return Object.defineProperties(
(allProps?: StyleProps<BaseVariants & Variants>): Directive<CSSRules> => {
(allProps?: string | string[] | StyleProps<BaseVariants & Variants>): Directive<CSSRules> => {
if (typeof allProps == 'string') {
allProps = allProps.split('-')
}

if (Array.isArray(allProps)) {
allProps = makeProps(allProps) as StyleProps<BaseVariants & Variants>
}

const { tw, css, class: localClass, className: localClassName, ...props } = {
...defaults,
...allProps,
Expand Down Expand Up @@ -269,6 +292,14 @@ const createStyle = <Variants, BaseVariants>(
selector: {
value: selector,
},
// ['size=sm', 'size=md', 'outline', 'outline=false']
// ['size=sm', 'size=sm-outline', 'size=sm-outline=false']
// ['size=md', 'size=md-outline', 'size=md-outline=false']
// tokens: {
// value: Object.keys(variants).map((key) =>
// Object.keys((variants as Record<string, Record<string, unknown>>)[key]),
// ),
// },
},
)
}
Expand Down
62 changes: 62 additions & 0 deletions src/style/style.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,4 +675,66 @@ test('is added to component layer', ({ tw, sheet }) => {
])
})

test('component can be used as plugin', ({ sheet }) => {
const button = style({
base: `
px-3 py-1 rounded-md
text-white bg-gray-500 hover:bg-gray-400
`,
variants: {
variant: {
primary: 'bg-blue-500 hover:bg-blue-400',
warn: 'bg-red-500 hover:bg-red-400',
},
outline: { true: `border-2 border-black` },
},
})

const { tw } = create({
sheet,
mode: strict,
preflight: false,
prefix: false,
plugins: {
btn: button,
},
})

assert.equal(sheet.target, [])

assert.is(button.className, 'tw-1fg076q')
assert.is('' + button, '.tw-1fg076q')
assert.is(button.selector, '.tw-1fg076q')

assert.is(tw`btn`, 'btn tw-1fg076q')
assert.equal(sheet.target, [
'.btn{padding-left:0.75rem;padding-right:0.75rem;padding-bottom:0.25rem;padding-top:0.25rem;border-radius:0.375rem;--tw-text-opacity:1;color:#fff;color:rgba(255,255,255,var(--tw-text-opacity));--tw-bg-opacity:1;background-color:#6b7280;background-color:rgba(107,114,128,var(--tw-bg-opacity))}',
'.btn:hover{--tw-bg-opacity:1;background-color:#9ca3af;background-color:rgba(156,163,175,var(--tw-bg-opacity))}',
])

sheet.reset()

assert.is(tw`btn-outline`, 'btn-outline tw-1fg076q')
assert.equal(sheet.target, [
'.btn-outline{padding-left:0.75rem;padding-right:0.75rem;padding-bottom:0.25rem;padding-top:0.25rem;border-radius:0.375rem;--tw-text-opacity:1;color:#fff;color:rgba(255,255,255,var(--tw-text-opacity));--tw-bg-opacity:1;background-color:#6b7280;background-color:rgba(107,114,128,var(--tw-bg-opacity));border-width:2px;--tw-border-opacity:1;border-color:#000;border-color:rgba(0,0,0,var(--tw-border-opacity))}',
'.btn-outline:hover{--tw-bg-opacity:1;background-color:#9ca3af;background-color:rgba(156,163,175,var(--tw-bg-opacity))}',
])

sheet.reset()

assert.is(tw`btn-variant=primary`, 'btn-variant=primary tw-1fg076q')
assert.equal(sheet.target, [
'.btn-variant\\=primary{padding-left:0.75rem;padding-right:0.75rem;padding-bottom:0.25rem;padding-top:0.25rem;border-radius:0.375rem;--tw-text-opacity:1;color:#fff;color:rgba(255,255,255,var(--tw-text-opacity));--tw-bg-opacity:1;background-color:#3b82f6;background-color:rgba(59,130,246,var(--tw-bg-opacity))}',
'.btn-variant\\=primary:hover{--tw-bg-opacity:1;background-color:#60a5fa;background-color:rgba(96,165,250,var(--tw-bg-opacity))}',
])

sheet.reset()

assert.is(tw`btn-variant=warn-outline`, 'btn-variant=warn-outline tw-1fg076q')
assert.equal(sheet.target, [
'.btn-variant\\=warn-outline{padding-left:0.75rem;padding-right:0.75rem;padding-bottom:0.25rem;padding-top:0.25rem;border-radius:0.375rem;--tw-text-opacity:1;color:#fff;color:rgba(255,255,255,var(--tw-text-opacity));--tw-bg-opacity:1;background-color:#ef4444;background-color:rgba(239,68,68,var(--tw-bg-opacity));border-width:2px;--tw-border-opacity:1;border-color:#000;border-color:rgba(0,0,0,var(--tw-border-opacity))}',
'.btn-variant\\=warn-outline:hover{--tw-bg-opacity:1;background-color:#f87171;background-color:rgba(248,113,113,var(--tw-bg-opacity))}',
])
})

test.run()
2 changes: 1 addition & 1 deletion src/twind/default.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { create } from './instance'

export const { tw, setup } = create()
export const { tw, setup } = /*#__PURE__*/ create()
73 changes: 48 additions & 25 deletions src/twind/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,55 @@ export interface RuleWithPresedence {

const stringifyBlock = (body: string, selector: string): string => selector + '{' + body + '}'

// Not using const enums as they get transpiled to a lot of code
// /**
// * Determines the default order of styles.
// *
// * For example: screens have a higher presedence (eg override) utilities
// */
// const enum Layer {
// /**
// * The preflight styles and any base styles registered by plugins.
// */
// base = 0,

// /**
// * Component classes and any component classes registered by plugins.
// */
// components = 1,

// /**
// * Utility classes and any utility classes registered by plugins.
// */
// utilities = 2,

// /**
// * Inline directives
// */
// css = 3,
// }

/**
* Determines the default order of styles.
*
* For example: screens have a higher presedence (eg override) utilities
* The preflight styles and any base styles registered by plugins.
*/
const enum Layer {
/**
* The preflight styles and any base styles registered by plugins.
*/
base = 0,

/**
* Component classes and any component classes registered by plugins.
*/
components = 1,

/**
* Utility classes and any utility classes registered by plugins.
*/
utilities = 2,

/**
* Inline directives
*/
css = 3,
}
export type LayerBase = 0

/**
* Component classes and any component classes registered by plugins.
*/
export type LayerComponents = 1

/**
* Utility classes and any utility classes registered by plugins.
*/
export type LayerUtilities = 2

/**
* Inline directives
*/
export type LayerCss = 3

export type Layer = LayerBase | LayerComponents | LayerUtilities | LayerCss

export const serialize = (
prefix: Prefixer,
Expand Down Expand Up @@ -270,7 +293,7 @@ export const serialize = (

const variantPresedence = makeVariantPresedenceCalculator(theme, variants)

return (css, className, rule, layer = Layer.base) => {
return (css, className, rule, layer = 0 /* Layerbase */) => {
// Initial presedence based on layer (base = 0, components = 1, utilities = 2, css = 3)
layer <<= 28

Expand Down
Loading