Skip to content

Commit

Permalink
feat: container directive
Browse files Browse the repository at this point in the history
  • Loading branch information
sastan committed Dec 9, 2020
1 parent 6ef74ff commit 5126655
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 19 deletions.
23 changes: 23 additions & 0 deletions packages/core/src/__tests__/api.json
Expand Up @@ -660,5 +660,28 @@
"@media (min-width: 768px){.md\\:rotate-3{--tw-rotate:3deg;transform:rotate(3deg);transform:translateX(var(--tw-translate-x,0)) translateY(var(--tw-translate-y,0)) rotate(var(--tw-rotate,0)) skewX(var(--tw-skew-x,0)) skewY(var(--tw-skew-y,0)) scaleX(var(--tw-scale-x,1)) scaleY(var(--tw-scale-y,1))}}",
"@media (min-width: 768px){.md\\:hover\\:-rotate-6:hover{--tw-rotate:calc(6deg * -1);transform:rotate(calc(6deg * -1));transform:translateX(var(--tw-translate-x,0)) translateY(var(--tw-translate-y,0)) rotate(var(--tw-rotate,0)) skewX(var(--tw-skew-x,0)) skewY(var(--tw-skew-y,0)) scaleX(var(--tw-scale-x,1)) scaleY(var(--tw-scale-y,1))}}"
]
],
"container": [
"container",
[
".container{width:100%}",
"@media (min-width: 640px){.container{max-width:640px}}",
"@media (min-width: 768px){.container{max-width:768px}}",
"@media (min-width: 1024px){.container{max-width:1024px}}",
"@media (min-width: 1280px){.container{max-width:1280px}}",
"@media (min-width: 1536px){.container{max-width:1536px}}"
]
],
"md:(container mx-auto)": [
"md:container md:mx-auto",
[
"@media (min-width: 768px){.md\\:mx-auto{margin-left:auto;margin-right:auto}}",
"@media (min-width: 768px){.md\\:container{width:100%}}",
"@media (min-width: 768px){@media (min-width: 640px){.md\\:container{max-width:640px}}}",
"@media (min-width: 768px){@media (min-width: 768px){.md\\:container{max-width:768px}}}",
"@media (min-width: 768px){@media (min-width: 1024px){.md\\:container{max-width:1024px}}}",
"@media (min-width: 768px){@media (min-width: 1280px){.md\\:container{max-width:1280px}}}",
"@media (min-width: 768px){@media (min-width: 1536px){.md\\:container{max-width:1536px}}}"
]
]
}
84 changes: 84 additions & 0 deletions packages/core/src/__tests__/api.test.ts
Expand Up @@ -220,6 +220,90 @@ test('tw`bg-${"fuchsia"} rounded-${"xl"}`', () => {
})
/* eslint-enable no-template-curly-in-string */

test('container center', () => {
const { tw } = create({
injector,
prefix: false,
preflight: false,
mode: strict,
theme: {
extend: {
container: {
center: true,
},
},
},
})

expect(tw`container`).toBe('container')
expect(injector.target).toStrictEqual([
'.container{width:100%;margin-right:auto;margin-left:auto}',
'@media (min-width: 640px){.container{max-width:640px}}',
'@media (min-width: 768px){.container{max-width:768px}}',
'@media (min-width: 1024px){.container{max-width:1024px}}',
'@media (min-width: 1280px){.container{max-width:1280px}}',
'@media (min-width: 1536px){.container{max-width:1536px}}',
])
})

test('container padding', () => {
const { tw } = create({
injector,
prefix: false,
preflight: false,
mode: strict,
theme: {
extend: {
container: {
padding: '2rem',
},
},
},
})

expect(tw`container`).toBe('container')
expect(injector.target).toStrictEqual([
'.container{width:100%;padding-right:2rem;padding-left:2rem}',
'@media (min-width: 640px){.container{max-width:640px;padding-right:2rem;padding-left:2rem}}',
'@media (min-width: 768px){.container{max-width:768px;padding-right:2rem;padding-left:2rem}}',
'@media (min-width: 1024px){.container{max-width:1024px;padding-right:2rem;padding-left:2rem}}',
'@media (min-width: 1280px){.container{max-width:1280px;padding-right:2rem;padding-left:2rem}}',
'@media (min-width: 1536px){.container{max-width:1536px;padding-right:2rem;padding-left:2rem}}',
])
})

test('container padding per screeen', () => {
const { tw } = create({
injector,
prefix: false,
preflight: false,
mode: strict,
theme: {
extend: {
container: {
padding: {
DEFAULT: '1rem',
sm: '2rem',
lg: '4rem',
xl: '5rem',
'2xl': '6rem',
},
},
},
},
})

expect(tw`container`).toBe('container')
expect(injector.target).toStrictEqual([
'.container{width:100%;padding-right:1rem;padding-left:1rem}',
'@media (min-width: 640px){.container{max-width:640px;padding-right:2rem;padding-left:2rem}}',
'@media (min-width: 768px){.container{max-width:768px;padding-right:1rem;padding-left:1rem}}',
'@media (min-width: 1024px){.container{max-width:1024px;padding-right:4rem;padding-left:4rem}}',
'@media (min-width: 1280px){.container{max-width:1280px;padding-right:5rem;padding-left:5rem}}',
'@media (min-width: 1536px){.container{max-width:1536px;padding-right:6rem;padding-left:6rem}}',
])
})

test('falsy arguments', () => {
expect(tw(true, false, '', null, undefined, 0, Number.NaN)).toBe('')
expect(tw('')).toBe('')
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/internal/presedence.ts
Expand Up @@ -17,8 +17,12 @@ let match: RegExpExecArray | null
// 1536px -> 9
// 36rem -> 3
// 96rem -> 9
const responsivePrecedence = (css: string): number =>
(match = /^(\d+(?:.\d+)?)(p)?/.exec(css)) ? +match[1] / (match[2] ? 15 : 1) / 10 : 0
export const responsivePrecedence = (css: string): number =>
(((match = /(?:^|min-width:\s*)(\d+(?:.\d+)?)(p)?/.exec(css))
? +match[1] / (match[2] ? 15 : 1) / 10
: 0) &
31) <<
22

// Colon and dash count of string (ascending): 0 -> 7 => 3 bits
export const seperatorPrecedence = (string: string): number => {
Expand Down Expand Up @@ -89,7 +93,7 @@ export const makeVariantPresedenceCalculator = (
// 1536px -> 9
// 36rem -> 3
// 96rem -> 9
(responsivePrecedence(_) & 31) << 22
responsivePrecedence(_)
: // 1: dark mode flag
variant === ':dark'
? 1 << 21
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/internal/theme.ts
Expand Up @@ -3,7 +3,6 @@ import type {
ThemeColor,
ThemeConfiguration,
ThemeResolver,
ThemeSectionRecord,
ThemeSectionResolverContext,
} from '@tw-in-js/types'

Expand Down Expand Up @@ -76,14 +75,14 @@ export const makeThemeResolver = (config?: ThemeConfiguration): ThemeResolver =>
const deref = (
theme: undefined | Partial<Theme>,
section: keyof Theme,
): ThemeSectionRecord<unknown> | undefined => {
): Record<string, unknown> | undefined => {
const base = theme && theme[section]

const value = is.function(base) ? base(themeResolver, resolveContext) : base

return value && section === 'colors'
? flattenColorPalette(value as Record<string, ThemeColor>)
: value
: (value as Record<string, unknown>)
}

const resolve = ((
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/process/serialize.ts
Expand Up @@ -4,6 +4,7 @@ import * as is from '../internal/is'

import { join, includes, escape, hyphenate } from '../internal/util'
import {
responsivePrecedence,
descending,
declarationPropertyPrecedence,
declarationValuePrecedence,
Expand Down Expand Up @@ -71,7 +72,7 @@ export const serialize = (
// Handling the `@font-face` where the
// block doesn't need the brackets wrapped
stringify([], key, 0, value)
} else {
} else if (key[1] === 'k') {
// To prevent
// "@keyframes spin{from{transform:rotate(0deg)}}"
// "@keyframes spin{to{transform:rotate(360deg)}}"
Expand All @@ -81,7 +82,7 @@ export const serialize = (
// => "@keyframes name{from{transform:rotate(0deg)}from{transform:rotate(0deg)}}"
const currentSize = rules.length

stringify([], key[1] === 'k' ? '' : selector, presedence, value)
stringify([], '', 0, value)

const waypoints = rules.splice(currentSize, rules.length - currentSize)

Expand All @@ -96,6 +97,8 @@ export const serialize = (
// eslint-disable-next-line unicorn/no-reduce
p: waypoints.reduce((sum, p) => sum + p.p, 0),
})
} else {
stringify(atRules.concat(key), selector, presedence | responsivePrecedence(key), value)
}
} else {
// Call the serialize for this block
Expand Down
52 changes: 42 additions & 10 deletions packages/core/src/tailwind/plugins.ts
Expand Up @@ -8,6 +8,7 @@ import type {
ThemeResolver,
Context,
Falsy,
ThemeContainer,
} from '@tw-in-js/types'

import * as is from '../internal/is'
Expand All @@ -20,12 +21,12 @@ let _: undefined | string | CSSRules | CSSProperties | string[] | boolean | Fals
let __: undefined | string | CSSProperties
let $: undefined | string

const property = (property?: string) => (
const property = (property: string) => (
params: string[],
context: unknown,
id: string,
): CSSRules => ({
[property || id]: id + ((_ = join(params)) && '-' + _),
[property]: id + ((_ = join(params)) && '-' + _),
})

const propertyValue = (property: string, separator?: string) => (params: string[]): CSSRules => ({
Expand Down Expand Up @@ -91,14 +92,12 @@ const withOpacityFallback = (
kind: string,
color: string | undefined,
): CSSRules | undefined =>
color
? (_ = asRGBA(color, kind + '-opacity')) && _ !== color
? {
[`--tw-${kind}-opacity`]: '1',
[property]: [color, _],
}
: { [property]: color }
: undefined
color && (_ = asRGBA(color, kind + '-opacity')) && _ !== color
? {
[`--tw-${kind}-opacity`]: '1',
[property]: [color, _],
}
: { [property]: color }

const reversableEdge = (
params: string[],
Expand Down Expand Up @@ -865,5 +864,38 @@ export const corePlugins: Plugins = {
' ',
),
}),

container: (params, { theme }) => {
const { screens = theme('screens'), center, padding } = theme('container') as ThemeContainer

const paddingFor = (screen: string): CSSRules =>
(_ = padding && (is.string(padding) ? padding : padding[screen] || padding.DEFAULT))
? {
paddingRight: _,
paddingLeft: _,
}
: {}

// eslint-disable-next-line unicorn/no-reduce
return Object.keys(screens).reduce(
(rules, screen) => {
if ((_ = screens[screen])) {
rules[`@media (min-width: ${_})`] = {
'&': {
'max-width': _,
...paddingFor(screen),
},
}
}

return rules
},
{
width: '100%',
...(center ? { marginRight: 'auto', marginLeft: 'auto' } : {}),
...paddingFor('xs'),
} as CSSRules,
)
},
}
/* eslint-enable unicorn/prevent-abbreviations, no-return-assign, no-cond-assign, @typescript-eslint/consistent-type-assertions */
3 changes: 3 additions & 0 deletions packages/core/src/tailwind/theme.ts
Expand Up @@ -296,6 +296,7 @@ export const defaultTheme: Theme = {
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
none: 'none',
},
container: {},
divideColor: (theme) => theme('borderColor'),
divideOpacity: (theme) => theme('borderOpacity'),
divideWidth: (theme) => theme('borderWidth'),
Expand Down Expand Up @@ -514,6 +515,8 @@ export const defaultTheme: Theme = {
// 90: '0.9',
// 100: '1',
5: '0.05',
25: '0.25',
75: '0.75',
95: '0.95',
},
order: {
Expand Down
11 changes: 10 additions & 1 deletion packages/types/src/theme.ts
Expand Up @@ -16,7 +16,9 @@ export interface ThemeResolver {

export type Unwrap<T> = T extends string[] ? string : T extends Record<string, infer R> ? R : T

export type ThemeSectionType<T> = T extends ThemeSection<infer R> ? Unwrap<R> : never
export type ThemeSectionType<T> = T extends ThemeSection<infer R>
? Unwrap<R>
: Exclude<T, ThemeSectionResolver<T>>

export interface ThemeSectionResolverContext {
/**
Expand All @@ -40,6 +42,12 @@ export type ThemeSectionResolver<T = string> = (

export type ThemeSection<T = string> = ThemeSectionRecord<T> | ThemeSectionResolver<T>

export interface ThemeContainer {
screens?: Record<string, string | undefined>
center?: boolean
padding?: string | Record<string, string | undefined>
}

export type ThemeColor = string | Record<string, string>

export type ThemeFontSize =
Expand All @@ -65,6 +73,7 @@ export interface Theme {
borderRadius: ThemeSection
borderWidth: ThemeSection
boxShadow: ThemeSection<string | string[]>
container: ThemeContainer | ThemeSectionResolver<ThemeContainer>
divideColor: ThemeSection<ThemeColor>
divideOpacity: ThemeSection
divideWidth: ThemeSection
Expand Down

0 comments on commit 5126655

Please sign in to comment.