Showing preview only (2,750K chars total). Download the full file or copy to clipboard to get everything.
Repository: ben-rogerson/twin.macro
Branch: master
Commit: e477fca539e7
Files: 391
Total size: 2.6 MB
Directory structure:
gitextract_n8pigfyt/
├── .babelrc
├── .eslintrc.js
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ └── config.yml
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── docs/
│ ├── advanced-theming.md
│ ├── arbitrary-values.md
│ ├── customizing-config.md
│ ├── fonts.md
│ ├── group.md
│ ├── index.md
│ ├── options.md
│ ├── prop-styling-guide.md
│ ├── screen-import.md
│ └── styled-component-guide.md
├── jest.config.ts
├── package.json
├── sandbox/
│ └── in.tsx
├── src/
│ ├── core/
│ │ ├── constants.ts
│ │ ├── createCoreContext.ts
│ │ ├── extractRuleStyles.ts
│ │ ├── getGlobalStyles.ts
│ │ ├── getStyles.ts
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── configHelpers.ts
│ │ │ ├── convertClassName.ts
│ │ │ ├── createAssert.ts
│ │ │ ├── createTheme.ts
│ │ │ ├── defaultTailwindConfig.ts
│ │ │ ├── expandVariantGroups.ts
│ │ │ ├── getStitchesPath.ts
│ │ │ ├── logging.ts
│ │ │ ├── twinConfig.ts
│ │ │ ├── userPresets.ts
│ │ │ └── util/
│ │ │ ├── camelize.ts
│ │ │ ├── deepMerge.ts
│ │ │ ├── escapeRegex.ts
│ │ │ ├── formatProp.ts
│ │ │ ├── get.ts
│ │ │ ├── isEmpty.ts
│ │ │ ├── isObject.ts
│ │ │ ├── isShortCss.ts
│ │ │ ├── replaceThemeValue.ts
│ │ │ ├── sassifySelector.ts
│ │ │ ├── splitOnFirst.ts
│ │ │ ├── toArray.ts
│ │ │ └── twImports.ts
│ │ └── types/
│ │ └── index.ts
│ ├── macro/
│ │ ├── className.ts
│ │ ├── css.ts
│ │ ├── dataProp.ts
│ │ ├── globalStyles.ts
│ │ ├── lib/
│ │ │ ├── astHelpers.ts
│ │ │ ├── util/
│ │ │ │ ├── get.ts
│ │ │ │ └── isEmpty.ts
│ │ │ └── validateImports.ts
│ │ ├── screen.ts
│ │ ├── shortCss.ts
│ │ ├── styled.ts
│ │ ├── theme.ts
│ │ ├── tw.ts
│ │ ├── twin.ts
│ │ └── types/
│ │ └── index.ts
│ ├── macro.ts
│ └── suggestions/
│ ├── index.ts
│ ├── lib/
│ │ ├── colors.ts
│ │ ├── extractors.ts
│ │ ├── getClassSuggestions.ts
│ │ ├── getPackageVersions.ts
│ │ ├── makeColor.ts
│ │ ├── validateVariants.ts
│ │ └── validators.ts
│ └── types/
│ └── index.ts
├── tests/
│ ├── @applyInPlugins.test.ts
│ ├── __fixtures__/
│ │ ├── !general.tsx
│ │ ├── !important.tsx
│ │ ├── !imports.tsx
│ │ ├── !namelessImport.tsx
│ │ ├── !ordering.tsx
│ │ ├── !properties.tsx
│ │ ├── !variantGrouping.tsx
│ │ ├── !variants.tsx
│ │ ├── .eslintrc.js
│ │ ├── addBase/
│ │ │ ├── addBase.tsx
│ │ │ └── tailwind.config.js
│ │ ├── arbitraryProperties/
│ │ │ └── arbitraryProperties.tsx
│ │ ├── arbitraryVariants/
│ │ │ ├── arbitraryVariants.tsx
│ │ │ └── config.json
│ │ ├── autoCssProp/
│ │ │ └── autoCssProp.tsx
│ │ ├── colorFunctions/
│ │ │ ├── colorFunctions.tsx
│ │ │ └── tailwind.config.js
│ │ ├── comments/
│ │ │ ├── comments.tsx
│ │ │ └── config.json
│ │ ├── config/
│ │ │ ├── config.tsx
│ │ │ └── tailwind.config.js
│ │ ├── configTS/
│ │ │ ├── configTS.tsx
│ │ │ └── tailwind.config.ts
│ │ ├── content/
│ │ │ ├── content.tsx
│ │ │ └── tailwind.config.js
│ │ ├── cssPropEmotion/
│ │ │ ├── autoCssProp.tsx
│ │ │ └── autoCssPropWithStyled.tsx
│ │ ├── cssPropStyledComponents/
│ │ │ ├── autoCssProp.tsx
│ │ │ ├── autoCssPropWithStyled.tsx
│ │ │ └── config.json
│ │ ├── darkLightModeArray/
│ │ │ ├── darkLightModeArray.tsx
│ │ │ └── tailwind.config.js
│ │ ├── directionalBorders/
│ │ │ └── directionalBorders.tsx
│ │ ├── fluidContainer/
│ │ │ ├── fluidContainer.tsx
│ │ │ └── tailwind.config.js
│ │ ├── globalStyles/
│ │ │ ├── config.json
│ │ │ ├── globalStyles.tsx
│ │ │ └── tailwind.config.js
│ │ ├── group/
│ │ │ └── group.tsx
│ │ ├── includeClassNames/
│ │ │ ├── config.json
│ │ │ └── includeClassNames.tsx
│ │ ├── lineClamp/
│ │ │ └── lineClamp.tsx
│ │ ├── negative/
│ │ │ ├── negative.tsx
│ │ │ └── tailwind.config.js
│ │ ├── peers/
│ │ │ └── peers.tsx
│ │ ├── pluginAspectRatio/
│ │ │ ├── pluginAspectRatio.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginDaisyUi/
│ │ │ ├── pluginDaisyUi.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginExamples/
│ │ │ ├── pluginExamples.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginForms/
│ │ │ ├── pluginForms.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginFormsClassStrategy/
│ │ │ ├── pluginTypography.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginGapFallback/
│ │ │ ├── pluginGapFallback.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginTypography/
│ │ │ ├── pluginTypography.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginUserParentSelector/
│ │ │ ├── pluginUserParentSelector.tsx
│ │ │ └── tailwind.config.js
│ │ ├── plugins/
│ │ │ ├── config.json
│ │ │ ├── plugins.tsx
│ │ │ └── tailwind.config.js
│ │ ├── prefix/
│ │ │ ├── config.json
│ │ │ ├── prefix.tsx
│ │ │ └── tailwind.config.js
│ │ ├── preflight/
│ │ │ ├── preflight.tsx
│ │ │ └── tailwind.config.js
│ │ ├── presets/
│ │ │ ├── presets.tsx
│ │ │ └── tailwind.config.js
│ │ ├── sassyPseudo/
│ │ │ ├── config.json
│ │ │ ├── sassyPseudo.tsx
│ │ │ └── tailwind.config.js
│ │ ├── screenImport/
│ │ │ ├── screenImport.tsx
│ │ │ └── tailwind.config.js
│ │ ├── screens/
│ │ │ ├── screens.tsx
│ │ │ └── tailwind.config.js
│ │ ├── separator/
│ │ │ ├── separator.tsx
│ │ │ └── tailwind.config.js
│ │ ├── shortCss/
│ │ │ ├── config.json
│ │ │ └── shortCss.tsx
│ │ ├── stitches/
│ │ │ ├── config.json
│ │ │ ├── stitches.config.js
│ │ │ ├── stitchesDotSyntax.tsx
│ │ │ ├── stitchesGlobals.tsx
│ │ │ ├── stitchesImports.tsx
│ │ │ └── stitchesProps.tsx
│ │ ├── themeValuesToString/
│ │ │ ├── tailwind.config.js
│ │ │ └── themeValuesToString.tsx
│ │ ├── userPluginOrdering/
│ │ │ ├── tailwind.config.js
│ │ │ └── userPluginOrdering.tsx
│ │ ├── utilitiesAccessibility/
│ │ │ └── screenReaders.tsx
│ │ ├── utilitiesBackgrounds/
│ │ │ ├── backgroundAttachment.tsx
│ │ │ ├── backgroundClip.tsx
│ │ │ ├── backgroundColor.tsx
│ │ │ ├── backgroundImage.tsx
│ │ │ ├── backgroundOpacity.tsx
│ │ │ ├── backgroundOrigin.tsx
│ │ │ ├── backgroundPosition.tsx
│ │ │ ├── backgroundRepeat.tsx
│ │ │ ├── backgroundSize.tsx
│ │ │ ├── gradientColorStops.tsx
│ │ │ └── tailwind.config.js
│ │ ├── utilitiesBorders/
│ │ │ ├── borderColor.tsx
│ │ │ ├── borderOpacity.tsx
│ │ │ ├── borderRadius.tsx
│ │ │ ├── borderStyle.tsx
│ │ │ ├── borderWidth.tsx
│ │ │ ├── divideColor.tsx
│ │ │ ├── divideOpacity.tsx
│ │ │ ├── divideStyle.tsx
│ │ │ ├── divideWidth.tsx
│ │ │ ├── outlineColor.tsx
│ │ │ ├── outlineOffset.tsx
│ │ │ ├── outlineStyle.tsx
│ │ │ ├── outlineWidth.tsx
│ │ │ ├── ringColor.tsx
│ │ │ ├── ringMisc.tsx
│ │ │ ├── ringOffsetColor.tsx
│ │ │ ├── ringOffsetWidth.tsx
│ │ │ ├── ringOpacity.tsx
│ │ │ ├── ringWidth.tsx
│ │ │ └── tailwind.config.js
│ │ ├── utilitiesEffects/
│ │ │ ├── backgroundBlendMode.tsx
│ │ │ ├── boxShadow.tsx
│ │ │ ├── boxShadowColor.tsx
│ │ │ ├── mixBlendMode.tsx
│ │ │ └── opacity.tsx
│ │ ├── utilitiesFilters/
│ │ │ ├── backdropBlur.tsx
│ │ │ ├── backdropBrightness.tsx
│ │ │ ├── backdropContrast.tsx
│ │ │ ├── backdropGrayscale.tsx
│ │ │ ├── backdropHueRotate.tsx
│ │ │ ├── backdropInvert.tsx
│ │ │ ├── backdropOpacity.tsx
│ │ │ ├── backdropSaturate.tsx
│ │ │ ├── backdropSepia.tsx
│ │ │ ├── blur.tsx
│ │ │ ├── brightness.tsx
│ │ │ ├── contrast.tsx
│ │ │ ├── dropShadow.tsx
│ │ │ ├── grayscale.tsx
│ │ │ ├── hueRotate.tsx
│ │ │ ├── invert.tsx
│ │ │ ├── misc.tsx
│ │ │ ├── saturate.tsx
│ │ │ └── sepia.tsx
│ │ ├── utilitiesLayout/
│ │ │ ├── aspectRatio.tsx
│ │ │ ├── boxDecorationBreak.tsx
│ │ │ ├── boxSizing.tsx
│ │ │ ├── breakAfter.tsx
│ │ │ ├── breakBefore.tsx
│ │ │ ├── breakInside.tsx
│ │ │ ├── clear.tsx
│ │ │ ├── columns.tsx
│ │ │ ├── container.tsx
│ │ │ ├── display.tsx
│ │ │ ├── float.tsx
│ │ │ ├── isolation.tsx
│ │ │ ├── objectFit.tsx
│ │ │ ├── objectPosition.tsx
│ │ │ ├── overflow.tsx
│ │ │ ├── overscrollBehavior.tsx
│ │ │ ├── position.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── topRightBottomLeft.tsx
│ │ │ ├── visibility.tsx
│ │ │ └── zIndex.tsx
│ │ ├── utilitiesSpacing/
│ │ │ ├── margin.tsx
│ │ │ ├── padding.tsx
│ │ │ └── spaceBetween.tsx
│ │ ├── utilitiesSvg/
│ │ │ ├── fill.tsx
│ │ │ ├── stroke.tsx
│ │ │ ├── strokeWidth.tsx
│ │ │ └── tailwind.config.js
│ │ ├── utilitiesTransforms/
│ │ │ ├── misc.tsx
│ │ │ ├── rotate.tsx
│ │ │ ├── scale.tsx
│ │ │ ├── skew.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── transformOrigin.tsx
│ │ │ └── translate.tsx
│ │ ├── utilitiesTransitionsAnimation/
│ │ │ ├── animation.tsx
│ │ │ ├── misc.tsx
│ │ │ ├── transitionDelay.tsx
│ │ │ ├── transitionDuration.tsx
│ │ │ ├── transitionProperty.tsx
│ │ │ └── transitionTimingFunction.tsx
│ │ ├── utiltiesFlexboxGrid/
│ │ │ ├── alignContent.tsx
│ │ │ ├── alignItems.tsx
│ │ │ ├── alignSelf.tsx
│ │ │ ├── flex.tsx
│ │ │ ├── flexBasis.tsx
│ │ │ ├── flexDirection.tsx
│ │ │ ├── flexGrow.tsx
│ │ │ ├── flexShrink.tsx
│ │ │ ├── flexWrap.tsx
│ │ │ ├── gap.tsx
│ │ │ ├── gridAutoColumns.tsx
│ │ │ ├── gridAutoFlow.tsx
│ │ │ ├── gridAutoRows.tsx
│ │ │ ├── gridColumn.tsx
│ │ │ ├── gridRow.tsx
│ │ │ ├── gridTemplateColumns.tsx
│ │ │ ├── gridTemplateRows.tsx
│ │ │ ├── justifyContent.tsx
│ │ │ ├── justifyItems.tsx
│ │ │ ├── justifySelf.tsx
│ │ │ ├── misc.tsx
│ │ │ ├── order.tsx
│ │ │ ├── placeContent.tsx
│ │ │ ├── placeItems.tsx
│ │ │ └── placeSelf.tsx
│ │ ├── utiltiesInteractivity/
│ │ │ ├── accentColor.tsx
│ │ │ ├── appearance.tsx
│ │ │ ├── caretColor.tsx
│ │ │ ├── cursor.tsx
│ │ │ ├── pointerEvents.tsx
│ │ │ ├── resize.tsx
│ │ │ ├── scrollBehavior.tsx
│ │ │ ├── scrollMargin.tsx
│ │ │ ├── scrollPadding.tsx
│ │ │ ├── scrollSnapAlign.tsx
│ │ │ ├── scrollSnapStop.tsx
│ │ │ ├── scrollSnapType.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── touchAction.tsx
│ │ │ ├── userSelect.tsx
│ │ │ └── willChange.tsx
│ │ ├── utiltiesSizing/
│ │ │ ├── height.tsx
│ │ │ ├── maxHeight.tsx
│ │ │ ├── maxWidth.tsx
│ │ │ ├── minHeight.tsx
│ │ │ ├── minWidth.tsx
│ │ │ └── width.tsx
│ │ ├── utiltiesTables/
│ │ │ ├── borderCollapse.tsx
│ │ │ ├── borderSpacing.tsx
│ │ │ ├── captionSide.tsx
│ │ │ └── tableLayout.tsx
│ │ ├── utiltiesTypography/
│ │ │ ├── fontFamily.tsx
│ │ │ ├── fontSize.tsx
│ │ │ ├── fontSmoothing.tsx
│ │ │ ├── fontStyle.tsx
│ │ │ ├── fontVariantNumeric.tsx
│ │ │ ├── fontWeight.tsx
│ │ │ ├── hyphens.tsx
│ │ │ ├── letterSpacing.tsx
│ │ │ ├── lineHeight.tsx
│ │ │ ├── listStyleImage.tsx
│ │ │ ├── listStylePosition.tsx
│ │ │ ├── listStyleType.tsx
│ │ │ ├── placeholderColor.tsx
│ │ │ ├── placeholderOpacity.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── textAlign.tsx
│ │ │ ├── textColor.tsx
│ │ │ ├── textDecoration.tsx
│ │ │ ├── textDecorationColor.tsx
│ │ │ ├── textDecorationStyle.tsx
│ │ │ ├── textDecorationThickness.tsx
│ │ │ ├── textIndent.tsx
│ │ │ ├── textOpacity.tsx
│ │ │ ├── textOverflow.tsx
│ │ │ ├── textTransform.tsx
│ │ │ ├── textUnderlineOffset.tsx
│ │ │ ├── verticalAlign.tsx
│ │ │ ├── whitespace.tsx
│ │ │ └── wordBreak.tsx
│ │ ├── variables/
│ │ │ ├── tailwind.config.js
│ │ │ └── variables.tsx
│ │ ├── variantOrdering/
│ │ │ └── variantOrdering.tsx
│ │ └── visitedOpacity/
│ │ └── visitedOpacity.tsx
│ ├── __snapshots__/
│ │ └── plugin.test.js.snap
│ ├── animations.test.ts
│ ├── arbitraryProperties.test.ts
│ ├── arbitraryValues.test.ts
│ ├── arbitraryVariants.test.ts
│ ├── config.test.ts
│ ├── containerQueries.test.ts
│ ├── dividers.test.ts
│ ├── escaping.test.ts
│ ├── fontSize.test.ts
│ ├── minMaxScreenVariants.test.ts
│ ├── plugin.test.js
│ ├── presetEmotion.test.ts
│ ├── presetGoober.test.ts
│ ├── presetSolid.test.ts
│ ├── presetStitches.test.ts
│ ├── presetStyledComponents.test.ts
│ ├── screens.test.ts
│ ├── stitches.config.js
│ ├── types/
│ │ ├── index.ts
│ │ └── types.d.ts
│ └── util/
│ ├── customMatchers.ts
│ └── run.ts
├── tsconfig.json
└── types/
├── index.d.ts
├── macro.d.ts
├── tests/
│ ├── __fixtures__/
│ │ ├── config/
│ │ │ └── tailwind.config.d.ts
│ │ └── configTS/
│ │ └── tailwind.config.d.ts
│ ├── basic/
│ │ ├── index.tsx
│ │ ├── noDefaultImport.tsx
│ │ └── tsconfig.json
│ ├── emotion/
│ │ ├── index.tsx
│ │ └── tsconfig.json
│ └── styled-components/
│ ├── index.tsx
│ └── tsconfig.json
├── tsconfig.base.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-syntax-jsx",
"babel-plugin-macros"
]
}
================================================
FILE: .eslintrc.js
================================================
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
project: './tsconfig.json',
},
settings: {
version: 'detect',
},
plugins: [
'@typescript-eslint',
'chai-friendly',
'jest',
'import',
'prettier',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:unicorn/recommended',
'xo/browser',
'xo-typescript/space',
'xo-react/space',
'plugin:jest/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'prettier',
],
overrides: [
{
files: ['src/**/*.ts', 'tests/**/*.ts'],
rules: {
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0, maxBOF: 0 }],
'no-undef': 2,
'no-console': 'error',
'capitalized-comments': 0,
'func-style': ['error', 'declaration'],
'no-constant-binary-expression': 0,
'import/no-unresolved': 'error',
'import/no-relative-parent-imports': 'error',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'unicorn/filename-case': ['error', { case: 'camelCase' }],
'unicorn/prefer-optional-catch-binding': 0, // Doubleup
'unicorn/consistent-destructuring': 0,
'unicorn/prefer-node-protocol': 0,
'unicorn/import-style': 0,
'unicorn/prefer-array-flat': 0,
'unicorn/no-array-for-each': 0,
'unicorn/prevent-abbreviations': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
disallowTypeAnnotations: true,
},
],
},
},
{
files: ['tests/**/*test.ts'],
rules: {
'@typescript-eslint/no-require-imports': 0,
'unicorn/prefer-module': 0,
'jest/no-conditional-expect': 0,
},
},
{
files: ['types/tests/**/*.ts', 'types/tests/**/*.tsx'],
rules: {
'import/no-unassigned-import': 0,
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-unsafe-call': 0,
},
},
{
files: ['**/types/**/*.ts'],
rules: {
'import/no-relative-parent-imports': 0,
'unicorn/prefer-export-from': 0,
},
},
],
ignorePatterns: [
'/tests/plugin.test.js',
'/tests/stitches.config.js',
'/tests/__fixtures__',
'/types/macro.d.ts',
'/types',
'/.eslintrc.js',
'/macro.js',
'/sandbox',
'/jest.config.ts',
],
globals: { JSX: true, AriaAttributes: true, process: true },
env: { browser: false, node: true, es6: true },
}
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: ben-rogerson
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: 'Bug report'
about: 'Report a reproducible bug'
title: ''
labels: ''
assignees: ''
---
<!--
Thanks for taking the time to post a bug or issue
Please include some helpful information like:
1. The buggy behavior you’re experiencing and what should be happening instead.
2. If you're using styled-components, emotion, goober, stitches or other.
3. A link to a repo or a minimal demo that shows the bug in action.
Feel free to start with a template from the twin.examples repo:
https://github.com/ben-rogerson/twin.examples
Apply code highlighting by adding the language next to the opening backticks, eg:
```ts
my code
```
-->
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Help / Question / Feature request
url: https://github.com/ben-rogerson/twin.macro/discussions/new
about: 'If you have a question, need help or have an idea please ask a question in the discussion forum'
================================================
FILE: .github/workflows/main.yml
================================================
name: Notification on push
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
================================================
FILE: .gitignore
================================================
node_modules
/macro.js
/macro.js.map
/utils.umd.js
/utils.umd.js.map
.vscode
yarn.lock
# artifacts from type tests
*.tsbuildinfo
types/build
types/core
types/macro
types/suggestions
types/src
types/tests/types
types/tests/util
types/tests/*.ts
.eslintcache
coverage
sandbox/out.tsx
================================================
FILE: .nvmrc
================================================
16.14.0
================================================
FILE: .prettierrc
================================================
{
"endOfLine": "lf",
"semi": false,
"singleQuote": true,
"bracketSpacing": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"arrowParens": "avoid"
}
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at info@benrogerson.dev. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: LICENSE
================================================
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://github.com/ben-rogerson/twin.macro#gh-light-mode-only" target="_blank">
<img src="./.github/logo-light.svg" alt="Twin examples" width="199" height="70">
</a>
<a href="https://github.com/ben-rogerson/twin.macro#gh-dark-mode-only" target="_blank">
<img src="./.github/logo-dark.svg" alt="Twin examples" width="199" height="70">
</a>
</p>
<p align="center">
The <em>magic</em> of Tailwind with the <em>flexibility</em> of css-in-js.<br><br>
<a href="https://www.npmjs.com/package/twin.macro"><img src="https://img.shields.io/npm/dt/twin.macro.svg" alt="Total Downloads"></a>
<a href="https://www.npmjs.com/package/twin.macro"><img src="https://img.shields.io/npm/v/twin.macro.svg" alt="Latest Release"></a>
<a href="https://discord.gg/Xj6x9z7"><img src="https://img.shields.io/discord/705884695400939552?label=discord&logo=discord" alt="Discord"></a>
<br>
<br>
<a href="https://stackblitz.com/github/ben-rogerson/twin.examples/tree/master/vite-styled-components-typescript?file=src/App.tsx">
<img
alt="Open in StackBlitz"
src="https://developer.stackblitz.com/img/open_in_stackblitz_small.svg"
/>
</a>
</p>
---
Style jsx elements using Tailwind classes:
```js
import 'twin.macro'
const Input = () => <input tw="border hover:border-black" />
```
Nest Twin’s `tw` import within a css prop to add conditional styles:
```js
import tw from 'twin.macro'
const Input = ({ hasHover }) => (
<input css={[tw`border`, hasHover && tw`hover:border-black`]} />
)
```
Or mix sass styles with the css import:
```js
import tw, { css } from 'twin.macro'
const hoverStyles = css`
&:hover {
border-color: black;
${tw`text-black`}
}
`
const Input = ({ hasHover }) => (
<input css={[tw`border`, hasHover && hoverStyles]} />
)
```
### Styled Components
You can also use the tw import to create and style new components:
```js
import tw from 'twin.macro'
const Input = tw.input`border hover:border-black`
```
And clone and style existing components:
```js
const PurpleInput = tw(Input)`border-purple-500`
```
Switch to the styled import to add conditional styling:
```js
import tw, { styled } from 'twin.macro'
const StyledInput = styled.input(({ hasBorder }) => [
`color: black;`,
hasBorder && tw`border-purple-500`,
])
const Input = () => <StyledInput hasBorder />
```
Or use backticks to mix with sass styles:
```js
import tw, { styled } from 'twin.macro'
const StyledInput = styled.input`
color: black;
${({ hasBorder }) => hasBorder && tw`border-purple-500`}
`
const Input = () => <StyledInput hasBorder />
```
## How it works
When babel runs over your javascript or typescript files at compile time, twin grabs your classes and converts them into css objects.
These css objects are then passed into your chosen css-in-js library without the need for an extra client-side bundle:
```js
import tw from 'twin.macro'
tw`text-sm md:text-lg`
// ↓ ↓ ↓ ↓ ↓ ↓
{
fontSize: '0.875rem',
'@media (min-width: 768px)': {
fontSize: '1.125rem',
},
}
```
## Features
**👌 Simple imports** - Twin collapses imports from common styling libraries into a single import:
```diff
- import styled from '@emotion/styled'
- import css from '@emotion/react'
+ import { styled, css } from 'twin.macro'
```
**🐹 Adds no size to your build** - Twin converts the classes you’ve used into css objects using Babel and then compiles away, leaving no runtime code
**🍱 Apply variants to multiple classes at once with variant groups**
```js
import 'twin.macro'
const interactionStyles = () => (
<div tw="hover:(text-black underline) focus:(text-blue-500 underline)" />
)
const mediaStyles = () => <div tw="sm:(w-4 mt-3) lg:(w-8 mt-6)" />
const pseudoElementStyles = () => <div tw="before:(block w-10 h-10 bg-black)" />
const stackedVariants = () => <div tw="sm:hover:(bg-black text-white)" />
const groupsInGroups = () => <div tw="sm:(bg-black hover:(bg-white w-10))" />
```
**🛎 Helpful suggestions for mistypings** - Twin chimes in with class and variant values from your Tailwind config:
```bash
✕ ml-1.25 was not found
Try one of these classes:
- ml-1.5 > 0.375rem
- ml-1 > 0.25rem
- ml-10 > 2.5rem
```
**🖌️ Use the theme import to add values from your tailwind config**
```js
import { css, theme } from 'twin.macro'
const Input = () => <input css={css({ color: theme`colors.purple.500` })} />
```
See more examples [using the theme import →](https://github.com/ben-rogerson/twin.macro/pull/106)
**💡 Works with the official tailwind vscode plugin** - Avoid having to look up your classes with auto-completions straight from your Tailwind config - [setup instructions →](https://github.com/ben-rogerson/twin.macro/discussions/227)
**💥 Add !important to any class with a trailing or leading bang!**
```js
<div tw="hidden!" /> || <div tw="!hidden" />
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
<div css={{ "display": "none !important" }} />
```
Add !important to multiple classes with bracket groups:
```js
<div tw="(hidden ml-auto)!" />
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
<div css={{ "display": "none !important", "marginLeft": "auto !important" }} />
```
## Get started
Twin works with many modern stacks - take a look at these examples to get started:
### App build tools and libraries
- **Parcel**<br/>[styled-components](https://github.com/ben-rogerson/twin.examples/tree/master/react-styled-components) / [emotion](https://github.com/ben-rogerson/twin.examples/tree/master/react-emotion) / [emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/react-emotion-typescript)
- **Webpack**<br/>[styled-components (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/webpack-styled-components-typescript) / [emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/webpack-emotion-typescript)
- **Preact**<br/>[styled-components](https://github.com/ben-rogerson/twin.examples/tree/master/preact-styled-components) / [emotion](https://github.com/ben-rogerson/twin.examples/tree/master/preact-emotion) / [goober](https://github.com/ben-rogerson/twin.examples/tree/master/preact-goober)
- **Create React App**<br/>[styled-components](https://github.com/ben-rogerson/twin.examples/tree/master/cra-styled-components) / [emotion](https://github.com/ben-rogerson/twin.examples/tree/master/cra-emotion)
- **Vite**<br/>[styled-components (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/vite-styled-components-typescript) / [emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/vite-emotion-typescript) / [solid (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/vite-solid-typescript)
- **Jest / React Testing Library**<br/>[styled-components (ts) / emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/jest-testing-typescript)
### Advanced frameworks
- **Next.js**<br/>[styled-components](https://github.com/ben-rogerson/twin.examples/tree/master/next-styled-components) / [styled-components (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/next-styled-components-typescript) / [emotion](https://github.com/ben-rogerson/twin.examples/tree/master/next-emotion) / [emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/next-emotion-typescript) / [stitches (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/next-stitches-typescript)
- **T3 App**<br/>[styled-components (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/t3-styled-components-typescript) /
[emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/t3-emotion-typescript)
- **Blitz.js**<br/>[emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/blitz-emotion-typescript)
- **Gatsby**<br/>[styled-components](https://github.com/ben-rogerson/twin.examples/tree/master/gatsby-styled-components) / [emotion](https://github.com/ben-rogerson/twin.examples/tree/master/gatsby-emotion)
### Component libraries
- **Storybook**<br/>[styled-components (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/storybook-styled-components-typescript) / [emotion](https://github.com/ben-rogerson/twin.examples/tree/master/storybook-emotion) / [emotion (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/storybook-emotion-typescript)
- **yarn/npm workspaces + Next.js + shared ui components**<br/>[styled-components](https://github.com/ben-rogerson/twin.examples/tree/master/component-library-styled-components)
- **Yarn workspaces + Rollup**<br/>[emotion](https://github.com/ben-rogerson/twin.examples/tree/master/component-library-emotion)
- [**HeadlessUI** (ts)](https://github.com/ben-rogerson/twin.examples/tree/master/headlessui-typescript)
## Community
[Drop into our Discord server](https://discord.gg/Xj6x9z7) for announcements, help and styling chat.
<a href="https://discord.gg/Xj6x9z7"><img src="https://img.shields.io/discord/705884695400939552?label=discord&logo=discord" alt="Discord"></a>
## Resources
- 🔥 [Docs: The prop styling guide](https://github.com/ben-rogerson/twin.macro/blob/master/docs/prop-styling-guide.md) - A must-read guide to level up on prop styling
- 🔥 [Docs: The styled component guide](https://github.com/ben-rogerson/twin.macro/blob/master/docs/styled-component-guide.md) - A must-read guide on getting productive with styled components
- [Docs: Options](https://github.com/ben-rogerson/twin.macro/blob/master/docs/options.md) - Learn about the features you can tweak via the twin config
- [Plugin: babel-plugin-twin](https://github.com/ben-rogerson/babel-plugin-twin) - Use the tw and css props without adding an import
- [Example: Advanced theming](https://github.com/ben-rogerson/twin.macro/blob/master/docs/advanced-theming.md) - Add custom theming the right way using css variables
- [Example: React + Tailwind breakpoint syncing](https://gist.github.com/ben-rogerson/b4b406dffcc18ae02f8a6c8c97bb58a8) - Sync your tailwind.config.js breakpoints with react
- [Helpers: Twin VSCode snippets](https://gist.github.com/ben-rogerson/c6b62508e63b3e3146350f685df2ddc9) - For devs who want to type less
- [Plugins: VSCode plugins](https://github.com/ben-rogerson/twin.macro/discussions/227) - VScode plugins that work with twin
- [Article: "Why I Love Tailwind" by Max Stoiber](https://mxstbr.com/thoughts/tailwind) - Max (inventor of styled-components) shares his thoughts on twin
## Special thanks
This project stemmed from [babel-plugin-tailwind-components](https://github.com/bradlc/babel-plugin-tailwind-components) so a big shout out goes to [Brad Cornes](https://github.com/bradlc) for the amazing work he produced. Styling with tailwind.macro has been such a pleasure.
---
[Consider donating some 🍕 if you enjoy!](https://www.buymeacoffee.com/benrogerson)
================================================
FILE: docs/advanced-theming.md
================================================
# Theming with css variables
These examples show how to create themes using css variables rather than relying on the default `dark:` variant supplied in tailwind.
This technique is the preferred way to add a dark/light theme and allows you to add more themes when needed.
- [react + emotion](https://codesandbox.io/s/github/alexperronnet/codesandbox-examples/tree/master/react/twin-emotion-dark-mode-variables)
- [react + styled-components](https://codesandbox.io/s/github/alexperronnet/codesandbox-examples/tree/master/react/twin-styled-components-dark-mode-variables)
- [gatsby + emotion](https://codesandbox.io/s/github/alexperronnet/codesandbox-examples/tree/master/gatsby/twin-emotion-dark-mode-variables)
- [gatsby + styled-components](https://codesandbox.io/s/github/alexperronnet/codesandbox-examples/tree/master/gatsby/twin-styled-components-dark-mode-variables)
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/arbitrary-values.md
================================================
# Arbitrary values
Twin supports the same arbitrary values syntax popularized by Tailwind’s [jit ("Just-in-Time") mode](https://tailwindcss.com/docs/just-in-time-mode).
```js
tw`top-[calc(100vh - 2rem)]`
// ↓ ↓ ↓ ↓ ↓ ↓
;({ top: 'calc(100vh - 2rem)' })
```
Arbitrary values use square brackets to allow custom css values instead of classes built from your tailwind.config.js.
This is a good solution for those unique “once off” values that every project requires which you may not want to add to your tailwind.config.js.
## Supported classes
Generally the rule is: Dynamic classes - like `bg-red-500` - support arbitrary values, while static classes like `block` don’t.
> For fully custom css properties and values use [arbitrary properties](https://tailwindcss.com/docs/adding-custom-styles#arbitrary-properties).
## Spaces in values
In Tailwind, when we add classes within the className prop/attribute, values cannot have spaces in them.
```js
// Spaced values won’t work in Tailwind
;<div className="h-[calc(1000px - 4rem)]" />
```
But with twin, spaces are okay because Twin is not restricted by the spacing rules of the className prop:
```js
// Twin supports values with spaces
;<div tw="h-[calc(1000px - 4rem)]" />
// Classes can be added on multiple lines when using template literals
;<div
css={tw`
h-[calc(1000px - 4rem)]
`}
/>
```
And we can also use Arbitrary values within variant groups:
```js
;<div
css={tw`
first:(h-[calc(1000px - 4rem)] mt-5)
`}
/>
```
## Dynamic values
Just like Tailwind, values can't be dynamically added because Twin doesn’t have the ability to read the variables before converting to a css object:
```js
// Dynamic values without the tw call won’t work
;<div css={tw`mt-[${size === 'lg' ? '22px' : '17px'}]`}></div>
```
You’ll need to use a full tw class definition to make dynamic values possible:
```js
// Dynamic values work when constructed like this
;<div css={[size === 'lg' ? tw`mt-[22px]` : tw`mt-[17px]`]}></div>
```
## Resources
- [The PR for arbitrary values](https://github.com/ben-rogerson/twin.macro/pull/447)
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/customizing-config.md
================================================
# Customizing the Tailwind config
For style customizations, add a `tailwind.config.js` in your project root.
> It’s important to know that you don’t need a `tailwind.config.js` to use Twin. You already have access to every class with every variant.
Choose from one of the following configs:
- a) Start with an empty config:
```js
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {},
},
},
plugins: [],
}
```
- b) Start with a [full config](https://raw.githubusercontent.com/tailwindcss/tailwindcss/master/stubs/defaultConfig.stub.js):
```bash
# cd into your project folder then:
npx tailwindcss-cli@latest init --full
```
## Plugins
You can use all Tailwind plugins with twin, some popular ones are [tailwindcss-typography](https://github.com/tailwindlabs/tailwindcss-typography) and [@tailwindcss/forms](https://github.com/tailwindlabs/tailwindcss-forms).
## Resources
- Official [Tailwind theme docs](https://tailwindcss.com/docs/theme)
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/fonts.md
================================================
# Fonts
You can add `@font-face` definitions either [in the global styles provider](#add-the-font-face-in-the-global-styles-provider) or [in a traditional .css file](#add-the-font-face-in-a-traditional-css-file).
## Add `@font-face` in the Global styles provider
An option is to add the font with the global provider that comes with your css-in-js library. Here are some examples:
### Styled-components
```js
// styles/GlobalStyles.js
import React from 'react'
import { createGlobalStyle } from 'styled-components'
import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro'
const CustomStyles = createGlobalStyle`
@font-face {
font-family: 'Foo';
src: url('/path/to/exampleFont.woff') format('woff');
font-style: normal;
font-weight: 400;
/* https://styled-components.com/docs/faqs#how-do-i-fix-flickering-text-after-server-side-rendering */
font-display: fallback;
}
`
const GlobalStyles = () => (
<>
<BaseStyles />
<CustomStyles />
</>
)
export default GlobalStyles
```
[createGlobalStyle docs →](https://styled-components.com/docs/api#createglobalstyle)
### Emotion
```js
// styles/GlobalStyles.js
import React from 'react'
import { Global, css } from '@emotion/react'
import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro'
const customStyles = css`
@font-face {
font-family: 'Foo';
src: url('/path/to/exampleFont.woff') format('woff');
font-style: normal;
font-weight: 400;
/* https://styled-components.com/docs/faqs#how-do-i-fix-flickering-text-after-server-side-rendering */
font-display: fallback;
}
`
const GlobalStyles = () => (
<>
<BaseStyles />
<Global styles={customStyles} />
</>
)
export default GlobalStyles
```
[Global docs →](https://emotion.sh/docs/globals)
### Goober
```js
// styles/GlobalStyles.js
import React from 'react'
import { createGlobalStyle } from 'styled-components'
import tw, { GlobalStyles as BaseStyles } from 'twin.macro'
const CustomStyles = createGlobalStyle`
@font-face {
font-family: 'Foo';
src: url('/path/to/exampleFont.woff') format('woff');
font-style: normal;
font-weight: 400;
/* https://styled-components.com/docs/faqs#how-do-i-fix-flickering-text-after-server-side-rendering */
font-display: fallback;
}
`
const GlobalStyles = () => (
<>
<BaseStyles />
<CustomStyles />
</>
)
export default GlobalStyles
```
[createGlobalStyle docs →](https://goober.js.org/api/createGlobalStyles)
## Add the `@font-face` in a .css file and import it
This method may help to remove text flickering in some frameworks.
```css
/* styles/fonts.css */
@font-face {
font-family: 'Foo';
src: url('/path/to/exampleFont.woff') format('woff');
font-style: normal;
font-weight: 400;
font-display: fallback;
}
```
```js
// index.js / _app.js
import '../styles/fonts.css'
// ...
```
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/group.md
================================================
# Using the group className
There’s a couple of Tailwind classes that need to be added to React elements as a `className`.
These classes are the `peer` and the `group` classes.
A className is used so variants like `group-hover:` and `peer-hover:` can use the className as an anchor to allow their styles to work.
Here’s how we use the `group` classes in twin:
```js
import 'twin.macro'
export default () => (
<button className="group">
<div tw="group-hover:bg-black">Child 1</div>
<div tw="group-hover:font-bold">Child 2</div>
</button>
)
```
When working in emotion and styled-components without the `group` classes, the equivalent looks like this:
```js
import tw, { styled } from 'twin.macro'
const Group = tw.button``
Group.Child1 = styled.div`
${Group}:hover & {
${tw`bg-black`}
}
`
Group.Child2 = styled.div`
${Group}:hover & {
${tw`font-bold`}
}
`
export default () => (
<Group>
<Group.Child1>Child 1</Group.Child1>
<Group.Child2>Child 2</Group.Child2>
</Group>
)
```
Not as great right?
Here’s some ways you can improve upon that:
## Attrs in 💅 styled‑components
In styled-components we have a `styled` function called `attrs`.
Here’s what the docs have to say about it:
> The rule of thumb is to use attrs when you want every instance of a styled component to have that prop, and pass props directly when every instance needs a different one.<br/>- [styled-components docs](https://styled-components.com/docs/faqs#when-to-use-attrs)
But we can also put it to use to define the `group` class in Tailwind.
Rather than adding `className="group"` directly onto your jsx element, the class can be more tightly coupled with your styles:
```js
import tw, { styled } from 'twin.macro'
const Group = styled.button.attrs({ className: 'group' })``
Group.Child1 = tw.div`group-hover:bg-black`
Group.Child2 = tw.div`group-hover:font-bold`
export default () => (
<Group>
<Group.Child1>Child 1</Group.Child1>
<Group.Child2>Child 2</Group.Child2>
</Group>
)
```
## Attrs in emotion
Unfortunately emotion [doesn’t have any plans](https://github.com/emotion-js/emotion/issues/821) to add `attrs` so the easiest option is to add `className="group"` directly on the jsx element:
```js
import tw from 'twin.macro'
const Group = tw.button``
Group.Child1 = tw.div`group-hover:bg-black`
Group.Child2 = tw.div`group-hover:font-bold`
export default () => (
<Group className="group">
<Group.Child1>Child 1</Group.Child1>
<Group.Child2>Child 2</Group.Child2>
</Group>
)
```
But if you’d like similar functionality to the attr function in styled-components then you could add the className using a [Higher-Order Component (HOC)](https://reactjs.org/docs/higher-order-components.html):
```js
import tw from 'twin.macro'
const withAttrs = (Component, attrs) => props =>
<Component {...attrs} {...props} />
const Button = tw.button``
const Group = withAttrs(Button, { className: 'group' })
Group.Child1 = tw.div`group-hover:bg-black`
Group.Child2 = tw.div`group-hover:font-bold`
export default () => (
<Group>
<Group.Child1>Child 1</Group.Child1>
<Group.Child2>Child 2</Group.Child2>
</Group>
)
```
You could also use `defaultProps` but it’s [going to be deprecated at some stage](https://twitter.com/dan_abramov/status/1133878326358171650), which is a shame because it’s a really nice way to add the className:
```js
import tw from 'twin.macro'
const Group = tw.button``
Group.defaultProps = { className: 'group' }
Group.Child1 = tw.div`group-hover:bg-black`
Group.Child2 = tw.div`group-hover:font-bold`
export default () => (
<Group>
<Group.Child1>Child 1</Group.Child1>
<Group.Child2>Child 2</Group.Child2>
</Group>
)
```
## Resources
- [Quick Start Guide to Attrs in styled-components](https://scalablecss.com/styled-components-attrs/)
- [Emotion issue: .attrs equivalent](https://github.com/emotion-js/emotion/issues/821)
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/index.md
================================================
[](#documentation)
# Documentation
[](#usage)
## Usage
- [The prop styling guide](https://github.com/ben-rogerson/twin.macro/blob/master/docs/prop-styling-guide.md)
- [Styled component guide](https://github.com/ben-rogerson/twin.macro/blob/master/docs/styled-component-guide.md)
[](#configuration)
## Configuration
- [Twin config options](https://github.com/ben-rogerson/twin.macro/blob/master/docs/options.md)
- [Customizing the tailwind config](https://github.com/ben-rogerson/twin.macro/blob/master/docs/customizing-config.md)
- [Fonts](https://github.com/ben-rogerson/twin.macro/blob/master/docs/fonts.md)
[](#theming)
## Theming
- [Theming with css variables](https://github.com/ben-rogerson/twin.macro/blob/master/docs/advanced-theming.md)
[](#classes)
## More
- [group](https://github.com/ben-rogerson/twin.macro/blob/master/docs/group.md)
================================================
FILE: docs/options.md
================================================
[](#twin-config-options)
# Twin config options
These options are available in your [twin config](#twin-config-location):
| Name | Default | Description |
| --------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| config | `"tailwind.config.js"` | The path to your Tailwind config. Also takes a config object. |
| preset | `"emotion"` | The css-in-js library behind the scenes.<br>Also supported: `"styled-components"` `"goober"` `"stitches"` `"solid"` |
| dataTwProp | `true` | Add a prop to jsx components in development showing the original tailwind classes.<br/> Use `"all"` to keep the prop in production. |
| debug | `false` | Display information in your terminal about the Tailwind class conversions. |
| disableShortCss | `true` | Disable converting short css within the tw import/prop. |
| hasLogColors | `true` | Disable log colors to remove the glyphs when the color display is not supported |
| includeClassNames | `false` | Check className attributes for tailwind classes to convert. |
| dataCsProp | `true` | Add a prop to your elements in development so you can see the original cs prop classes, eg: `<div data-cs="maxWidth[1em]" />`. |
| disableCsProp | `true` | Disable twin from reading values specified in the cs prop. |
| sassyPseudo | `false` | Some css-in-js frameworks require the `&` in selectors like `&:hover`, this option ensures it’s added. |
| moveKeyframesToGlobalStyles | `false` | `@keyframes` are added next to the `animation-x` classes - this option can move them to global styles instead. |
### Options
---
<details>
<summary><strong>config</strong></summary>
<br />
```js
config: 'tailwind.config.js', // Path to the tailwind config
```
Set a custom location by specifying a path to your tailwind.config.js file.
**Passing in a config**: The config option also accepts a config object:
```js
// babel-plugin-macros.config.js
const tailwindConfig = {
theme: {
extend: {
colors: {
primary: '#ff0000',
},
},
},
}
module.exports = {
twin: {
config: tailwindConfig,
},
}
```
This can be useful in component libraries, tests, or just to remove the need for a tailwind.config.js file.
**Monorepos / Workspaces**: The tailwind.config.js is commonly added as a shared file in the project root so you may need to add a `path.resolve` on the pathname in the twin config:
```js
// babel-plugin-macros.config.js
const path = require('path')
module.exports = {
twin: {
config: path.resolve(__dirname, '../../', 'tailwind.config.js'),
},
}
```
</details>
---
<details>
<summary><strong>preset</strong></summary>
<br />
```js
preset: 'emotion', // Set the css-in-js library to use with twin
```
Supports: `'emotion'` / `'styled-components'` / `'goober'` / `'stitches'`.
The preset option primarily assigns the library imports for `css`, `styled` and `GlobalStyles`.
</details>
---
<details>
<summary><strong>dataTwProp</strong></summary>
<br />
```js
dataTwProp: false, // Set the display of the data-tw prop on jsx elements
```
The `data-tw` prop gets added to your elements while in development so you can see the original tailwind classes:
```js
<div data-tw="bg-black" />
```
If you add the value `all`, twin will add the data-tw prop in production as well as development.
</details>
---
<details>
<summary><strong>debug</strong></summary>
<br />
```js
debug: true, // Display information about class conversions
```
When debug mode is on, twin displays logs on class conversions.
This feedback only displays in development.
## </details>
---
<details>
<summary><strong>hasLogColors</strong></summary>
<br />
```js
hasLogColors: false, // Disable log colors (removes those glyphs in your console/overlay)
```
Sometimes the display of errors and suggestions are pretty poor due to lack of support for custom colors. Use this setting to disable the colors so you can actually read the messages.
</details>
---
<details>
<summary><strong>disableShortCss</strong></summary>
<br />
```js
disableShortCss: false, // Enable converting short css within the tw import/prop
```
When set to `true`, this will throw an error if short css is added within the tw import or tw prop.
Disable short css completely with `dataCsProp: false`.
</details>
---
<details>
<summary><strong>includeClassNames</strong></summary>
<br />
```js
includeClassNames: true, // Check className props for tailwind classes to convert
```
When a tailwind class is found in a className prop, it’s plucked out, converted and delivered to the css-in-js library.
- Unmatched classes are skipped and preserved within the className
- Suggestions aren’t shown for unmatched classes like they are for the tw prop
- The tw and css props can be used on the same jsx element
- Limitation: classNames with conditional props or variables aren’t touched, eg: `<div className={isBlock && "block"} />`
</details>
---
<details>
<summary><strong>dataCsProp</strong></summary>
<br />
```js
dataCsProp: false, // JSX prop twin adds that shows the original cs prop classes
```
If you add short css within the `cs` prop then twin will add a `data-cs` prop to preserve the css you added.
This option controls the display of the prop.
Shows in development only.
</details>
---
<details>
<summary><strong>disableCsProp</strong></summary>
<br />
```js
disableCsProp: true, // Whether to read short css values added in a `cs` prop
```
If you're using the cs prop for something else or don’t want other developers using the feature you can disable it with this option.
</details>
---
<details>
<summary><strong>sassyPseudo</strong></summary>
<br />
```js
sassyPseudo: true, // Prefix pseudo selectors with a `&`
```
Some css-in-js frameworks require the `&` in selectors like `&:hover`, this option ensures it’s added.
</details>
---
<details>
<summary><strong>moveKeyframesToGlobalStyles</strong></summary>
<br />
```js
moveKeyframesToGlobalStyles: true, // Avoid @keyframes next to animation-x classes
```
Add `@keyframes` matching an `animation-x` class to global styles instead of alongside the `animation-x` class.<br/>
In stitches this gets set to `true` to make animations work.
</details>
---
[](#twin-config-location)
## Twin config location
Twin’s config can be added in a couple of different files.
a) Either in `babel-plugin-macros.config.js`:
```js
// babel-plugin-macros.config.js
module.exports = {
twin: {
// add options here
},
}
```
b) Or in `package.json`:
```js
// package.json
"babelMacros": {
"twin": {
// add options here
}
},
```
---
[‹ Documentation index](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/prop-styling-guide.md
================================================
# The prop styling guide
## Basic styling
Use Twin’s tw prop to add Tailwind classes onto jsx elements:
```js
import 'twin.macro'
const Component = () => (
<div tw="flex w-full">
<div tw="w-1/2"></div>
<div tw="w-1/2"></div>
</div>
)
```
- Use the tw prop when conditional styles aren’t needed
- Any import from `twin.macro` activates the tw prop
- Remove the need for an import with [babel-plugin-twin](https://github.com/ben-rogerson/babel-plugin-twin)
## Conditional styling
To add conditional styles, nest the styles in an array and use the `css` prop:
```js
import tw from 'twin.macro'
const Component = ({ hasBg }) => (
<div
css={[
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
]}
>
<div tw="w-1/2" />
<div tw="w-1/2" />
</div>
)
```
<details>
<summary>TypeScript example</summary>
```tsx
import tw from 'twin.macro'
interface ComponentProps {
hasBg?: string
}
const Component = ({ hasBg }: ComponentProps) => (
<div
css={[
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
]}
>
<div tw="w-1/2" />
<div tw="w-1/2" />
</div>
)
```
</details>
- Twin doesn’t own the css prop, the prop comes from your css-in-js library
- Adding values to an array makes it easier to define base styles, conditionals and vanilla css
- Use multiple lines to organize styles within the backticks ([template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals))
## Overriding styles
Use the `tw` prop after the css prop to add any overriding styles:
```js
import tw from 'twin.macro'
const Component = () => (
<div css={tw`text-white`} tw="text-black">
Has black text
</div>
)
```
## Keeping jsx clean
It’s no secret that when tailwind class sets become larger, they obstruct the readability of other jsx props.
To clean up the jsx, lift the styles out and group them as named entries in an object:
```js
import tw from 'twin.macro'
const styles = {
container: ({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
],
column: tw`w-1/2`,
}
const Component = ({ hasBg }) => (
<section css={styles.container({ hasBg })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
```
<details>
<summary>TypeScript example</summary>
```js
import tw from 'twin.macro'
interface ContainerProps {
hasBg?: boolean;
}
const styles = {
container: ({ hasBg }: ContainerProps) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
],
column: tw`w-1/2`,
}
const Component = ({ hasBg }: ContainerProps) => (
<section css={styles.container({ hasBg })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
```
</details>
## Variants with many values
When a variant has many values (eg: `variant="light/dark/etc"`), name the class set in an object and use a prop to grab the entry containing the styles:
```js
import tw from 'twin.macro'
const containerVariants = {
// Named class sets
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const styles = {
container: ({ variant = 'dark' }) => [
tw`flex w-full`,
containerVariants[variant], // Grab the variant style via a prop
],
column: tw`w-1/2`,
}
const Component = ({ variant }) => (
<section css={styles.container({ variant })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
```
<details>
<summary>TypeScript example</summary>
Use the `TwStyle` import to type tw blocks:
```tsx
import tw, { TwStyle } from 'twin.macro'
type WrapperVariant = 'light' | 'dark' | 'crazy'
interface ContainerProps {
variant?: WrapperVariant
}
const containerVariants: Record<WrapperVariant, TwStyle> = {
// Named class sets
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const styles = {
container: ({ variant = 'dark' }: ContainerProps) => [
tw`flex w-full`,
containerVariants[variant], // Grab the variant style via a prop
],
column: tw`w-1/2`,
}
const Component = ({ variant }: ContainerProps) => (
<section css={styles.container({ variant })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
```
</details>
## Interpolation workaround
Due to Babel limitations, tailwind classes and arbitrary properties can’t have any part of them dynamically created.
So interpolated values like this won’t work:
```js
<div tw="mt-${spacing === 'sm' ? 2 : 4}" /> // Won't work with tailwind classes
<div tw="[margin-top:${spacing === 'sm' ? 2 : 4}rem]" /> // Won't work with arbitrary properties
```
This is because babel doesn’t know the values of the variables and so twin can’t make a conversion to css.
Instead, define the classes in objects and grab them using props:
```js
import tw from 'twin.macro'
const styles = { sm: tw`mt-2`, lg: tw`mt-4` }
const Component = ({ spacing = 'sm' }) => <div css={styles[spacing]} />
```
Or combine vanilla css with twins `theme` import:
```js
import { theme } from 'twin.macro'
// Use theme values from your tailwind config
const styles = { sm: theme`spacing.2`, lg: theme`spacing.4` }
const Component = ({ spacing = 'sm' }) => (
<div css={{ marginTop: styles[spacing] }} />
)
```
Or we can always fall back to vanilla css, which can interpolate anything:
```js
import 'twin.macro'
const Component = ({ width = 5 }) => <div css={{ maxWidth: `${width}rem` }} />
```
## Custom selectors (Arbitrary variants)
Use square-bracketed arbitrary variants to style elements with a custom selector:
```js
import tw from 'twin.macro'
const buttonStyles = tw`
bg-black
[> i]:block
[> span]:(text-blue-500 w-10)
`
const Component = () => (
<button css={buttonStyles}>
<i>Icon</i>
<span>Label</span>
</button>
)
```
<details>
<summary>More examples</summary>
<br/>
```js
// Style the current element based on a theming/scoping className
;<body className="dark-theme">
<div tw="[.dark-theme &]:(bg-black text-white)">Dark theme</div>
</body>
// Add custom group selectors
;<button className="group" disabled>
<span tw="[.group:disabled &]:text-gray-500">Text gray</span>
</button>
// Add custom height queries
;<div tw="[@media (min-height: 800px)]:hidden">
This window is less than 800px height
</div>
// Use custom at-rules like @supports
;<div tw="[@supports (display: grid)]:grid">A grid</div>
// Style the current element based on a dynamic className
const Component = ({ isLarge }) => (
<div className={isLarge && 'is-large'} tw="text-base [&.is-large]:text-lg">
...
</div>
)
```
</details>
## Custom class values (Arbitrary values)
Custom values can be added to many tailwind classes by using square brackets to define the custom value:
```js
;<div tw="top-[calc(100vh - 2rem)]" />
// ↓ ↓ ↓ ↓ ↓ ↓
<div css={{
"top": "calc(100vh - 2rem)"
}} />
```
[Read more about Arbitrary values →](https://github.com/ben-rogerson/twin.macro/blob/master/docs/arbitrary-values.md)
## Custom css
Basic css is added using [arbitrary properties](https://tailwindcss.com/docs/adding-custom-styles#arbitrary-properties) or within vanilla css which supports more advanced use cases like dynamic/interpolated values.
### Simple css styling
To add simple custom styling, use [arbitrary properties](https://tailwindcss.com/docs/adding-custom-styles#arbitrary-properties):
```js
// Set css variables
<div tw="[--my-width-variable:calc(100vw - 10rem)]" />
// Set vendor prefixes
<div tw="[-webkit-line-clamp:3]" />
// Set grid areas
<div tw="[grid-area:1 / 1 / 4 / 2]" />
```
Use arbitrary properties with variants or twins grouping features:
```js
<div tw="block md:(relative [grid-area:1 / 1 / 4 / 2])" />
```
Arbitrary properties also work with the `tw` import:
```js
import tw from 'twin.macro'
;<div
css={tw`
block
md:(relative [grid-area:1 / 1 / 4 / 2])
`}
/>
```
- Add a bang to make the custom css !important: `![grid-area:1 / 1 / 4 / 2]`
- Arbitrary properties can have camelCase properties: `[gridArea:1 / 1 / 4 / 2]`
### Advanced css styling
The css prop accepts a sass-like syntax, allowing both custom css and tailwind styles with values that can come from your tailwind config:
```js
import tw, { css, theme } from 'twin.macro'
const Components = () => (
<input
css={[
tw`text-blue-500 border-2`,
css`
-webkit-tap-highlight-color: transparent; /* add css styles */
background-color: ${theme`colors.red.500`}; /* use the theme import to add config values */
&::selection {
${tw`text-purple-500`}; /* style with tailwind classes */
}
`,
]}
/>
)
```
But it’s often cleaner to use an object to add styles as it avoids the interpolation cruft seen above:
```js
import tw, { css, theme } from 'twin.macro'
const Components = () => (
<input
css={[
tw`text-blue-500 border-2`,
css({
WebkitTapHighlightColor: 'transparent', // css properties are camelCased
backgroundColor: theme`colors.red.500`, // values don’t require interpolation
'&::selection': tw`text-purple-500`, // single line tailwind selector styling
}),
]}
/>
)
```
## Learn more
- [Styled component guide](https://github.com/ben-rogerson/twin.macro/blob/master/docs/styled-component-guide.md) - A must-read guide on getting productive with styled-components
## Resources
- [babel-plugin-twin](https://github.com/ben-rogerson/babel-plugin-twin) - Use the tw and css props without adding an import
- [React + Tailwind breakpoint syncing](https://gist.github.com/ben-rogerson/b4b406dffcc18ae02f8a6c8c97bb58a8) - Sync your tailwind.config.js breakpoints with react
- [Twin VSCode snippits](https://gist.github.com/ben-rogerson/c6b62508e63b3e3146350f685df2ddc9) - For devs who want to type less
- [Twin VSCode extensions](https://github.com/ben-rogerson/twin.macro/discussions/227) - For faster class suggestions and feedback
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/screen-import.md
================================================
# Screen import
The screen import creates media queries for custom css that sync with your tailwind config screen values (sm, md, lg, etc).
**Usage with the css prop**
```js
import tw, { screen, css } from 'twin.macro'
const styles = [
screen`sm`({ display: 'block', ...tw`inline` }),
]
<div css={styles} />
```
**Usage with styled components**
```js
import tw, { styled, screen, css } from 'twin.macro'
const Component = styled.div(() => [
screen`sm`({ display: 'block', ...tw`inline` }),
])
<Component />
```
## Screen as a key
Without the styles, the screen import just creates a media query, so you can use it as a key:
```js
<div
css={{
[screen`2xl`]: { display: 'block' },
}}
/>
// ↓ ↓ ↓ ↓ ↓ ↓
<div
css={{
'@media (min-width: 1536px)': { display: 'block' },
}}
/>
```
## Relaxed usage
The screen import can be used in different ways:
```js
screen`sm`({ ... })
screen('sm')({ ... })
screen(`sm`)({ ... })
screen.sm({ ... }) // Dot syntax can’t be used when the screen begins with a number, eg: screen.2xl
```
## Custom media queries
Since the screen import always adds a min-width query, it’s not suitable for constructing custom media queries.
So to add custom media queries, use the theme import instead.
**With the css prop**
```js
import tw, { theme } from 'twin.macro'
const styles = {
[`@media (max-width: ${theme`screens.sm`})`]: {
display: 'block',
...tw`inline`,
},
}
;<div css={styles} />
```
**With a styled component**
```js
import tw, { styled, theme } from 'twin.macro'
const Component = styled.div({
[`@media (max-width: ${theme`screens.sm`})`]: {
display: 'block',
...tw`inline`,
},
})
<Component />
```
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: docs/styled-component-guide.md
================================================
# Styled component guide
## Basic styling
Use Twin’s `tw` import to create and style new components with Tailwind classes:
```js
import tw from 'twin.macro'
const Wrapper = tw.section`flex w-full`
const Column = tw.div`w-1/2`
const Component = () => (
<Wrapper>
<Column></Column>
<Column></Column>
</Wrapper>
)
```
## Conditional styling
To add conditional styles, nest the styles in an array and use the `styled` import:
```js
import tw, { styled } from 'twin.macro'
const Container = styled.div(({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
])
const Column = tw.div`w-1/2`
const Component = ({ hasBg }) => (
<Container {...{ hasBg }}>
<Column></Column>
<Column></Column>
</Container>
)
```
<details>
<summary>TypeScript example</summary>
```tsx
import tw, { styled } from 'twin.macro'
interface ContainerProps {
hasBg?: string
}
const Container = styled.div<ContainerProps>(({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
])
const Column = tw.div`w-1/2`
const Component = ({ hasBg }: ContainerProps) => (
<Container {...{ hasBg }}>
<Column></Column>
<Column></Column>
</Container>
)
```
</details>
- Adding styles in an array makes it easier to separate base styles, conditionals and vanilla css
- The `styled` import comes from the css-in-js library, twin just exports it
- Use multiple lines to organize styles within the backticks ([template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals))
## Variants with many values
When a variant has many values (eg: `variant="light/dark/etc"`), name the class set in an object and use a prop to grab the entry containing the styles. Note that you must return a function as follows:
```js
import tw, { styled } from 'twin.macro'
const containerVariants = {
// Named class sets
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const Container = styled.section(() => [
// Return a function here
tw`flex w-full`,
({ variant = 'dark' }) => containerVariants[variant], // Grab the variant style via a prop
])
const Column = tw.div`w-1/2`
const Component = () => (
<Container variant="light">
<Column></Column>
<Column></Column>
</Container>
)
```
<details>
<summary>TypeScript example</summary>
Use the `TwStyle` import to type tw blocks:
```tsx
import tw, { styled, TwStyle } from 'twin.macro'
type ContainerVariant = 'light' | 'dark' | 'crazy'
interface ContainerProps {
variant?: ContainerVariant
}
// Use the `TwStyle` import to type tw blocks
const containerVariants: Record<ContainerVariant, TwStyle> = {
// Named class sets
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const Container = styled.section<ContainerProps>(() => [
// Return a function here
tw`flex w-full`,
({ variant = 'dark' }) => containerVariants[variant], // Grab the variant style via a prop
])
const Column = tw.div`w-1/2`
const Component = () => (
<Container variant="light">
<Column></Column>
<Column></Column>
</Container>
)
```
</details>
## Interpolation workaround
Due to Babel limitations, tailwind classes and arbitrary properties can’t have any part of them dynamically created.
So interpolated values like this won’t work:
```js
const Component = styled.div(({ spacing }) => [
tw`mt-${spacing === 'sm' ? 2 : 4}`, // Won't work with tailwind classes
`[margin-top:${spacing === 'sm' ? 2 : 4}rem]`, // Won't work with arbitrary properties
])
```
This is because babel doesn’t know the values of the variables and so twin can’t make a conversion to css.
Instead, define the classes in objects and grab them using props:
```js
import tw, { styled } from 'twin.macro'
const styles = { sm: tw`mt-2`, lg: tw`mt-4` }
const Card = styled.div(({ spacing }) => styles[spacing])
const Component = ({ spacing = 'sm' }) => <Card spacing={spacing} />
```
Or combine vanilla css with twins `theme` import:
```js
import { styled, theme } from 'twin.macro'
// Use theme values from your tailwind config
const styles = { sm: theme`spacing.2`, lg: theme`spacing.4` }
const Card = styled.div(({ spacing }) => ({ marginTop: styles[spacing] }))
const Component = ({ spacing = 'sm' }) => <Card spacing={spacing} />
```
Or you can always fall back to “vanilla css” which can interpolate anything:
```js
import { styled } from 'twin.macro'
const Card = styled.div(({ spacing }) => ({
marginTop: `${spacing === 'sm' ? 2 : 4}rem`,
}))
const Component = ({ spacing = 'sm' }) => <Card spacing={spacing} />
```
## Overriding styles
You can use the `tw` jsx prop to override styles in the styled-component:
```tsx
import tw from 'twin.macro'
const Text = tw.div`text-white`
const Component = () => <Text tw="text-black">Has black text</Text>
```
## Extending components
Wrap components using the component extending feature to copy/override styles from another component:
```tsx
import tw, { styled } from 'twin.macro'
const Container = tw.div`bg-black text-white`
// Extend with tw: for basic styling
const BlueContainer = tw(Container)`bg-blue-500`
// Or extend with styled: For conditionals
const RedContainer = styled(Container)(({ hasBorder }) => [
tw`bg-red-500 text-black`,
hasBorder && tw`border`,
])
// Extending more than once like this is not recommended
const BlueContainerBold = tw(BlueContainer)`font-bold`
const Component = () => (
<>
<Container />
<BlueContainer />
<RedContainer hasBorder />
</>
)
```
## Changing elements
Reuse styled components with a different element using the `as` prop:
```tsx
import tw from 'twin.macro'
const Heading = tw.h1`font-bold` // or styled.h1(tw`font-bold`)
const Component = () => (
<>
<Heading>I am a H1</Heading>
<Heading as="h2">I am a H2 with the same style</Heading>
</>
)
```
## Custom selectors (Arbitrary variants)
Use square-bracketed arbitrary variants to style elements with a custom selector:
```js
import tw from 'twin.macro'
const Button = tw.button`
bg-black
[> i]:block
[> span]:(text-blue-500 w-10)
`
const Component = () => (
<Button>
<i>Icon</i>
<span>Label</span>
</Button>
)
```
<details>
<summary>More examples</summary>
<br/>
```js
// Style the current element based on a theming/scoping className
const Theme = tw.div`[.dark-theme &]:(bg-black text-white)`
;<body className="dark-theme">
<Theme>Dark theme</Theme>
</body>
// Add custom group selectors
const Text = tw.div`[.group:disabled &]:text-gray-500`
;<button className="group" disabled>
<Text>Text gray</Text>
</button>
// Add custom height queries
const SmallHeightOnly = tw.div`[@media (min-height: 800px)]:hidden`
;<SmallHeightOnly>Burger menu</SmallHeightOnly>
// Use custom at-rules like @supports
const Grid = tw.div`[@supports (display: grid)]:grid`
;<Grid>A grid</Grid>
// Style the component based on a dynamic className
const Text = tw.div`text-base [&.is-large]:text-lg`
const Container = ({ isLarge }) => (
<Text className={isLarge ? 'is-large' : null}>...</Text>
)
```
</details>
## Custom class values (Arbitrary values)
Custom values can be added to many tailwind classes by using square brackets to define the custom value:
```js
tw.div`top-[calc(100vh - 2rem)]`
// ↓ ↓ ↓ ↓ ↓ ↓
styled.div({ top: 'calc(100vh - 2rem)' })
```
[Read more about Arbitrary values →](https://github.com/ben-rogerson/twin.macro/blob/master/docs/arbitrary-values.md)
## Custom css
Basic css is added using [arbitrary properties](https://tailwindcss.com/docs/adding-custom-styles#arbitrary-properties) or within vanilla css which supports more advanced use cases like dynamic/interpolated values.
### Simple css styling
To add simple custom styling, use [arbitrary properties](https://tailwindcss.com/docs/adding-custom-styles#arbitrary-properties):
```js
// Set css variables
tw.div`[--my-width-variable:calc(100vw - 10rem)]`
// Set vendor prefixes
tw.div`[-webkit-line-clamp:3]`
// Set grid areas
tw.div`[grid-area:1 / 1 / 4 / 2]`
```
Use arbitrary properties with variants or twins grouping features:
```js
tw.div`block md:(relative [grid-area:1 / 1 / 4 / 2])`
```
Use a theme value to grab a value from your tailwind.config:
```js
tw.div`[color:theme('colors.gray.300')]`
tw.div`[margin:theme('spacing[2.5]')]`
tw.div`[box-shadow: 5px 10px theme('colors.black')]`
```
- Add a bang to make the custom css !important: `![grid-area:1 / 1 / 4 / 2]`
- Arbitrary properties can have camelCase properties: `[gridArea:1 / 1 / 4 / 2]`
### Advanced css styling
The styled import accepts a sass-like syntax, allowing both custom css and tailwind styles with values that can come from your tailwind config:
```js
import tw, { styled, css, theme } from 'twin.macro'
const Input = styled.div`
${css`
-webkit-tap-highlight-color: transparent; /* add css styles */
background-color: ${theme`colors.red.500`}; /* add values from your tailwind config */
${tw`text-blue-500 border-2`}; /* tailwind classes */
&::selection {
${tw`text-purple-500`}; /* style custom css selectors with tailwind classes */
}
`}
`
const Component = () => <Input />
```
- Prefix css styles with the `css` import to apply css highlighting in your editor
- Add semicolons to the end of each line
It can be cleaner to use an object to add styles as it avoids the interpolation cruft seen in the last example:
```js
import tw, { styled, theme } from 'twin.macro'
const Input = styled.div({
WebkitTapHighlightColor: 'transparent', // css properties are camelCased
backgroundColor: theme`colors.red.500`, // values don’t require interpolation
...tw`text-blue-500 border-2`, // merge tailwind classes into the container
'&::selection': tw`text-purple-500`, // allows single-line tailwind selector styling
})
const Component = () => <Input />
```
### Mixing css with tailwind classes
Mix tailwind classes and custom css in an array:
```js
import tw, { styled } from 'twin.macro'
const Input = styled.div(({ tapColor }) => [
tw`block`,
`-webkit-tap-highlight-color: ${tapColor};`,
])
const Component = () => <Input tapColor="red" />
```
When you move the styles out of jsx, prefix them with the `css` import:
```js
import tw, { styled, css } from 'twin.macro'
const widthStyles = ({ tapColor }) => css`
-webkit-tap-highlight-color: ${tapColor};
`
const Input = styled.div(({ tapColor }) => [
tw`block`,
widthStyles({ tapColor }),
])
const Component = () => <Input tapColor="red" />
```
## Learn more
- [Prop styling guide](https://github.com/ben-rogerson/twin.macro/blob/master/docs/prop-styling-guide.md) - A must-read guide to level up on prop styling
## Resources
- [babel-plugin-twin](https://github.com/ben-rogerson/babel-plugin-twin) - Use the tw and css props without adding an import
- [React + Tailwind breakpoint syncing](https://gist.github.com/ben-rogerson/b4b406dffcc18ae02f8a6c8c97bb58a8) - Sync your tailwind.config.js breakpoints with react
- [Twin VSCode snippits](https://gist.github.com/ben-rogerson/c6b62508e63b3e3146350f685df2ddc9) - For devs who want to type less
- [Twin VSCode extensions](https://github.com/ben-rogerson/twin.macro/discussions/227) - For faster class suggestions and feedback
---
[‹ Documentation](https://github.com/ben-rogerson/twin.macro/blob/master/docs/index.md)
================================================
FILE: jest.config.ts
================================================
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.ts?$': 'ts-jest',
},
transformIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/types'],
setupFilesAfterEnv: ['<rootDir>/tests/util/customMatchers.ts'],
}
================================================
FILE: package.json
================================================
{
"name": "twin.macro",
"version": "3.4.1",
"description": "Twin blends the magic of Tailwind with the flexibility of css-in-js",
"main": "macro.js",
"types": "types/index.d.ts",
"scripts": {
"dev": "concurrently npm:dev:* -p none",
"dev:macro": "NODE_ENV=dev nodemon -q --watch 'src/**/*.ts' --watch package.json -x \"npm run build:macro\" --delay .01",
"dev:sandbox": "NODE_ENV=dev nodemon -q --watch sandbox/in.tsx --watch package.json --watch macro.js -x \"npm run build:sandbox\" --delay .01",
"build": "npm run build:macro",
"build:macro": "microbundle -i src/macro.ts -f cjs -o ./macro.js --target node",
"build:sandbox": "babel sandbox/in.tsx --out-file sandbox/out.tsx",
"test": "npm run build && jest && npm run test:types",
"test:types": "tsc -b ./types/tsconfig.json",
"test:update": "npm run build && jest --u",
"prepublishOnly": "npm run build"
},
"nodemonConfig": {
"ignore": [],
"watch": [
"src"
],
"ext": "ts",
"delay": "0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"engines": {
"node": ">=16.14.0"
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint src --cache --fix",
"jest plugin.test.js"
],
"*.{js,ts,jsx,tsx,json,md}": [
"prettier --write"
]
},
"files": [
"macro.js",
"types/index.d.ts"
],
"repository": {
"type": "git",
"url": "git+https://github.com/ben-rogerson/twin.macro.git"
},
"keywords": [
"emotion",
"styled-components",
"stitches",
"goober",
"tailwind",
"tailwindcss",
"css-in-js",
"babel-plugin",
"babel-plugin-macros"
],
"author": "Ben Rogerson <info@benrogerson.dev>",
"license": "MIT",
"bugs": {
"url": "https://github.com/ben-rogerson/twin.macro/issues"
},
"homepage": "https://github.com/ben-rogerson/twin.macro#readme",
"peerDependencies": {
"tailwindcss": ">=3.3.1"
},
"dependencies": {
"@babel/template": "^7.22.15",
"babel-plugin-macros": "^3.1.0",
"chalk": "4.1.2",
"lodash.get": "^4.4.2",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "^6.0.13"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/plugin-syntax-jsx": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.7",
"@types/babel-plugin-macros": "^2.8.5",
"@types/didyoumean": "^1.2.0",
"@types/dlv": "^1.1.2",
"@types/jest": "^29.2.2",
"@types/lodash.flatmap": "^4.5.7",
"@types/lodash.get": "^4.4.7",
"@types/lodash.merge": "^4.6.7",
"@types/react": "^18.0.25",
"@types/string-similarity": "^4.0.0",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"babel-plugin-tester": "^10.1.0",
"concurrently": "^7.5.0",
"daisyui": "^2.38.0",
"escalade": "^3.1.1",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-xo": "^0.42.0",
"eslint-config-xo-react": "^0.27.0",
"eslint-config-xo-space": "^0.33.0",
"eslint-config-xo-typescript": "^0.53.0",
"eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^27.1.4",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-unicorn": "^44.0.2",
"glob-all": "^3.3.0",
"husky": "4.3.8",
"jest": "^29.2.2",
"lint-staged": "^13.0.3",
"microbundle": "^0.15.1",
"nodemon": "^2.0.20",
"prettier": "^2.8.7",
"react": "^18.2.0",
"string-similarity": "^4.0.4",
"styled-components": "^5.3.6",
"tailwindcss-typography": "3.1.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
}
}
================================================
FILE: sandbox/in.tsx
================================================
/**
* Twin Sandbox
* A place to test the output twin creates.
* Good for general testing or for developing new features.
*
* Getting started
* 1. Run the script: `npm run dev`
* 2. Make a change to this file or to a file in the `src` folder
* 3. Check `sandbox/out.tsx` for the macro output
*/
// @ts-nocheck
import tw, { globalStyles, css, styled, screen, theme } from '../macro'
// Tw block
tw`bg-black/25`
// Styled component
tw.div`bg-black/25`
// Inline tw
;<div tw="bg-black/25" />
================================================
FILE: src/core/constants.ts
================================================
const CLASS_SEPARATOR = /\S+/g
const DEFAULTS_UNIVERSAL = '*, ::before, ::after'
const EMPTY_CSS_VARIABLE_VALUE = 'var(--tw-empty,/*!*/ /*!*/)'
const PRESERVED_ATRULE_TYPES = new Set([
'charset',
'counter-style',
'document',
'font-face',
'font-feature-values',
'import',
'keyframes',
'namespace',
])
const LAYER_DEFAULTS = 'defaults'
const LINEFEED = /\n/g
const WORD_CHARACTER = /\w/
const SPACE_ID = '_'
const SPACE = /\s/
const SPACES = /\s+/g
export {
CLASS_SEPARATOR,
DEFAULTS_UNIVERSAL,
EMPTY_CSS_VARIABLE_VALUE,
PRESERVED_ATRULE_TYPES,
LAYER_DEFAULTS,
LINEFEED,
SPACE_ID,
SPACE,
SPACES,
WORD_CHARACTER,
}
================================================
FILE: src/core/createCoreContext.ts
================================================
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { getTailwindConfig, getConfigTwinValidated } from './lib/configHelpers'
import getStitchesPath from './lib/getStitchesPath'
import userPresets from './lib/userPresets'
import createTheme from './lib/createTheme'
import createAssert from './lib/createAssert'
import { createDebug } from './lib/logging'
import { createContext } from './lib/util/twImports'
import type {
PresetItem,
GetPackageUsed,
PossiblePresets,
CoreContext,
CreateCoreContext,
TwinConfig,
Assert,
} from './types'
function packageCheck(
packageToCheck: PossiblePresets,
params: GetPackageConfig,
hasNoFallback?: boolean
): boolean {
if (params.config && params.config.preset === packageToCheck) return true
if (hasNoFallback) return false
return (
params.styledImport.from.includes(packageToCheck) ||
params.cssImport.from.includes(packageToCheck)
)
}
type GetPackageConfig = {
config?: TwinConfig
cssImport: PresetItem
styledImport: PresetItem
}
function getPackageUsed(params: GetPackageConfig): GetPackageUsed {
return {
isEmotion: packageCheck('emotion', params),
isStyledComponents: packageCheck('styled-components', params, true),
isGoober: packageCheck('goober', params),
isSolid: packageCheck('solid', params),
isStitches: packageCheck('stitches', params),
}
}
type ConfigParameters = {
sourceRoot?: string
filename: string
config?: TwinConfig
assert: Assert
}
function getStyledConfig({
sourceRoot,
filename,
config,
}: ConfigParameters): PresetItem {
const usedConfig =
(config?.styled && config) ||
(config?.preset && userPresets[config.preset]) ||
userPresets.emotion
if (typeof usedConfig.styled === 'string') {
return { import: 'default', from: usedConfig.styled }
}
if (config && config.preset === 'stitches') {
const stitchesPath = getStitchesPath({ sourceRoot, filename, config })
if (stitchesPath && usedConfig.styled) {
// Overwrite the stitches import data with the path from the current file
usedConfig.styled.from = stitchesPath
}
}
return usedConfig.styled as PresetItem
}
function getCssConfig({
sourceRoot,
filename,
config,
}: ConfigParameters): PresetItem {
const usedConfig =
(config?.css && config) ||
(config?.preset && userPresets[config.preset]) ||
userPresets.emotion
if (typeof usedConfig.css === 'string') {
return { import: 'css', from: usedConfig.css }
}
if (config && config.preset === 'stitches') {
const stitchesPath = getStitchesPath({ sourceRoot, filename, config })
if (stitchesPath && usedConfig.css) {
// Overwrite the stitches import data with the path from the current file
usedConfig.css.from = stitchesPath
}
}
return usedConfig.css as PresetItem
}
function getGlobalConfig(config: TwinConfig): PresetItem {
const usedConfig =
(config.global && config) ||
(config.preset && userPresets[config.preset]) ||
userPresets.emotion
return usedConfig.global as PresetItem
}
function createCoreContext(params: CreateCoreContext): CoreContext {
const { sourceRoot, filename, config, isDev = false, CustomError } = params
const assert = createAssert(CustomError, false, config?.hasLogColors)
const configParameters = {
sourceRoot,
assert,
filename: filename ?? '',
config,
}
const styledImport = getStyledConfig(configParameters)
const cssImport = getCssConfig(configParameters)
const tailwindConfig =
params.tailwindConfig ?? getTailwindConfig(configParameters)
const packageUsed = getPackageUsed({ config, cssImport, styledImport })
const twinConfig = getConfigTwinValidated(config, { ...packageUsed, isDev })
const importConfig = {
styled: styledImport,
css: cssImport,
global: getGlobalConfig(config ?? {}),
}
return {
isDev,
assert,
debug: createDebug(isDev, twinConfig),
theme: createTheme(tailwindConfig),
tailwindContext: createContext(tailwindConfig),
packageUsed,
tailwindConfig,
twinConfig,
CustomError,
importConfig,
}
}
export default createCoreContext
================================================
FILE: src/core/extractRuleStyles.ts
================================================
import camelize from './lib/util/camelize'
import deepMerge from './lib/util/deepMerge'
import get from './lib/util/get'
import replaceThemeValue from './lib/util/replaceThemeValue'
import sassifySelector from './lib/util/sassifySelector'
import { splitAtTopLevelOnly, unescape } from './lib/util/twImports'
import {
DEFAULTS_UNIVERSAL,
EMPTY_CSS_VARIABLE_VALUE,
PRESERVED_ATRULE_TYPES,
LAYER_DEFAULTS,
LINEFEED,
} from './constants'
import getStyles from './getStyles'
import type { ExtractRuleStyles, CssObject, TransformDecl } from './types'
import type * as P from 'postcss'
const ESC_COMMA = /\\2c/g
const ESC_DIGIT = /\\3(\d)/g
const UNDERSCORE_ESCAPING = /\\+(_)/g
const SLASH_DOT_ESCAPING = /\\\./g
const BACKSLASH_ESCAPING = /\\\\/g
function transformImportant(value: string, params: TransformDecl): string {
if (params.passChecks === true) return value
if (!params.hasImportant) return value
// Avoid adding important if the rule doesn't respect it
if (params.hasImportant && params.options?.respectImportant === false)
return value
return `${value} !important`
}
function transformEscaping(value: string): string {
return (
value
.replace(UNDERSCORE_ESCAPING, '$1')
// Remove slash dot encoding in values
// eg: calc(\\.5 * .25rem)
.replace(SLASH_DOT_ESCAPING, '.')
// Fix the duplicate escaping babel delivers
.replace(BACKSLASH_ESCAPING, '\\')
)
}
const transformValueTasks = [
replaceThemeValue,
transformImportant,
transformEscaping,
]
function transformDeclValue(value: string, params: TransformDecl): string {
const valueOriginal = value
for (const task of transformValueTasks) {
value = task(value, params)
}
if (value !== valueOriginal)
params.debug('converted theme/important', {
old: valueOriginal,
new: value,
})
return value
}
function extractFromRule(
rule: P.Rule,
params: ExtractRuleStyles
): [string, CssObject] {
const selectorForUnescape = rule.selector.replace(ESC_DIGIT, '$1') // Remove digit escaping
const selector = unescape(selectorForUnescape).replace(LINEFEED, ' ')
return [selector, extractRuleStyles(rule.nodes, params)] as [
string,
CssObject
]
}
function extractSelectorFromAtRule(
name: string,
value: string,
params: ExtractRuleStyles
): string | undefined {
if (name === LAYER_DEFAULTS) {
if (params.includeUniversalStyles === false) return
return DEFAULTS_UNIVERSAL
}
const val = value.replace(ESC_COMMA, ',')
// Handle @screen usage in plugins, eg: `@screen md`
if (name === 'screen') {
const screenConfig = get(params, 'tailwindConfig.theme.screens') as Record<
string,
string
>
return `@media (min-width: ${screenConfig[val]})`
}
return `@${name} ${val}`.trim()
}
const ruleTypes = {
decl(decl: P.Declaration, params: ExtractRuleStyles): CssObject | undefined {
const property = decl.prop.startsWith('--')
? decl.prop
: camelize(decl.prop)
const value =
decl.prop.startsWith('--') && decl.value === ' '
? EMPTY_CSS_VARIABLE_VALUE // valid empty value in js, unlike ` `
: transformDeclValue(decl.value, { ...params, decl, property })
if (value === null) return
// `background-clip: text` is still in "unofficial" phase and needs a
// prefix in Firefox, Chrome and Safari.
// https://caniuse.com/background-img-opts
if (
property === 'backgroundClip' &&
(value === 'text' || value === 'text !important')
)
return {
WebkitBackgroundClip: value,
[property]: value,
}
return { [property]: value }
},
// General styles, eg: `{ display: block }`
rule(rule: P.Rule, params: ExtractRuleStyles): CssObject | undefined {
if (!rule.selector) {
if (rule.nodes) {
const styles = extractRuleStyles(rule.nodes, params)
params.debug('rule has no selector, returning nodes', styles)
return styles
}
params.debug('no selector found in rule', rule, 'error')
return
}
let [selector, styles] = extractFromRule(rule, params)
if (selector && styles === null) return
if (params.passChecks) {
const out = selector ? { [selector]: styles } : styles
params.debug('style pass return', out)
return out
}
params.debug('styles extracted', { selector, styles })
// As classes aren't used in css-in-js we split the selector into
// multiple selectors and strip the ones that don't affect the current
// element, eg: In `.this, .sub`, .sub is stripped as it has no target
const selectorList = [...splitAtTopLevelOnly(selector, ',')].filter(s => {
// Match the selector as a class
const result = params.selectorMatchReg?.test(s)
// Only keep selectors if they contain a `&` || aren’t
// targeting multiple elements with classes
if (!result && (s.includes('&') || !s.includes('.'))) return true
return result
})
if (selectorList.length === 0) {
params.debug('no selector match', selector, 'warn')
return
}
if (selectorList.length === 1)
params.debug('matched whole selector', selectorList[0])
if (selectorList.length > 1)
params.debug('matched multiple selectors', selectorList)
selector = selectorList
.map(s =>
sassifySelector(
s,
params as ExtractRuleStyles & {
selectorMatchReg: RegExp
sassyPseudo: boolean
}
)
)
.filter(Boolean)
.join(',')
params.debug('sassified key', selector || styles)
if (!selector) return styles
return { [selector]: styles }
},
// At-rules, eg: `@media __` && `@screen md`
atrule(atrule: P.AtRule, params: ExtractRuleStyles): CssObject | undefined {
const selector = extractSelectorFromAtRule(
atrule.name,
atrule.params,
params
)
if (!selector) {
params.debug(
'no atrule selector found, removed',
{ name: atrule.name, params: atrule.params },
'warn'
)
return
}
// Add @apply support in plugins
if (selector.startsWith('@apply ')) {
const { styles, unmatched } = getStyles(
selector.slice(7),
params.coreContext
)
params.coreContext.assert(unmatched.length === 0, ({ color }) => {
const extraMessage =
selector === `@apply ${unmatched.join(' ')}`
? '.'
: ` as:\n\`${selector}\``
return `${color(
`✕ ${color(unmatched.join(' '), 'errorLight')} ${
unmatched.length > 1 ? 'classes' : 'class'
} can’t be used.\n\nThis is defined in a tailwind plugin${extraMessage}`
)}`
})
return styles
}
// Strip keyframes from animate-* classes
if (
selector.startsWith('@keyframes ') &&
!params.passChecks &&
params.twinConfig.moveKeyframesToGlobalStyles
)
return
if (PRESERVED_ATRULE_TYPES.has(atrule.name)) {
params.debug(`${atrule.name} pass given`, selector)
// Rules that pass checks have no further style transformations
params.passChecks = true
}
const styles = extractRuleStyles(atrule.nodes, params)
if (!styles) return
let ruleset = { [selector]: styles }
if (selector === DEFAULTS_UNIVERSAL) {
// Add a cloned backdrop style
ruleset = { ...ruleset, '::backdrop': styles }
params.debug('universal default', styles)
}
params.debug('atrule', selector)
return ruleset
},
}
type Styles = CssObject | undefined
function extractRuleStyles(nodes: P.Node[], params: ExtractRuleStyles): Styles {
const styles: Styles[] = nodes
.map((rule): CssObject | undefined => {
const handler = ruleTypes[rule.type as keyof typeof ruleTypes]
if (!handler) return
return handler(rule as never, params)
})
.filter(Boolean)
if (styles.length === 0) return undefined
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return deepMerge(styles[0], ...styles.slice(1))
}
export default extractRuleStyles
================================================
FILE: src/core/getGlobalStyles.ts
================================================
import deepMerge from './lib/util/deepMerge'
import extractRuleStyles from './extractRuleStyles'
import { LAYER_DEFAULTS } from './constants'
import type { CoreContext, CssObject, Candidate } from './types'
function getGlobalStyles(params: CoreContext): CssObject | undefined {
const candidates = [...params.tailwindContext.candidateRuleMap]
const globalPluginStyles = candidates
.flatMap(([, candidate]: [unknown, Candidate[]]) => {
const out = candidate.map(([data, rule]) => {
if (data.layer !== LAYER_DEFAULTS) return
return extractRuleStyles([rule], {
...params,
coreContext: params,
passChecks: true,
})
})
if (out.length === 0) return
return out
})
.filter(Boolean)
const [globalKey, preflightRules]: [string, Candidate[]] = candidates[0]
// @ts-expect-error TOFIX: Fix tuple type error
if (globalKey.trim() !== '*') return deepMerge(...globalPluginStyles)
// @ts-expect-error TOFIX: Fix tuple type error
if (!Array.isArray(preflightRules)) return deepMerge(...globalPluginStyles)
const preflightStyles = preflightRules.flatMap(([, rule]) =>
extractRuleStyles([rule], {
...params,
coreContext: params,
passChecks: true,
})
)
return deepMerge(
// @ts-expect-error TOFIX: Fix tuple type error
...preflightStyles,
...globalPluginStyles,
...globalKeyframeStyles(params)
)
}
function globalKeyframeStyles(
params: CoreContext
): Array<Record<string, unknown>> {
if (params.twinConfig.moveKeyframesToGlobalStyles === false) return []
const keyframes = params.theme('keyframes')
if (!keyframes) return []
return Object.entries(keyframes).map(
([name, frames]: [string, Record<string, unknown>]) => ({
[`@keyframes ${name}`]: frames,
})
)
}
export default getGlobalStyles
================================================
FILE: src/core/getStyles.ts
================================================
import extractRuleStyles from './extractRuleStyles'
import createAssert from './lib/createAssert'
import expandVariantGroups from './lib/expandVariantGroups'
import deepMerge from './lib/util/deepMerge'
import { resolveMatches, splitAtTopLevelOnly } from './lib/util/twImports'
import escapeRegex from './lib/util/escapeRegex'
import convertClassName from './lib/convertClassName'
import { WORD_CHARACTER } from './constants'
import type {
CoreContext,
CssObject,
ExtractRuleStyles,
AssertContext,
TailwindMatch,
TailwindContext,
TailwindConfig,
Assert,
} from './types'
const IMPORTANT_OUTSIDE_BRACKETS =
/(:!|^!)(?=(?:(?:(?!\)).)*\()|[^()]*$)(?=(?:(?:(?!]).)*\[)|[^[\]]*$)/
const COMMENTS_MULTI_LINE = /(?<!\/)\/(?!\/)\*[\S\s]*?\*\//g
const COMMENTS_SINGLE_LINE = /(?<!:)\/\/.*/g
const CLASS_DIVIDER_PIPE = / \| /g
const ALL_BRACKET_SQUARE_LEFT = /\[/g
const ALL_BRACKET_SQUARE_RIGHT = /]/g
const ALL_BRACKET_ROUND_LEFT = /\(/g
const ALL_BRACKET_ROUND_RIGHT = /\)/g
const ESCAPE_CHARACTERS = /\n|\t/g
function getStylesFromMatches(
matches: TailwindMatch[],
params: ExtractRuleStyles
): CssObject | undefined {
if (matches.length === 0) {
params.debug('no matches supplied', {}, 'error')
return
}
const rulesets = matches
.map(([data, rule]) =>
extractRuleStyles([rule], { ...params, options: data.options })
)
.filter(Boolean)
if (rulesets.length === 0) {
params.debug('no node rulesets found', {}, 'error')
return
}
// @ts-expect-error Avoid tuple type error
return deepMerge(...rulesets)
}
// When removing a multiline comment, determine if a space is left or not
// eg: You'd want a space left in this situation: tw`class1/* comment */class2`
function multilineReplaceWith(
match: string,
index: number,
input: string
): ' ' | '' {
const charBefore = input[index - 1]
const directPrefixMatch = charBefore && WORD_CHARACTER.exec(charBefore)
const charAfter = input[Number(index) + Number(match.length)]
const directSuffixMatch = charAfter && WORD_CHARACTER.exec(charAfter)
return directPrefixMatch?.[0] && directSuffixMatch && directSuffixMatch[0]
? ' '
: ''
}
function validateClasses(
classes: string,
{
assert,
tailwindConfig,
}: { tailwindConfig: TailwindConfig; assert: CoreContext['assert'] }
): boolean {
// TOFIX: Avoid counting brackets within arbitrary values
assert(
(classes.match(ALL_BRACKET_SQUARE_LEFT) ?? []).length ===
(classes.match(ALL_BRACKET_SQUARE_RIGHT) ?? []).length,
({ color }: AssertContext) =>
`${color(
`✕ Unbalanced square brackets found in classes:\n\n${color(
classes,
'errorLight'
)}`
)}`
)
// TOFIX: Avoid counting brackets within arbitrary values
assert(
(classes.match(ALL_BRACKET_ROUND_LEFT) ?? []).length ===
(classes.match(ALL_BRACKET_ROUND_RIGHT) ?? []).length,
({ color }: AssertContext) =>
`${color(
`✕ Unbalanced round brackets found in classes:\n\n${color(
classes,
'errorLight'
)}`
)}`
)
for (const className of splitAtTopLevelOnly(classes, ' ')) {
// Check for missing class attached to a variant
const classCheck = className.replace(ESCAPE_CHARACTERS, ' ').trim()
assert(
!classCheck.endsWith(tailwindConfig.separator ?? ':'),
({ color }: AssertContext) =>
`${color(
`✕ The variant ${String(
color(classCheck, 'errorLight')
)} doesn’t look right`
)}\n\nUpdate to ${String(
color(`${classCheck}block`, 'success')
)} or ${String(color(`${classCheck}(block mt-4)`, 'success'))}`
)
}
return true
}
const tasks: Array<
(classes: string, tailwindConfig: TailwindConfig, assert: Assert) => string
> = [
(classes): string => classes.replace(CLASS_DIVIDER_PIPE, ' '),
(classes): string =>
classes.replace(COMMENTS_MULTI_LINE, multilineReplaceWith),
(classes): string => classes.replace(COMMENTS_SINGLE_LINE, ''),
(classes, tailwindConfig, assert): string =>
expandVariantGroups(classes, { assert, tailwindConfig }), // Expand grouped variants to individual classes
]
function sortBigSign(bigIntValue: bigint): number {
return Number(bigIntValue > 0n) - Number(bigIntValue < 0n)
}
function getOrderedClassList(
tailwindContext: TailwindContext,
convertedClassList: string[],
classList: string[],
assert: CoreContext['assert']
): Array<[order: bigint, className: string, preservedClassName: string]> {
assert(typeof tailwindContext?.getClassOrder === 'function', ({ color }) =>
color('Twin requires a newer version of tailwindcss, please update')
) // `getClassOrder` was added in tailwindcss@3.0.23
let orderedClassList
try {
orderedClassList = tailwindContext
.getClassOrder(convertedClassList)
.map(([className, order], index): [bigint, string, string] => [
order || 0n,
className,
classList[index],
])
.sort(([a], [z]) => sortBigSign(a - z))
} catch (error: unknown) {
assert(
false,
({ color }) =>
`${color(
String(error).replace('with \\ may', 'with a single \\ may') // Improve error
)}\n\n${color('Found in:')} ${color(
convertedClassList.join(' '),
'errorLight'
)}`
)
}
return orderedClassList as Array<[bigint, string, string]>
}
function getStyles(
classes: string,
params: CoreContext
): { styles: CssObject | undefined; unmatched: string[]; matched: string[] } {
const assert = createAssert(
params.CustomError,
params.isSilent,
params.twinConfig.hasLogColors
)
params.debug('string in', classes)
assert(
![null, 'null', undefined].includes(classes),
({ color }: AssertContext) =>
`${color(
`✕ Your classes need to be complete strings for Twin to detect them correctly`
)}\n\nRead more at https://twinredirect.page.link/template-literals`
)
const result = validateClasses(classes, {
tailwindConfig: params.tailwindConfig,
assert,
})
if (!result) return { styles: undefined, matched: [], unmatched: [] }
for (const task of tasks) {
classes = task(classes, params.tailwindConfig, assert)
}
params.debug('classes after format', classes)
const matched: string[] = []
const unmatched: string[] = []
const styles: CssObject[] = []
const commonContext = {
assert,
theme: params.theme,
debug: params.debug,
}
const convertedClassNameContext = {
...commonContext,
tailwindConfig: params.tailwindConfig,
isShortCssOnly: params.isShortCssOnly,
disableShortCss: params.twinConfig.disableShortCss,
}
const classList = [...splitAtTopLevelOnly(classes, ' ')]
const convertedClassList = classList.map(c =>
convertClassName(c, convertedClassNameContext)
)
const orderedClassList = getOrderedClassList(
params.tailwindContext,
convertedClassList,
classList,
assert
)
const commonMatchContext = {
...commonContext,
includeUniversalStyles: false,
coreContext: params,
twinConfig: params.twinConfig,
tailwindConfig: params.tailwindConfig,
tailwindContext: params.tailwindContext,
sassyPseudo: params.twinConfig.sassyPseudo,
}
for (const [, convertedClassName, className] of orderedClassList) {
const matches = [
...resolveMatches(convertedClassName, params.tailwindContext),
]
const results = getStylesFromMatches(matches, {
...commonMatchContext,
hasImportant: IMPORTANT_OUTSIDE_BRACKETS.test(
escapeRegex(convertedClassName)
),
selectorMatchReg: new RegExp(
// This regex specifies a list of characters allowed for the character
// immediately after the class ends - this avoids matching other classes
// eg: Input 'btn' will avoid matching '.btn-primary' in `.btn + .btn-primary`
`(${escapeRegex(`.${convertedClassName}`)})(?=[\\[.# >~+*:$\\)]|$)`
),
original: convertedClassName,
})
if (!results) {
params.debug('🔥 No matching rules found', className, 'error')
// Allow tw``/tw="" to pass through
if (className !== '') unmatched.push(className)
// If non-match and is on silent mode: Continue next iteration
if (params.isSilent) continue
// If non-match: Stop iteration and return
// (This "for of" loop returns to the parent function)
return { styles: undefined, matched, unmatched }
}
matched.push(className)
params.debug('✨ ruleset out', results, 'success')
styles.push(results)
}
if (styles.length === 0) return { styles: undefined, matched, unmatched }
// @ts-expect-error Avoid tuple type error
const mergedStyles = deepMerge(...styles)
return { styles: mergedStyles, matched, unmatched }
}
export default getStyles
================================================
FILE: src/core/index.ts
================================================
export { default as createCoreContext } from './createCoreContext'
export { default as getGlobalStyles } from './getGlobalStyles'
export { default as getStyles } from './getStyles'
export { splitAtTopLevelOnly } from './lib/util/twImports'
================================================
FILE: src/core/lib/configHelpers.ts
================================================
import { resolve, dirname } from 'path'
import { existsSync } from 'fs'
import escalade from 'escalade/sync'
import { configTwinValidators, configDefaultsTwin } from './twinConfig'
import defaultTwinConfig from './defaultTailwindConfig'
import { resolveTailwindConfig, getAllConfigs } from './util/twImports'
import isObject from './util/isObject'
import { logGeneralError } from './logging'
import type {
TwinConfig,
TwinConfigAll,
GetConfigTwinValidatedParameters,
TailwindConfig,
Assert,
AssertContext,
} from 'core/types'
import loadConfig from 'tailwindcss/loadConfig'
type Validator = [(value: unknown) => boolean, string]
type GetTailwindConfig = {
sourceRoot?: string
filename: string
config?: TwinConfig
assert: Assert
}
function getTailwindConfig({
sourceRoot,
filename,
config,
assert,
}: GetTailwindConfig): TailwindConfig {
sourceRoot = sourceRoot ?? '.'
const baseDirectory = filename ? dirname(filename) : process.cwd()
const userTailwindConfig = config?.config
if (isObject(userTailwindConfig))
return resolveTailwindConfig([
// User config
...getAllConfigs(userTailwindConfig as Record<string, unknown[]>),
// Default config
...getAllConfigs(defaultTwinConfig),
])
const configPath = userTailwindConfig
? resolve(sourceRoot, userTailwindConfig)
: escalade(baseDirectory, (_, names) => {
if (names.includes('tailwind.config.js')) return 'tailwind.config.js'
if (names.includes('tailwind.config.cjs')) return 'tailwind.config.cjs'
if (names.includes('tailwind.config.ts')) return 'tailwind.config.ts'
}) ?? ''
const configExists = Boolean(configPath && existsSync(configPath))
if (userTailwindConfig)
assert(configExists, ({ color }: AssertContext) =>
[
`${String(
color(
`✕ The tailwind config ${color(
String(userTailwindConfig),
'errorLight'
)} wasn’t found`
)
)}`,
`Update the \`config\` option in your twin config`,
].join('\n\n')
)
const configs = [
// User config
...(configExists ? getAllConfigs(loadConfig(configPath)) : []),
// Default config
...getAllConfigs(defaultTwinConfig),
]
const tailwindConfig = resolveTailwindConfig(configs)
return tailwindConfig
}
function runConfigValidator([item, value]: [
keyof typeof configTwinValidators,
string | boolean
]): boolean {
const validatorConfig: Validator = configTwinValidators[item]
if (!validatorConfig) return true
const [validator, errorMessage] = validatorConfig
if (typeof validator !== 'function') return false
if (!validator(value)) {
throw new Error(logGeneralError(String(errorMessage)))
}
return true
}
function getConfigTwin(
config: TwinConfig | undefined,
params: GetConfigTwinValidatedParameters
): TwinConfigAll {
const output: TwinConfigAll = {
...configDefaultsTwin(params),
...config,
}
return output
}
function getConfigTwinValidated(
config: TwinConfig | undefined,
params: GetConfigTwinValidatedParameters
): TwinConfigAll {
const twinConfig = getConfigTwin(config, params)
// eslint-disable-next-line unicorn/no-array-reduce
return Object.entries(twinConfig).reduce((result, item) => {
const validatedItem = item as [
keyof typeof configTwinValidators,
string | boolean
]
return {
...result,
...(runConfigValidator(validatedItem) && {
[validatedItem[0]]: validatedItem[1],
}),
}
}, {}) as TwinConfigAll
}
export { getTailwindConfig, getConfigTwinValidated }
================================================
FILE: src/core/lib/convertClassName.ts
================================================
import replaceThemeValue from './util/replaceThemeValue'
import isShortCss from './util/isShortCss'
import splitOnFirst from './util/splitOnFirst'
import { splitAtTopLevelOnly } from './util/twImports'
import type { AssertContext, CoreContext, TailwindConfig } from 'core/types'
// eslint-disable-next-line import/no-relative-parent-imports
import { SPACE_ID, SPACES } from '../constants'
const ALL_COMMAS = /,/g
const ALL_AMPERSANDS = /&/g
const ENDING_AMP_THEN_WHITESPACE = /&[\s_]*$/
const ALL_CLASS_DOTS = /(?<!\\)(\.)(?=\w)/g
const ALL_CLASS_ATS = /(?<!\\)(@)(?=\w)(?!media)/g
const ALL_WRAPPABLE_PARENT_SELECTORS = /&(?=([^ $)*+,.:>[_~])[\w-])/g
const BASIC_SELECTOR_TYPES = /^#|^\\.|[^\W_]/
type ConvertShortCssToArbitraryPropertyParameters = {
disableShortCss: CoreContext['twinConfig']['disableShortCss']
origClassName: string
} & Pick<CoreContext, 'tailwindConfig' | 'assert' | 'isShortCssOnly'>
function convertShortCssToArbitraryProperty(
className: string,
{
tailwindConfig,
assert,
disableShortCss,
isShortCssOnly,
origClassName,
}: ConvertShortCssToArbitraryPropertyParameters
): string {
const splitArray = [
...splitAtTopLevelOnly(className, tailwindConfig.separator ?? ':'),
]
const lastValue = splitArray.slice(-1)[0]
let [property, value] = splitOnFirst(lastValue, '[')
value = value.slice(0, -1).trim()
let preSelector = ''
if (property.startsWith('!')) {
property = property.slice(1)
preSelector = '!'
}
const template = `${preSelector}[${[
property,
value === '' ? "''" : value,
].join(tailwindConfig.separator ?? ':')}]`
splitArray.splice(-1, 1, template)
const arbitraryProperty = splitArray.join(tailwindConfig.separator ?? ':')
const isShortCssDisabled = disableShortCss && !isShortCssOnly
assert(!isShortCssDisabled, ({ color }) =>
[
`${String(
color(
`✕ ${String(
color(origClassName, 'errorLight')
)} uses twin’s deprecated short-css syntax`
)
)}`,
`Update to ${String(color(arbitraryProperty, 'success'))}`,
`To ignore this notice, add this to your twin config:\n{ "disableShortCss": false }`,
`Read more at https://twinredirect.page.link/short-css`,
].join('\n\n')
)
return arbitraryProperty
}
type ConvertClassNameParameters = {
disableShortCss: CoreContext['twinConfig']['disableShortCss']
} & Pick<
CoreContext,
'tailwindConfig' | 'theme' | 'assert' | 'debug' | 'isShortCssOnly'
>
function checkForVariantSupport({
className,
tailwindConfig,
assert,
}: { className: string } & Pick<
CoreContext,
'tailwindConfig' | 'assert'
>): void {
const pieces = [
...splitAtTopLevelOnly(className, tailwindConfig.separator ?? ':'),
]
const hasMultipleVariants = pieces.length > 2 // One is the class name
const hasACommaInVariants = pieces.some(
p => splitAtTopLevelOnly(p.slice(1, -1), ',').length > 1
)
const hasIssue = hasMultipleVariants && hasACommaInVariants
assert(
!hasIssue,
({ color }: AssertContext) =>
`${color(
`✕ The variants on ${String(
color(className, 'errorLight')
)} are invalid tailwind and twin classes`
)}\n\n${color(
`To fix, either reduce all variants into a single arbitrary variant:`,
'success'
)}\nFrom: \`[.this, .that]:first:block\`\nTo: \`[.this:first, .that:first]:block\`\n\n${color(
`Or split the class into separate classes instead of using commas:`,
'success'
)}\nFrom: \`[.this, .that]:first:block\`\nTo: \`[.this]:first:block [.that]:first:block\`\n\nRead more at https://twinredirect.page.link/arbitrary-variants-with-commas`
)
}
// Convert a twin class to a tailwindcss friendly class
function convertClassName(
className: string,
{
tailwindConfig,
theme,
isShortCssOnly,
disableShortCss,
assert,
debug,
}: ConvertClassNameParameters
): string {
checkForVariantSupport({ className, tailwindConfig, assert })
const origClassName = className
// Convert spaces to class friendly underscores
className = className.replace(SPACES, SPACE_ID)
// Move the bang to the front of the class
if (className.endsWith('!')) {
debug('trailing bang found', className)
const splitArray = [
...splitAtTopLevelOnly(
className.slice(0, -1),
tailwindConfig.separator ?? ':'
),
]
// Place a ! before the class
splitArray.splice(-1, 1, `!${splitArray[splitArray.length - 1]}`)
className = splitArray.join(tailwindConfig.separator ?? ':')
}
// Convert short css to an arbitrary property, eg: `[display:block]`
// (Short css is deprecated)
if (isShortCss(className, tailwindConfig)) {
debug('short css found', className)
className = convertShortCssToArbitraryProperty(className, {
tailwindConfig,
assert,
disableShortCss,
isShortCssOnly,
origClassName,
})
}
// Replace theme values throughout the class
className = replaceThemeValue(className, { assert, theme })
// Add missing parent selectors and collapse arbitrary variants
className = sassifyArbitraryVariants(className, { tailwindConfig })
debug('class after format', className)
return className
}
function isArbitraryVariant(variant: string): boolean {
return variant.startsWith('[') && variant.endsWith(']')
}
function unbracket(variant: string): string {
return variant.slice(1, -1)
}
function sassifyArbitraryVariants(
fullClassName: string,
{ tailwindConfig }: { tailwindConfig: TailwindConfig }
): string {
const splitArray = [
...splitAtTopLevelOnly(fullClassName, tailwindConfig.separator ?? ':'),
]
const variants = splitArray.slice(0, -1)
const className = splitArray.slice(-1)[0]
if (variants.length === 0) return fullClassName
// Collapse arbitrary variants when they don't contain `&`.
// `[> div]:[.nav]:(flex block)` -> `[> div_.nav]:flex [> div_.nav]:block`
const collapsed = [] as string[]
variants.forEach((variant, index) => {
// We can’t match the selector if there's a character right next to the parent selector (eg: `[§ion]:block`) otherwise we'd accidentally replace `.step` in classes like this:
// Bad: `.steps-primary .steps` -> `&-primary &`
// Good: `.steps-primary .steps` -> `.steps-primary &`
// So here we replace it with crazy brackets to identify and unwrap it later
if (isArbitraryVariant(variant))
variant = variant.replace(ALL_WRAPPABLE_PARENT_SELECTORS, '(((&)))')
if (
index === 0 ||
!isArbitraryVariant(variant) ||
!isArbitraryVariant(variants[index - 1])
)
return collapsed.push(variant)
const prev = collapsed[collapsed.length - 1]
if (variant.includes('&')) {
const prevHasParent = prev.includes('&')
// Merge with current
if (prevHasParent) {
const mergedWithCurrent = variant.replace(
ALL_AMPERSANDS,
unbracket(prev)
)
const isLast = index === variants.length - 1
collapsed[index - 1] = isLast
? mergedWithCurrent.replace(ALL_AMPERSANDS, '')
: mergedWithCurrent
return
}
// Merge with previous
if (!prevHasParent) {
const mergedWithPrev = `[${unbracket(variant).replace(
ALL_AMPERSANDS,
unbracket(prev)
)}]`
collapsed[collapsed.length - 1] = mergedWithPrev
return
}
}
// Parentless variants are merged into the previous arbitrary variant
const mergedWithPrev = `[${[
unbracket(prev).replace(ENDING_AMP_THEN_WHITESPACE, ''),
unbracket(variant),
].join('_')}]`
collapsed[collapsed.length - 1] = mergedWithPrev
})
// The supplied class requires the reversal of it's variants as resolveMatches adds them in reverse order
const reversedVariantList = [...collapsed].slice().reverse()
const allVariants = reversedVariantList.map((v, idx) => {
if (!isArbitraryVariant(v)) return v
const unwrappedVariant = unbracket(v)
// Unescaped dots incorrectly add the prefix within arbitrary variants (only when`prefix` is set in tailwind config)
// eg: tw`[.a]:first:tw-block` -> `.tw-a &:first-child`
.replace(ALL_CLASS_DOTS, '\\.')
// Unescaped ats will throw a conversion error
.replace(ALL_CLASS_ATS, '\\@')
const variantList = unwrappedVariant.startsWith('@')
? [unwrappedVariant]
: // Arbitrary variants with commas are split, handled as separate selectors then joined
[...splitAtTopLevelOnly(unwrappedVariant, ',')]
const out = variantList
.map(variant =>
addParentSelector(variant, collapsed[idx - 1], collapsed[idx + 1] ?? '')
)
// Tailwindcss removes everything from a comma onwards in arbitrary variants, so we need to encode to preserve them
// Underscore is needed to distance the code from another possible number
// Eg: [path[fill='rgb(51,100,51)']]:[fill:white]
.join('\\2c_')
.replace(ALL_COMMAS, '\\2c_')
return `[${out}]`
})
return [...allVariants, className].join(tailwindConfig.separator ?? ':')
}
function addParentSelector(
selector: string,
prev: string,
next: string
): string {
// Preserve selectors with a parent selector and media queries
if (selector.includes('&') || selector.startsWith('@')) return selector
// Arbitrary variants
// Pseudo elements get an auto parent selector prefixed
if (selector.startsWith(':')) return `&${selector}`
// Variants that start with a class/id get treated as a child
if (BASIC_SELECTOR_TYPES.test(selector) && !prev) return `& ${selector}`
// When there's more than one variant and it's at the end then prefix it
if (!next && prev) return `&${selector}`
return `& ${selector}`
}
export default convertClassName
================================================
FILE: src/core/lib/createAssert.ts
================================================
import type { AssertContext } from 'core/types'
import { makeColor } from './logging'
function createAssert(
CustomError = Error,
isSilent = false,
hasLogColors = true
) {
return (
expression: boolean | string | (({ color }: AssertContext) => string),
message: string | (({ color }: AssertContext) => string)
): void => {
if (isSilent) return
if (typeof expression === 'string') {
throw new CustomError(`\n\n${expression}\n`)
}
const messageContext = { color: makeColor(hasLogColors) }
if (typeof expression === 'function') {
throw new CustomError(`\n\n${expression(messageContext)}\n`)
}
if (expression) return
if (typeof message === 'string') {
throw new CustomError(`\n\n${message}\n`)
}
if (typeof message === 'function') {
throw new CustomError(`\n\n${message(messageContext)}\n`)
}
}
}
export default createAssert
================================================
FILE: src/core/lib/createTheme.ts
================================================
import dlv from 'dlv'
import { transformThemeValue, toPath } from './util/twImports'
import isObject from './util/isObject'
import type { TailwindConfig } from 'core/types'
function createTheme(
tailwindConfig: TailwindConfig
): (
dotSeparatedItem: string,
extra?: string
) => Record<string, unknown> | boolean | number {
function getConfigValue(path: string[], defaultValue?: string): unknown {
return dlv(tailwindConfig, path, defaultValue)
}
function resolveThemeValue(
path: string,
defaultValue?: string,
options = {}
): number | boolean | Record<string, unknown> {
let [pathRoot, ...subPaths] = toPath(path)
// Retain dots in spacing values, eg: `ml-[theme(spacing.0.5)]`
if (
pathRoot === 'spacing' &&
subPaths.length === 2 &&
subPaths.every(x => !Number.isNaN(Number(x)))
) {
subPaths = [subPaths.join('.')]
}
const value = getConfigValue(
path ? ['theme', pathRoot, ...subPaths] : ['theme'],
defaultValue
)
return sassifyValues(transformThemeValue(pathRoot)(value, options))
}
const out = Object.assign(
(path: string, defaultValue?: string) =>
resolveThemeValue(path, defaultValue),
{
withAlpha: (path: string, opacityValue?: string) =>
resolveThemeValue(path, undefined, { opacityValue }),
}
)
return out
}
function sassifyValues(
values: Record<string, unknown>
): Record<string, unknown> {
if (!isObject(values)) return values
const transformed: Array<[string, unknown]> = Object.entries(values).map(
([k, v]: [string, unknown]) => [
k,
(isObject(v) && sassifyValues(v)) ||
(typeof v === 'number' && String(v)) ||
v,
]
)
return Object.fromEntries(transformed)
}
export default createTheme
================================================
FILE: src/core/lib/defaultTailwindConfig.ts
================================================
import toArray from './util/toArray'
import type { PluginAPI } from 'tailwindcss/types/config'
const AMPERSAND_AFTER = /&(.+)/g
const AMPERSAND = /&/g
function stripAmpersands(string: string): string {
return typeof string === 'string'
? string.replace(AMPERSAND, '').trim()
: string
}
const EXTRA_VARIANTS = [
['all', '& *'],
['all-child', '& > *'],
['sibling', '& ~ *'],
['hocus', ['&:hover', '&:focus']],
'link',
'read-write',
['svg', '& svg'],
['even-of-type', '&:nth-of-type(even)'],
['odd-of-type', '&:nth-of-type(odd)'],
]
const EXTRA_NOT_VARIANTS = [
// Positional
['first', '&:first-child'],
['last', '&:last-child'],
['only', '&:only-child'],
['odd', '&:nth-child(odd)'],
['even', '&:nth-child(even)'],
'first-of-type',
'last-of-type',
'only-of-type',
// State
'target',
['open', '&[open]'],
// Forms
'default',
'checked',
'indeterminate',
'placeholder-shown',
'autofill',
'optional',
'required',
'valid',
'invalid',
'in-range',
'out-of-range',
'read-only',
// Content
'empty',
// Interactive
'focus-within',
'hover',
'focus',
'focus-visible',
'active',
'enabled',
'disabled',
]
function defaultVariants({ config, addVariant }: PluginAPI): void {
const extraVariants = EXTRA_VARIANTS.flatMap(v => {
let [name, selector] = toArray(v)
selector = selector || `&:${String(name)}`
const variant = [name, selector]
// Create a :not() version of the selectors above
const notVariant = [
`not-${String(name)}`,
(toArray(selector) as string[]).map(
(s: string) => `&:not(${stripAmpersands(s)})`
),
]
return [variant, notVariant]
})
// Create :not() versions of these selectors
const notPseudoVariants = EXTRA_NOT_VARIANTS.map(v => {
const [name, selector] = toArray(v)
const notConfig = [
`not-${name as string}`,
(toArray(selector || `&:${name as string}`) as string[]).map(
s => `&:not(${stripAmpersands(s)})`
),
]
return notConfig
})
const variants = [...extraVariants, ...notPseudoVariants]
for (const [name, selector] of variants) {
addVariant(name as string, toArray(selector) as string[])
}
for (const [name, selector] of variants) {
const groupSelector = (toArray(selector) as string[]).map(s =>
s.replace(AMPERSAND_AFTER, ':merge(.group)$1 &')
)
addVariant(`group-${name as string}`, groupSelector)
}
for (const [name, selector] of variants) {
const peerSelector = (toArray(selector) as string[]).map(s =>
s.replace(AMPERSAND_AFTER, ':merge(.peer)$1 ~ &')
)
addVariant(`peer-${name as string}`, peerSelector)
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-pointer
addVariant('any-pointer-none', '@media (any-pointer: none)')
addVariant('any-pointer-fine', '@media (any-pointer: fine)')
addVariant('any-pointer-coarse', '@media (any-pointer: coarse)')
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer
addVariant('pointer-none', '@media (pointer: none)')
addVariant('pointer-fine', '@media (pointer: fine)')
addVariant('pointer-coarse', '@media (pointer: coarse)')
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover
addVariant('any-hover-none', '@media (any-hover: none)')
addVariant('any-hover', '@media (any-hover: hover)')
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover
addVariant('can-hover', '@media (hover: hover)')
addVariant('cant-hover', '@media (hover: none)')
addVariant('screen', '@media screen')
// Light mode
// eslint-disable-next-line unicorn/prefer-spread
let [mode, className = '.light'] = ([] as Array<string | boolean>).concat(
config('lightMode', 'media')
)
if (mode === false) mode = 'media'
if (mode === 'class') {
addVariant('light', `${String(className)} &`)
} else if (mode === 'media') {
addVariant('light', '@media (prefers-color-scheme: light)')
}
// eslint-disable-next-line unicorn/prefer-spread
;[mode, className = '.light'] = ([] as string[]).concat(
config('lightMode', 'media')
)
if (mode === 'class') {
addVariant('light', `${className} &`)
} else if (mode === 'media') {
addVariant('light', '@media (prefers-color-scheme: light)')
}
}
const defaultTailwindConfig = {
presets: [
{
content: [''], // Silence empty content warning
theme: {
extend: {
content: { DEFAULT: '' }, // Add a `content` class
zIndex: { 1: '1' }, // Add a handy small zIndex (`z-1` / `-z-1`)
},
},
plugins: [defaultVariants], // Add extra variants
},
],
}
export default defaultTailwindConfig
================================================
FILE: src/core/lib/expandVariantGroups.ts
================================================
import type { Assert, AssertContext, TailwindConfig } from 'core/types'
import { splitAtTopLevelOnly } from './util/twImports'
const BRACKETED = /^\(.*?\)$/
const BRACKETED_MAYBE_IMPORTANT = /\)!?$/
const ESCAPE_CHARACTERS = /\n|\t/g
type Context = {
variants?: string
beforeImportant?: string
afterImportant?: string
tailwindConfig: TailwindConfig
assert: Assert
}
function spreadVariantGroups(classes: string, context: Context): string[] {
const pieces = [
...splitAtTopLevelOnly(
classes.trim(),
context.tailwindConfig.separator ?? ':'
),
] as string[]
let groupedClasses = pieces.pop()
if (!groupedClasses) return [] // type guard
// Check for too many dividers used
// Added here instead of "validateClasses" as it's less error prone to check here
context.assert(
!pieces.includes(''),
({ color }: AssertContext) =>
`${color(
`✕ ${String(color(classes, 'errorLight'))} has too many dividers`
)}\n\nUpdate to ${String(
color(
`${pieces
.filter(Boolean)
.join(context.tailwindConfig.separator ?? ':')}`,
'success'
)
)}`
)
let beforeImportant = context?.beforeImportant ?? ''
let afterImportant = context?.afterImportant ?? ''
if (!beforeImportant && groupedClasses.startsWith('!')) {
groupedClasses = groupedClasses.slice(1)
beforeImportant = '!'
}
if (!afterImportant && groupedClasses.endsWith('!')) {
groupedClasses = groupedClasses.slice(0, -1)
afterImportant = '!'
}
// Remove () brackets and split
const unwrapped = BRACKETED.test(groupedClasses)
? groupedClasses.slice(1, -1)
: groupedClasses
const classList = [...splitAtTopLevelOnly(unwrapped, ' ')].filter(Boolean)
const group = classList
.map(className => {
if (
BRACKETED_MAYBE_IMPORTANT.test(className) &&
// Avoid infinite loop due to lack of separator, eg: `[em](block)`
!className.includes('](')
) {
const ctx = { ...context, beforeImportant, afterImportant }
return expandVariantGroups(
[...pieces, className].join(context.tailwindConfig.separator ?? ':'),
ctx
)
}
return [...pieces, [beforeImportant, className, afterImportant].join('')]
.filter(Boolean)
.join(context.tailwindConfig.separator ?? ':')
})
.filter(Boolean)
return group
}
function expandVariantGroups(classes: string, context: Context): string {
const classList = [
...splitAtTopLevelOnly(classes.replace(ESCAPE_CHARACTERS, ' ').trim(), ' '),
]
if (classList.length === 1 && ['', '()'].includes(classList[0])) return ''
const expandedClasses = classList.flatMap(item =>
spreadVariantGroups(item, context)
)
return expandedClasses.join(' ')
}
export default expandVariantGroups
================================================
FILE: src/core/lib/getStitchesPath.ts
================================================
import { resolve, relative, parse } from 'path'
import { existsSync } from 'fs'
import { logGeneralError } from './logging'
import toArray from './util/toArray'
import type { TwinConfig } from 'core/types'
function getFirstValue<ListItem>(
list: ListItem[],
getValue: (
params: ListItem,
options: { index: number; isLast: boolean }
) => unknown
): [unknown, ListItem | undefined] {
let firstValue
const listLength = list.length - 1
const listItem = list.find((listItem, index) => {
const isLast = index === listLength
firstValue = getValue(listItem, { index, isLast })
return Boolean(firstValue)
})
return [firstValue, listItem]
}
function checkExists(
fileName: string | string[],
sourceRoot: string
): string | undefined {
const [, value] = getFirstValue(
toArray(fileName) as string[],
existingFileName => existsSync(resolve(sourceRoot, `./${existingFileName}`))
)
return value
}
function getRelativePath(comparePath: string, filename: string): string {
const pathName = parse(filename).dir
return relative(pathName, comparePath)
}
function getStitchesPath({
sourceRoot,
filename,
config,
}: {
sourceRoot?: string
filename: string
config: TwinConfig
}): string {
sourceRoot = sourceRoot ?? '.'
const configPathCheck = config.stitchesConfig ?? [
'stitches.config.ts',
'stitches.config.js',
]
const configPath = checkExists(configPathCheck, sourceRoot)
if (!configPath)
throw new Error(
logGeneralError(
`Couldn’t find the Stitches config at ${
config.stitchesConfig
? `“${String(config.stitchesConfig)}”`
: 'the project root'
}.\nUse the twin config: stitchesConfig="PATH_FROM_PROJECT_ROOT" to set the location.`
)
)
return getRelativePath(configPath, filename)
}
export default getStitchesPath
================================================
FILE: src/core/lib/logging.ts
================================================
import chalk from 'chalk'
import type {
MakeColor,
ColorType,
ColorValue,
TwinConfigAll,
} from 'core/types'
const colors = {
error: chalk.hex('#ff8383'),
errorLight: chalk.hex('#ffd3d3'),
warn: chalk.yellowBright,
success: chalk.greenBright,
highlight: chalk.yellowBright,
subdued: chalk.hex('#999'),
}
function makeColor(hasColor: boolean): MakeColor {
return (message: string, type: keyof typeof colors = 'error') => {
if (!hasColor) return message
return colors[type](message)
}
}
function spaced(string: string): string {
return `\n\n${string}\n`
}
function warning(string: string): string {
return colors.error(`✕ ${string}`)
}
function logGeneralError(error: string | [ColorValue, string]): string {
return Array.isArray(error)
? spaced(
`${warning(
typeof error[0] === 'function' ? error[0](colors) : error[0]
)}\n\n${error[1]}`
)
: spaced(warning(error))
}
function createDebug(isDev: boolean, twinConfig: TwinConfigAll) {
return (
reference: string,
data: unknown,
type: ColorType = 'subdued'
): void => {
if (!isDev) return
if (!twinConfig.debug) return
const log = `${String(colors[type]('-'))} ${reference} ${String(
colors[type](JSON.stringify(data))
)}`
// eslint-disable-next-line no-console
console.log(log)
}
}
export { makeColor, spaced, warning, colors, logGeneralError, createDebug }
================================================
FILE: src/core/lib/twinConfig.ts
================================================
import type { GetPackageUsed, TwinConfigAll } from 'core/types'
const TWIN_CONFIG_DEFAULTS = {
allowStyleProp: false,
autoCssProp: false,
config: undefined,
convertHtmlElementToStyled: false,
convertStyledDotToParam: false,
convertStyledDotToFunction: false,
css: { import: '', from: '' },
dataCsProp: false,
dataTwProp: false,
debug: false,
disableCsProp: true,
disableShortCss: true,
global: { import: '', from: '' },
hasLogColors: true,
includeClassNames: false,
moveTwPropToStyled: false,
moveKeyframesToGlobalStyles: false,
preset: undefined,
sassyPseudo: false,
stitchesConfig: undefined,
styled: { import: '', from: '' },
} as const
// Defaults for different css-in-js libraries
const configDefaultsStyledComponents = {
sassyPseudo: true, // Sets selectors like hover to &:hover
} as const
const configDefaultsGoober = {
sassyPseudo: true, // Sets selectors like hover to &:hover
} as const
const configDefaultsSolid = {
sassyPseudo: true, // Sets selectors like hover to &:hover
moveTwPropToStyled: true, // Move the tw prop to a styled definition
convertHtmlElementToStyled: true, // Add a styled definition on css prop elements
convertStyledDotToFunction: true, // Convert styled.[element] to a default syntax
} as const
const configDefaultsStitches = {
sassyPseudo: true, // Sets selectors like hover to &:hover
convertStyledDotToParam: true, // Convert styled.[element] to a default syntax
moveTwPropToStyled: true, // Move the tw prop to a styled definition
convertHtmlElementToStyled: true, // Add a styled definition on css prop elements
stitchesConfig: undefined, // Set the path to the stitches config
moveKeyframesToGlobalStyles: true, // Stitches doesn't support inline @keyframes
} as const
function configDefaultsTwin({
isSolid,
isStyledComponents,
isGoober,
isStitches,
isDev,
}: GetPackageUsed & { isDev: boolean }): TwinConfigAll {
return {
...TWIN_CONFIG_DEFAULTS,
...(isSolid && configDefaultsSolid),
...(isStyledComponents && configDefaultsStyledComponents),
...(isGoober && configDefaultsGoober),
...(isStitches && configDefaultsStitches),
dataTwProp: isDev,
dataCsProp: isDev,
}
}
function isBoolean(value: unknown): boolean {
return typeof value === 'boolean'
}
const allowedPresets = [
'styled-components',
'emotion',
'goober',
'stitches',
'solid',
]
type ConfigTwinValidators = Record<
keyof typeof TWIN_CONFIG_DEFAULTS & 'disableColorVariables',
[(value: unknown) => boolean, string]
>
const configTwinValidators: ConfigTwinValidators = {
preset: [
(value: unknown): boolean =>
value === undefined ||
(typeof value === 'string' && allowedPresets.includes(value)),
`The config “preset” can only be:\n${allowedPresets
.map(p => `'${p}'`)
.join(', ')}`,
],
allowStyleProp: [
isBoolean,
'The config “allowStyleProp” can only be a boolean',
],
autoCssProp: [
(value: unknown): boolean => !value,
'The “autoCssProp” feature has been removed from twin.macro@2.8.2+\nThis means the css prop must be added by styled-components instead.\nSetup info at https://twinredirect.page.link/auto-css-prop\n\nRemove the “autoCssProp” item from your config to avoid this message.',
],
convertStyledDot: [
(value: unknown): boolean => !value,
'The “convertStyledDot” feature was changed to “convertStyledDotParam”.',
],
disableColorVariables: [
(value: unknown): boolean => !value,
'The disableColorVariables feature has been removed from twin.macro@3+\n\nRemove the disableColorVariables item from your config to avoid this message.',
],
sassyPseudo: [isBoolean, 'The config “sassyPseudo” can only be a boolean'],
dataTwProp: [
(value: unknown): boolean => isBoolean(value) || value === 'all',
'The config “dataTwProp” can only be true, false or "all"',
],
dataCsProp: [
(value: unknown): boolean => isBoolean(value) || value === 'all',
'The config “dataCsProp” can only be true, false or "all"',
],
includeClassNames: [
isBoolean,
'The config “includeClassNames” can only be a boolean',
],
disableCsProp: [
isBoolean,
'The config “disableCsProp” can only be a boolean',
],
convertStyledDotToParam: [
isBoolean,
'The config “convertStyledDotToParam” can only be a boolean',
],
convertStyledDotToFunction: [
isBoolean,
'The config “convertStyledDotToFunction” can only be a boolean',
],
moveTwPropToStyled: [
isBoolean,
'The config “moveTwPropToStyled” can only be a boolean',
],
convertHtmlElementToStyled: [
isBoolean,
'The config “convertHtmlElementToStyled” can only be a boolean',
],
}
export { configDefaultsTwin, configTwinValidators, TWIN_CONFIG_DEFAULTS }
================================================
FILE: src/core/lib/userPresets.ts
================================================
/**
* Config presets
*
* To change the preset, add the following in `package.json`:
* `{ "babelMacros": { "twin": { "preset": "styled-components" } } }`
*
* Or in `babel-plugin-macros.config.js`:
* `module.exports = { twin: { preset: "styled-components" } }`
*/
const userPresets = {
'styled-components': {
styled: { import: 'default', from: 'styled-components' },
css: { import: 'css', from: 'styled-components' },
global: { import: 'createGlobalStyle', from: 'styled-components' },
},
emotion: {
styled: { import: 'default', from: '@emotion/styled' },
css: { import: 'css', from: '@emotion/react' },
global: { import: 'Global', from: '@emotion/react' },
},
goober: {
styled: { import: 'styled', from: 'goober' },
css: { import: 'css', from: 'goober' },
global: { import: 'createGlobalStyles', from: 'goober/global' },
},
stitches: {
styled: { import: 'styled', from: 'stitches.config' },
css: { import: 'css', from: 'stitches.config' },
global: { import: 'global', from: 'stitches.config' },
},
solid: {
styled: { import: 'styled', from: 'solid-styled-components' },
css: { import: 'css', from: 'solid-styled-components' },
global: { import: 'createGlobalStyles', from: 'solid-styled-components' },
},
}
export default userPresets
================================================
FILE: src/core/lib/util/camelize.ts
================================================
const CAMEL_FIND = /\W+(.)/g
export default function camelize(string: string): string {
return string?.replace(CAMEL_FIND, (_, chr: string) => chr.toUpperCase())
}
================================================
FILE: src/core/lib/util/deepMerge.ts
================================================
import deepMerge from 'lodash.merge'
// eslint-disable-next-line unicorn/prefer-export-from
export default deepMerge
================================================
FILE: src/core/lib/util/escapeRegex.ts
================================================
const REGEX_SPECIAL_CHARACTERS = /[$()*+./?[\\\]^{|}-]/g
export default function escapeRegex(string: string): string {
return string.replace(REGEX_SPECIAL_CHARACTERS, '\\$&')
}
================================================
FILE: src/core/lib/util/formatProp.ts
================================================
// eslint-disable-next-line import/no-relative-parent-imports
import { SPACE_ID, LINEFEED } from '../../constants'
const EXTRA_WHITESPACE = /\s\s+/g
export default function formatProp(classes: string): string {
return (
classes
// Normalize spacing
.replace(EXTRA_WHITESPACE, ' ')
// Remove newline characters
.replace(LINEFEED, ' ')
// Replace the space id
.replace(SPACE_ID, ' ')
.trim()
)
}
================================================
FILE: src/core/lib/util/get.ts
================================================
import get from 'lodash.get'
// eslint-disable-next-line unicorn/prefer-export-from
export default get
================================================
FILE: src/core/lib/util/isEmpty.ts
================================================
export default function isEmpty(value: unknown): boolean {
return (
value === undefined ||
value === null ||
(typeof value === 'object' && Object.keys(value).length === 0) ||
(typeof value === 'string' && value.trim().length === 0)
)
}
================================================
FILE: src/core/lib/util/isObject.ts
================================================
export default function isObject(
value: unknown
): value is Record<string, unknown> {
// eslint-disable-next-line eqeqeq, no-eq-null
return value != null && typeof value === 'object' && !Array.isArray(value)
}
================================================
FILE: src/core/lib/util/isShortCss.ts
================================================
import { splitAtTopLevelOnly } from './twImports'
import type { TailwindConfig } from 'core/types'
export default function isShortCss(
fullClassName: string,
tailwindConfig: TailwindConfig
): boolean {
const classPieces = [
...splitAtTopLevelOnly(fullClassName, tailwindConfig.separator ?? ':'),
]
const className = classPieces.slice(-1)[0]
if (!className.includes('[')) return false
// Replace brackets before splitting on them as the split function already
// reads brackets to determine where the top level is
const splitAtArbitrary = [
...splitAtTopLevelOnly(className.replace(/\[/g, '∀'), '∀'),
]
// Normal class
if (splitAtArbitrary[0].endsWith('-')) return false
// Important suffix
if (splitAtArbitrary[0].endsWith('!')) return false
// Arbitrary property
if (splitAtArbitrary[0] === '') return false
// Slash opacity, eg: bg-red-500/fromConfig/[.555]
if (splitAtArbitrary[0].endsWith('/')) return false
return true
}
================================================
FILE: src/core/lib/util/replaceThemeValue.ts
================================================
import type { AssertContext, CoreContext } from 'core/types'
const MATCH_THEME = /theme\((.+?)\)/
const MATCH_QUOTES = /["'`]/g
function replaceThemeValue(
value: string,
{
assert,
theme,
}: { assert: CoreContext['assert']; theme: CoreContext['theme'] }
): string {
const match = MATCH_THEME.exec(value)
if (!match) return value
const themeFunction = match[0]
const themeParameters = match[1].replace(MATCH_QUOTES, '').trim()
const [main, second] = themeParameters.split(',')
let themeValue = theme(main, second)
assert(Boolean(themeValue), ({ color }: AssertContext) =>
color(
`✕ ${color(
themeParameters,
'errorLight'
)} doesn’t match a theme value from the config`
)
)
// Account for the 'DEFAULT' key
if (typeof themeValue === 'object' && 'DEFAULT' in themeValue) {
themeValue = themeValue.DEFAULT as typeof themeValue
}
// Escape spaces in the value - without this we get an incorrect order
// in class groups like this:
// tw`w-[calc(100%-theme('spacing.1'))] w-[calc(100%-theme('spacing[0.5]'))]`
// theme: { spacing: { 0.5: "calc(.5 * .25rem)", 1: "calc(1 * .25rem)" } }
const stringValue = String(themeValue).replace(/\./g, '\\.')
const replacedValue = value.replace(themeFunction, stringValue)
return replacedValue
}
export default replaceThemeValue
================================================
FILE: src/core/lib/util/sassifySelector.ts
================================================
import type { ExtractRuleStyles } from 'core/types'
const SELECTOR_PARENT_CANDIDATE = /^[ #.[]/
const SELECTOR_SPECIAL_STARTS = /^ [>@]/
const SELECTOR_ROOT = /(^| ):root(?!\w)/g
const UNDERSCORE_ESCAPING = /\\+(_)/g
const WRAPPED_PARENT_SELECTORS = /(\({3}&(.*?)\){3})/g
type OptionalSassifyContext = {
selectorMatchReg: RegExp
sassyPseudo: boolean
original?: string
}
type SassifySelectorTasks = Array<
(selector: string, params: OptionalSassifyContext) => string
>
const sassifySelectorTasks: SassifySelectorTasks = [
(selector): string => selector.trim(),
// Prefix with the parent selector when sassyPseudo is enabled,
// otherwise just replace the class with the parent selector
(selector, { selectorMatchReg, sassyPseudo, original }): string => {
const out = selector.replace(
selectorMatchReg,
(match, __, offset: number) => {
if (selector === match) return ''
if (
/\w/.test(selector[offset - 1]) &&
selector[offset + match.length] === ':'
) {
if (sassyPseudo && selector[offset - 1] === undefined) return '&'
return '' // Cover [section&]:hover:block / .btn.loading&:before
}
return offset === 0 ? '' : '&'
}
)
// Fix certain matches not covered by the previous task, eg: `first:[section]:m-1`
// (Arbitrary variants targeting html elements)
if (original && out === selector && selector.includes(`.${original}`))
return selector.replace(`.${original}`, '')
return out
},
// Unwrap the pre-wrapped parent selectors (pre-wrapping avoids matching issues against word characters, eg: `[§ion]:block`)
(selector): string => selector.replace(WRAPPED_PARENT_SELECTORS, '&$2'),
// Remove unneeded escaping from the selector
(selector): string => selector.replace(UNDERSCORE_ESCAPING, '$1'),
// Prefix classes/ids/attribute selectors with a parent selector so styles
// are applied to the current element rather than its children
(selector): string => {
if (selector.includes('&')) return selector
const addParentSelector = SELECTOR_PARENT_CANDIDATE.test(selector)
if (!addParentSelector) return selector
// Fix: ` > :not([hidden]) ~ :not([hidden])` / ` > *`
// Fix: `[@page]:x`
if (SELECTOR_SPECIAL_STARTS.test(selector)) return selector
return `&${selector}`
},
// Fix the spotty `:root` support in emotion/styled-components
(selector): string => selector.replace(SELECTOR_ROOT, '*:root'),
// Escape selectors containing forward slashes, eg: group-hover/link:bg-black
(selector): string => selector.replace(/\//g, '\\/'),
(selector): string => selector.trim(),
]
function sassifySelector(
selector: string,
params: ExtractRuleStyles & OptionalSassifyContext
): string {
// Remove the selector if it only contains the parent selector
if (selector === '&') {
params.debug('selector not required', selector)
return ''
}
for (const task of sassifySelectorTasks) {
selector = task(selector, params)
}
return selector
}
export default sassifySelector
================================================
FILE: src/core/lib/util/splitOnFirst.ts
================================================
// Split a string at a value and return an array of the two parts
export default function splitOnFirst(input: string, delim: string): string[] {
return (([first, ...rest]): [string, string] => [first, rest.join(delim)])(
input.split(delim)
)
}
================================================
FILE: src/core/lib/util/toArray.ts
================================================
export default function toArray<T>(array: T): T | [T] {
if (Array.isArray(array)) return array
return [array]
}
================================================
FILE: src/core/lib/util/twImports.ts
================================================
import type { Config } from 'tailwindcss'
import type { TailwindConfig, TailwindContext, TailwindMatch } from 'core/types'
// @ts-expect-error Types added below
import { toPath as toPathRaw } from 'tailwindcss/lib/util/toPath'
// @ts-expect-error Types added below
import { resolveMatches as resolveMatchesRaw } from 'tailwindcss/lib/lib/generateRules'
// @ts-expect-error Types added below
import { createContext as createContextRaw } from 'tailwindcss/lib/lib/setupContextUtils'
// @ts-expect-error Types added below
import { default as defaultTailwindConfigRaw } from 'tailwindcss/stubs/config.full'
// @ts-expect-error Types added below
import { default as transformThemeValueRaw } from 'tailwindcss/lib/util/transformThemeValue'
// @ts-expect-error Types added below
import { default as resolveTailwindConfigRaw } from 'tailwindcss/lib/util/resolveConfig'
// @ts-expect-error Types added below
import { default as getAllConfigsRaw } from 'tailwindcss/lib/util/getAllConfigs'
// @ts-expect-error Types added below
import { splitAtTopLevelOnly as splitAtTopLevelOnlyRaw } from 'tailwindcss/lib/util/splitAtTopLevelOnly'
// @ts-expect-error Types added below
import unescapeRaw from 'postcss-selector-parser/dist/util/unesc'
const toPath = toPathRaw as (path: string[] | string) => string[]
const createContext = createContextRaw as (config: Config) => TailwindContext
const defaultTailwindConfig = defaultTailwindConfigRaw as Config
const resolveMatches = resolveMatchesRaw as (
candidate: string,
context: TailwindContext
) => TailwindMatch[]
const transformThemeValue = transformThemeValueRaw as (
themeValue: string
) => (
value: unknown,
options: Record<string, unknown>
) => Record<string, unknown>
const resolveTailwindConfig = resolveTailwindConfigRaw as (
config: unknown[]
) => TailwindConfig
const getAllConfigs = getAllConfigsRaw as (
config: Record<string, unknown[]>
) => TailwindConfig[]
const splitAtTopLevelOnly = splitAtTopLevelOnlyRaw as (
input: string,
separator: string
) => string[]
const unescape = unescapeRaw as (string: string) => string
export {
toPath,
createContext,
defaultTailwindConfig,
resolveMatches,
transformThemeValue,
resolveTailwindConfig,
getAllConfigs,
splitAtTopLevelOnly,
unescape,
}
================================================
FILE: src/core/types/index.ts
================================================
import type { MacroParams } from 'babel-plugin-macros'
import type { NodePath, types as T } from '@babel/core'
import type * as P from 'postcss'
import type { Config as TailwindConfig } from 'tailwindcss'
import type { colors } from '../lib/logging'
import type userPresets from '../lib/userPresets'
type KeyValuePair<K extends keyof never = string, V = string> = Record<K, V>
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface RecursiveKeyValuePair<K extends keyof never = string, V = string> {
[key: string]: V | RecursiveKeyValuePair<K, V>
}
export type CssObject = RecursiveKeyValuePair<string, string | string[]>
// Make all properties in T optional
type Partial<T> = {
[P in keyof T]?: T[P]
}
export type ColorValue = (c: typeof colors) => string
export type ColorType = keyof typeof colors
export type MakeColor = (message: string, type?: keyof typeof colors) => string
export type PresetItem = { import: string; from: string }
export type PresetConfig = {
styled: PresetItem
css: PresetItem
global: PresetItem
}
export type TwinConfigAll = {
preset?: keyof typeof userPresets
allowStyleProp: boolean
autoCssProp: boolean
dataTwProp: boolean | 'all'
sassyPseudo: boolean
debug: boolean
includeClassNames: boolean
dataCsProp: boolean | 'all'
disableCsProp: boolean
disableShortCss: boolean
config?: string | Partial<TailwindConfig>
convertStyledDotToParam?: boolean
convertStyledDotToFunction?: boolean
moveTwPropToStyled?: boolean
moveKeyframesToGlobalStyles?: boolean
convertHtmlElementToStyled?: boolean
hasLogColors?: boolean
stitchesConfig?: string
} & PresetConfig
export type Candidate = [
data: { layer: string },
rule: P.Rule | P.AtRule | P.Declaration
]
export type TailwindContext = {
getClassOrder: (
classes: string[]
) => Array<[className: string, order: bigint]>
candidateRuleMap: Array<[string, Candidate[]]>
variantMap: Array<Record<string, never>>
}
export type AssertContext = {
color: MakeColor
}
export type Assert = (
expression: boolean | string,
message: ({ color }: AssertContext) => string
) => void
export type CoreContext = {
isDev: boolean
assert: Assert
debug: (reference: string, data: unknown, type?: ColorType) => void
theme: (
dotSeparatedItem: string,
extra?: string
) => Record<string, unknown> | boolean | number
tailwindContext: TailwindContext
packageUsed: GetPackageUsed
tailwindConfig: TailwindConfig
twinConfig: TwinConfigAll
CustomError: typeof Error
importConfig: PresetConfig
isShortCssOnly?: boolean
isSilent?: boolean
options?: TailwindMatchOptions
}
export type ExtractRuleStyles = {
includeUniversalStyles?: boolean
original?: string
hasImportant?: boolean
selectorMatchReg?: RegExp
passChecks?: boolean
sassyPseudo?: TwinConfigAll['sassyPseudo']
coreContext: CoreContext
} & Pick<
CoreContext,
| 'assert'
| 'debug'
| 'theme'
| 'tailwindConfig'
| 'tailwindContext'
| 'options'
| 'twinConfig'
>
export type TransformDecl = {
decl: P.Declaration
property: string
} & ExtractRuleStyles
export type CreateCoreContext = {
isDev?: boolean
config?: TwinConfig
sourceRoot?: string
filename?: string
tailwindConfig?: TailwindConfig
CustomError: typeof Error
}
export type PossiblePresets = keyof typeof userPresets
export type GetPackageUsed = {
isEmotion: boolean
isStyledComponents: boolean
isGoober: boolean
isStitches: boolean
isSolid: boolean
}
export type TailwindMatchOptions = {
preserveSource?: boolean
respectPrefix?: boolean
respectImportant?: boolean
values?: Record<string, string>
}
export type TailwindMatch = [
{ options?: TailwindMatchOptions; layer?: string },
P.Rule | P.AtRule | P.Declaration
]
export type GetConfigTwinValidatedParameters = GetPackageUsed & {
isDev: boolean
}
export type TwinConfig = Partial<TwinConfigAll>
export type { T, NodePath, MacroParams, TailwindConfig, KeyValuePair }
================================================
FILE: src/macro/className.ts
================================================
// eslint-disable-next-line import/no-relative-parent-imports
import { getStyles } from '../core'
import { addDataTwPropToPath, addDataPropToExistingPath } from './dataProp'
import isEmpty from './lib/util/isEmpty'
import {
astify,
getParentJSX,
getAttributeNames,
getCssAttributeData,
} from './lib/astHelpers'
import type { JSXAttributeHandler, T, NodePath } from './types'
function makeJsxAttribute(
[key, value]: [string, T.Expression | T.JSXEmptyExpression],
t: typeof T
): T.JSXAttribute {
return t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(value))
}
function handleClassNameProperty({
path,
t,
state,
coreContext,
}: JSXAttributeHandler): void {
if (!coreContext.twinConfig.includeClassNames) return
if (path.node.name.name !== 'className') return
const nodeValue = path.node.value
if (!nodeValue) return
// Ignore className if it cannot be resolved
if ((nodeValue as T.JSXExpressionContainer).expression) return
const rawClasses = (nodeValue as T.StringLiteral).value
if (!rawClasses) return
const { styles, unmatched, matched } = getStyles(rawClasses, {
...coreContext,
isSilent: true,
})
if (matched.length === 0) return
const astStyles = astify(isEmpty(styles) ? {} : styles, t)
// When classes can't be matched we add them back into the className (it exists as a few properties)
const unmatchedClasses = unmatched.join(' ')
if (!path.node.value) return
;(path.node.value as T.StringLiteral).value = unmatchedClasses
if (path.node.value.extra) {
path.node.value.extra.rawValue = unmatchedClasses
path.node.value.extra.raw = `"${unmatchedClasses}"`
}
const jsxPath = getParentJSX(path)
const attributes = jsxPath.get('attributes')
const { attribute: cssAttribute } = getCssAttributeData(attributes)
if (!cssAttribute) {
const attribute = makeJsxAttribute(['css', astStyles], t)
if (unmatchedClasses) {
path.insertAfter(attribute)
} else {
path.replaceWith(attribute)
}
const pathParameters = {
t,
path,
state,
attributes,
coreContext,
rawClasses: matched.join(' '),
}
addDataTwPropToPath(pathParameters)
return
}
const cssExpression = (cssAttribute as NodePath<T.JSXAttribute>)
.get('value')
.get('expression') as NodePath<T.Expression>
const attributeNames = getAttributeNames(jsxPath)
const isBeforeCssAttribute =
attributeNames.indexOf('className') - attributeNames.indexOf('css') < 0
if (cssExpression.isArrayExpression()) {
// The existing css prop is an array, eg: css={[...]}
if (isBeforeCssAttribute) {
cssExpression.unshiftContainer('elements', astStyles)
} else {
cssExpression.pushContainer('elements', astStyles)
}
} else {
// The existing css prop is not an array, eg: css={{ ... }} / css={`...`}
const existingCssAttribute = cssExpression.node
coreContext.assert(Boolean(existingCssAttribute), ({ color }) =>
color(
`✕ An empty css prop (css="") isn’t supported alongside the className prop`
)
)
const styleArray = isBeforeCssAttribute
? [astStyles, existingCssAttribute]
: [existingCssAttribute, astStyles]
cssExpression.replaceWith(t.arrayExpression(styleArray))
}
if (!unmatchedClasses) path.remove()
addDataPropToExistingPath({
t,
attributes,
rawClasses: matched.join(' '),
path: jsxPath,
state,
coreContext,
})
}
export { handleClassNameProperty }
================================================
FILE: src/macro/css.ts
================================================
import { addImport, makeStyledComponent } from './lib/astHelpers'
import isEmpty from './lib/util/isEmpty'
import type {
T,
AdditionalHandlerParameters,
HandlerParameters,
NodePath,
} from './types'
function updateCssReferences({
references,
state,
}: AdditionalHandlerParameters): void {
if (state.existingCssIdentifier) return
const cssReferences = references.css
if (isEmpty(cssReferences)) return
cssReferences.forEach(path => {
// @ts-expect-error Setting value on target
path.node.name = state.cssIdentifier.name
})
}
function addCssImport({
references,
program,
t,
state,
coreContext,
}: AdditionalHandlerParameters): void {
if (!state.isImportingCss) {
const shouldImport =
!isEmpty(references.css) && !state.existingCssIdentifier
if (!shouldImport) return
}
if (state.existingCssIdentifier) return
if (!coreContext.importConfig.css) return
addImport({
types: t,
program,
name: coreContext.importConfig.css.import,
mod: coreContext.importConfig.css.from,
identifier: state.cssIdentifier,
})
}
function convertHtmlElementToStyled(
params: HandlerParameters & { path: NodePath<T.JSXElement> }
): void {
const { path, t, coreContext } = params
if (!coreContext.twinConfig.convertHtmlElementToStyled) return
const jsxPath = path.get('openingElement')
makeStyledComponent({
...params,
jsxPath,
secondArg: t.objectExpression([]),
fromProp: 'css',
})
}
export { updateCssReferences, addCssImport, convertHtmlElementToStyled }
================================================
FILE: src/macro/dataProp.ts
================================================
import type { AddDataPropToExistingPath, T } from './types'
const SPACE_ID = '_'
const EXTRA_WHITESPACE = /\s\s+/g
const LINEFEED = /\n/g
function formatProp(classes: string): string {
return (
classes
// Normalize spacing
.replace(EXTRA_WHITESPACE, ' ')
// Remove newline characters
.replace(LINEFEED, ' ')
// Replace the space id
.replace(SPACE_ID, ' ')
.trim()
)
}
function addDataTwPropToPath({
t,
attributes,
rawClasses,
path,
state,
coreContext,
propName = 'data-tw',
}: AddDataPropToExistingPath): void {
const dataTwPropAllEnvironments =
propName === 'data-tw' && coreContext.twinConfig.dataTwProp === 'all'
const dataCsPropAllEnvironments =
propName === 'data-cs' && coreContext.twinConfig.dataCsProp === 'all'
if (!state.isDev && !dataTwPropAllEnvironments && !dataCsPropAllEnvironments)
return
if (propName === 'data-tw' && !coreContext.twinConfig.dataTwProp) return
if (propName === 'data-cs' && !coreContext.twinConfig.dataCsProp) return
// A for in loop looping over attributes and removing the one we want
for (const p of attributes) {
if (p.type === 'JSXSpreadAttribute') continue
const nodeName = p.node as T.JSXAttribute
if (nodeName?.name && nodeName.name.name === propName) p.remove()
}
const classes = formatProp(rawClasses)
// Add the attribute
path.insertAfter(
t.jsxAttribute(t.jsxIdentifier(propName), t.stringLiteral(classes))
)
}
function addDataPropToExistingPath({
t,
attributes,
rawClasses,
path,
state,
coreContext,
propName = 'data-tw',
}: AddDataPropToExistingPath): void {
const dataTwPropAllEnvironments =
propName === 'data-tw' && coreContext.twinConfig.dataTwProp === 'all'
const dataCsPropAllEnvironments =
propName === 'data-cs' && coreContext.twinConfig.dataCsProp === 'all'
if (!state.isDev && !dataTwPropAllEnvironments && !dataCsPropAllEnvironments)
return
if (propName === 'data-tw' && !coreContext.twinConfig.dataTwProp) return
if (propName === 'data-cs' && !coreContext.twinConfig.dataCsProp) return
// Append to the existing debug attribute
const dataProperty = attributes.find(
p =>
(p.node as T.JSXAttribute)?.name &&
(p.node as T.JSXAttribute).name.name === propName
)
if (dataProperty) {
try {
// Existing data prop
if (
((dataProperty.node as T.JSXAttribute).value as T.StringLiteral).value
) {
;(
(dataProperty.node as T.JSXAttribute).value as T.StringLiteral
).value = `${[
((dataProperty.node as T.JSXAttribute).value as T.StringLiteral)
.value,
rawClasses,
]
.filter(Boolean)
.join(' | ')}`
return
}
// New data prop
const attribute = (dataProperty.node as T.JSXAttribute)
.value as T.JSXExpressionContainer
// @ts-expect-error Setting value on target
attribute.expression.value = `${[
// @ts-expect-error Okay with value not on all expression types
(dataProperty.node.value as T.JSXExpressionContainer).expression.value,
rawClasses,
]
.filter(Boolean)
.join(' | ')}`
} catch (_: unknown) {}
return
}
const classes = formatProp(rawClasses)
// Add a new attribute
path.pushContainer(
// @ts-expect-error Key is never
'attributes',
t.jSXAttribute(
t.jSXIdentifier(propName),
t.jSXExpressionContainer(t.stringLiteral(classes))
)
)
}
export { addDataTwPropToPath, addDataPropToExistingPath }
================================================
FILE: src/macro/globalStyles.ts
================================================
// eslint-disable-next-line import/no-relative-parent-imports
import { getGlobalStyles } from '../core'
import template from '@babel/template'
import {
addImport,
generateUid,
generateTaggedTemplateExpression,
} from './lib/astHelpers'
import type {
CoreContext,
AdditionalHandlerParameters,
NodePath,
State,
T,
CssObject,
} from './types'
const KEBAB_CANDIDATES = /([\da-z]|(?=[A-Z]))([A-Z])/g
type AddGlobalStylesImport = {
program: NodePath<T.Program>
t: typeof T
identifier: T.Identifier
coreContext: CoreContext
}
function addGlobalStylesImport({
program,
t,
identifier,
coreContext,
}: AddGlobalStylesImport): void {
addImport({
types: t,
program,
identifier,
name: coreContext.importConfig.global.import,
mod: coreContext.importConfig.global.from,
})
}
export type DeclarationParameters = {
t: typeof T
state: State
globalUid: T.Identifier
stylesUid: T.Identifier
styles: string | undefined
}
function getGlobalDeclarationTte({
t,
stylesUid,
globalUid,
styles,
}: DeclarationParameters): T.VariableDeclaration {
return t.variableDeclaration('const', [
t.variableDeclarator(
globalUid,
generateTaggedTemplateExpression({ t, identifier: stylesUid, styles })
),
])
}
function getGlobalDeclarationProperty(
params: DeclarationParameters
): T.VariableDeclaration {
const { t, stylesUid, globalUid, state, styles } = params
const ttExpression = generateTaggedTemplateExpression({
t,
identifier: state.cssIdentifier as T.Identifier,
styles,
})
const openingElement = t.jsxOpeningElement(
t.jsxIdentifier(stylesUid.name),
[
t.jsxAttribute(
t.jsxIdentifier('styles'),
t.jsxExpressionContainer(ttExpression)
),
],
true
)
const closingElement = t.jsxClosingElement(t.jsxIdentifier('close'))
const arrowFunctionExpression = t.arrowFunctionExpression(
[],
t.jsxElement(openingElement, closingElement, [], true)
)
const code = t.variableDeclaration('const', [
t.variableDeclarator(globalUid, arrowFunctionExpression),
])
return code
}
function kebabize(string: string): string {
return string.replace(KEBAB_CANDIDATES, '$1-$2').toLowerCase()
}
function convert(k: string, v: string | number): string {
return typeof v === 'string'
? ` ${kebabize(k)}: ${v};`
: `${k} {
${convertCssObjectToString(v)}
}`
}
function convertCssObjectToString(
cssObject: CssObject | string | number | undefined
): string {
if (!cssObject) return ''
return Object.entries(cssObject)
.map(([k, v]) => convert(k, v))
.join('\n')
}
function handleGlobalStylesFunction(params: AdditionalHandlerParameters): void {
const { references } = params
if (references.GlobalStyles) handleGlobalStylesJsx(params)
if (references.globalStyles) handleGlobalStylesVariable(params)
}
function handleGlobalStylesVariable(params: AdditionalHandlerParameters): void {
const { references } = params
if (references.globalStyles.length === 0) return
const styles = getGlobalStyles(params.coreContext)
references.globalStyles.forEach(path => {
const templateStyles = `(${JSON.stringify(styles)})` // `template` requires () wrapping
const convertedStyles = template(templateStyles, {
placeholderPattern: false,
})()
path.replaceWith(convertedStyles as NodePath)
})
}
function handleGlobalStylesJsx(params: AdditionalHandlerParameters): void {
const { references, program, t, state, coreContext } = params
if (references.GlobalStyles.length === 0) return
coreContext.assert(
references.GlobalStyles.length < 2,
({ color }) =>
`${color(
`✕ Only one <GlobalStyles /> can be added per file`
)}\n\nNeed something custom?\nUse the \`globalStyles\` import for a style object you can work with`
)
const path = references.GlobalStyles[0]
const parentPath = path.findParent(x => x.isJSXElement())
coreContext.assert(
Boolean(parentPath),
({ color }) =>
`${color(
`✕ The \`GlobalStyles\` import must be added as a JSX element`
)}\neg: \`<GlobalStyles />\`\n\nNeed something custom?\nUse the \`globalStyles\` import for a style object you can work with`
)
const globalStyles = getGlobalStyles(params.coreContext)
const styles = convertCssObjectToString(globalStyles)
const globalUid = generateUid('GlobalStyles', program)
const stylesUid = generateUid('globalImport', program)
const declarationData = { t, globalUid, stylesUid, styles, state }
if (coreContext.packageUsed.isStyledComponents) {
const declaration = getGlobalDeclarationTte(declarationData)
program.unshiftContainer('body', declaration)
path.replaceWith(t.jSXIdentifier(globalUid.name))
}
if (coreContext.packageUsed.isEmotion) {
const declaration = getGlobalDeclarationProperty(declarationData)
program.unshiftContainer('body', declaration)
path.replaceWith(t.jSXIdentifier(globalUid.name))
// Check if the css import has already been imported
// https://github.com/ben-rogerson/twin.macro/issues/313
state.isImportingCss = !state.existingCssIdentifier
}
if (coreContext.packageUsed.isGoober || coreContext.packageUsed.isSolid) {
const declaration = getGlobalDeclarationTte(declarationData)
program.unshiftContainer('body', declaration)
path.replaceWith(t.jSXIdentifier(globalUid.name))
}
coreContext.assert(
Boolean(!coreContext.packageUsed.isStitches),
({ color }) =>
`${color(
`✕ The ${color(
'GlobalStyles',
'errorLight'
)} import can’t be used with stitches`
)}\n\nUse the ${color(`globalStyles`, 'success')} import instead`
)
addGlobalStylesImport({
identifier: stylesUid,
t,
program,
coreContext,
})
}
export { handleGlobalStylesFunction }
================================================
FILE: src/macro/lib/astHelpers.ts
================================================
import get from './util/get'
import type {
T,
State,
NodePath,
CoreContext,
ImportDeclarationHandler,
} from 'macro/types'
function addImport({
types: t,
program,
mod,
name,
identifier,
}: {
types: typeof T
program: NodePath<T.Program>
mod: string
name: string
identifier: T.Identifier
}): void {
const importName =
name === 'default'
? [t.importDefaultSpecifier(identifier)]
: name
? [t.importSpecifier(identifier, t.identifier(name))]
: []
program.unshiftContainer(
'body',
t.importDeclaration(importName, t.stringLiteral(mod))
)
}
/**
* Convert plain js into babel ast
*/
function astify(
literal: unknown,
t: typeof T
):
| T.NullLiteral
| T.UnaryExpression
| T.NumericLiteral
| T.BooleanLiteral
| T.StringLiteral
| T.Expression {
if (literal === null) {
return t.nullLiteral()
}
switch (typeof literal) {
case 'function': {
return t.unaryExpression('void', t.numericLiteral(0), true)
}
case 'number': {
return t.numericLiteral(literal)
}
case 'boolean': {
return t.booleanLiteral(literal)
}
case 'undefined': {
return t.unaryExpression('void', t.numericLiteral(0), true)
}
case 'string': {
return t.stringLiteral(literal)
}
default: {
if (Array.isArray(literal)) {
return t.arrayExpression(literal.map(x => astify(x, t)))
}
return t.objectExpression(
objectExpressionElements(literal as Record<string, string>, t)
)
}
}
}
function objectExpressionElements(
literal: Record<string, string>,
t: typeof T
): T.ObjectProperty[] {
return Object.keys(literal)
.filter(k => typeof literal[k] !== 'undefined')
.map(
(k: string): T.ObjectProperty =>
t.objectProperty(t.stringLiteral(k), astify(literal[k], t))
)
}
function setStyledIdentifier({
state,
path,
coreContext,
}: ImportDeclarationHandler): void {
const importFromStitches =
coreContext.packageUsed.isStitches &&
coreContext.importConfig.styled.from.includes(path.node.source.value)
const importFromLibrary =
path.node.source.value === coreContext.importConfig.styled.from
if (!importFromLibrary && !importFromStitches) return
// Look for an existing import that matches the config,
// if found then reuse it for the rest of the function calls
path.node.specifiers.some(specifier => {
if (
specifier.type === 'ImportDefaultSpecifier' &&
coreContext.importConfig.styled.import === 'default' &&
// fixes an issue in gatsby where the styled-components plugin has run
// before twin. fix is to ignore import aliases which babel creates
// https://github.com/ben-rogerson/twin.macro/issues/192
!specifier.local.name.startsWith('_')
) {
state.styledIdentifier = specifier.local
state.existingStyledIdentifier = true
return true
}
if (
specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name === coreContext.importConfig.styled.import
) {
state.styledIdentifier = specifier.local
state.existingStyledIdentifier = true
return true
}
state.existingStyledIdentifier = false
return false
})
}
function setCssIdentifier({
state,
path,
coreContext,
}: ImportDeclarationHandler): void {
const importFromStitches =
coreContext.packageUsed.isStitches &&
coreContext.importConfig.css.from.includes(path.node.source.value)
const isLibraryImport =
path.node.source.value === coreContext.importConfig.css.from
if (!isLibraryImport && !importFromStitches) return
// Look for an existing import that matches the config,
// if found then reuse it for the rest of the function calls
path.node.specifiers.some(specifier => {
if (
specifier.type === 'ImportDefaultSpecifier' &&
coreContext.importConfig.css.import === 'default'
) {
state.cssIdentifier = specifier.local
state.existingCssIdentifier = true
return true
}
if (
specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name === coreContext.importConfig.css.import
) {
state.cssIdentifier = specifier.local
state.existingCssIdentifier = true
return true
}
state.existingCssIdentifier = false
return false
})
}
function getStringFromTTE(path: NodePath<T.TaggedTemplateExpression>): string {
let getRawValue = false
let rawValue = ''
// Convert basic interpolated variables defined in the same file
const evaluatedValue = (path.get('quasi').evaluate().value as string) ?? ''
if (evaluatedValue === '') getRawValue = true
// Evaluating strips escaping, so if there's a square bracket we know it's an
// arbitrary value/property/variant and should grab the raw value
if (evaluatedValue.includes('[')) getRawValue = true
if (getRawValue)
rawValue = (path.get('quasi.quasis') as Array<NodePath<T.TemplateElement>>)
.map(q => q.node.value.raw)
.join('')
// Trigger error due to non-evaluated value, eg:`w-[${sizes.width}]`
if (evaluatedValue.length === 0 && rawValue.length > 0) return 'null'
// Return raw classes with escaping, eg: [content\!]:block
if (rawValue.length > evaluatedValue.length) return rawValue
return evaluatedValue
}
// Parse tagged template arrays (``)
function parseTte(
path: NodePath<T.TaggedTemplateExpression>,
{ t, state }: { t: typeof T; state: State }
): { string: string; path: NodePath<T.TaggedTemplateExpression> } | undefined {
const cloneNode = t.cloneNode || t.cloneDeep
const tagType = path.node.tag.type
if (
tagType !== 'Identifier' &&
tagType !== 'MemberExpression' &&
tagType !== 'CallExpression'
)
return
const string = getStringFromTTE(path)
// Grab the path location before changing it
const stringLoc = path.get('quasi').node.loc
if (tagType === 'CallExpression') {
replaceWithLocation(
path.get('tag').get('callee') as NodePath,
// @ts-expect-error Source type doesn’t include `Identifier` as possible type
cloneNode(state.styledIdentifier)
)
state.isImportingStyled = true
} else if (tagType === 'MemberExpression') {
replaceWithLocation(
path.get('tag').get('object') as NodePath,
// @ts-expect-error Source type doesn’t include `Identifier` as possible type
cloneNode(state.styledIdentifier)
)
state.isImportingStyled = true
}
if (tagType === 'CallExpression' || tagType === 'MemberExpression') {
replaceWithLocation(
path,
t.callExpression(cloneNode(path.node.tag), [
t.identifier('__twPlaceholder'),
]) as unknown as NodePath
)
path = (
path.get('arguments') as Array<NodePath<T.TaggedTemplateExpression>>
)[0]
}
path.node.loc = stringLoc // Restore the original path location
return { string, path }
}
function replaceWithLocation<EmptyArray>(
path: NodePath,
replacement: NodePath | T.Expression | T.ExpressionStatement
): [NodePath] | EmptyArray[] {
const { loc } = path.node
const newPaths = replacement ? path.replaceWith(replacement) : []
if (Array.isArray(newPaths) && newPaths.length > 0) {
newPaths.forEach(p => {
p.node.loc = loc
})
}
return newPaths
}
function generateUid(name: string, program: NodePath<T.Program>): T.Identifier {
return program.scope.generateUidIdentifier(name)
}
function getParentJSX(path: NodePath): NodePath<T.JSXOpeningElement> {
return path.findParent(p =>
p.isJSXOpeningElement()
) as NodePath<T.JSXOpeningElement>
}
function getAttributeNames(jsxPath: NodePath): string[] {
const attributes = jsxPath.get('attributes') as Array<
NodePath<T.JSXAttribute>
>
const attributeNames = attributes.map(p => p.node.name?.name) as string[]
return attributeNames
}
function getCssAttributeData<NodeType extends NodePath>(
attributes: NodeType[]
): {
index: number
hasCssAttribute: boolean
attribute: NodeType | undefined
} {
if (!String(attributes))
return { index: 0, hasCssAttribute: false, attribute: undefined }
const index = attributes.findIndex(
attribute =>
attribute?.isJSXAttribute() &&
((attribute.get('name.name') as NodePath).node as unknown as string) ===
'css'
)
return { index, hasCssAttribute: index >= 0, attribute: attributes[index] }
}
function getFunctionValue(
path: NodePath
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): { parent: NodePath; input: any } | undefined {
if (path.parent.type !== 'CallExpression') return
const parent = path.findParent(x => x.isCallExpression())
if (!parent) return
const argument = (parent.get('arguments') as NodePath[])[0] || ''
return {
parent,
input: argument.evaluate && (argument.evaluate().value as string),
}
}
function getTaggedTemplateValue<Path extends NodePath>(
path: Path
): { parent: NodePath<T.TaggedTemplateExpression>; input: string } | undefined {
if (path.parent.type !== 'TaggedTemplateExpression') return
const parent = path.findParent(x =>
x.isTaggedTemplateExpression()
) as NodePath<T.TaggedTemplateExpression>
if (!parent) return
if (parent.node.tag.type !== 'Identifier') return
return { parent, input: parent.get('quasi').evaluate().value as string }
}
function getMemberExpression(
path: NodePath
): { parent: NodePath; input: string } | undefined {
if (path.parent.type !== 'MemberExpression') return
const parent = path.findParent(x =>
x.isMemberExpression()
) as NodePath<T.MemberExpression>
if (!parent) return
return {
parent,
// @ts-expect-error name doesn't exist on node
input: parent.get('property').node.name as string,
}
}
function generateTaggedTemplateExpression({
t,
identifier,
styles,
}: {
t: typeof T
identifier: T.Identifier
styles: string | undefined
}): T.TaggedTemplateExpression {
const backtickStyles = t.templateElement({
raw: `${styles ?? ''}`,
cooked: `${styles ?? ''}`,
})
const ttExpression = t.taggedTemplateExpression(
identifier,
t.templateLiteral([backtickStyles], [])
)
return ttExpression
}
function isComponent(name: string): boolean {
return name.slice(0, 1).toUpperCase() === name.slice(0, 1)
}
const jsxSingleDotError = `The css prop + tw props can only be added to jsx elements with a single dot in their name (or no dot at all).`
function getFirstStyledArgument(
jsxPath: NodePath<T.JSXOpeningElement>,
t: typeof T,
assert: CoreContext['assert']
): T.MemberExpression | T.Identifier | T.StringLiteral {
const path = get(jsxPath, 'node.name.name') as string
if (path)
return isComponent(path) ? t.identifier(path) : t.stringLiteral(path)
const dotComponent = get(jsxPath, 'node.name') as string
assert(Boolean(dotComponent), () => jsxSingleDotError)
// Element name has dots in it
const objectName = get(dotComponent, 'object.name') as string
assert(Boolean(objectName), () => jsxSingleDotError)
const propertyName = get(dotComponent, 'property.name') as string
assert(Boolean(propertyName), () => jsxSingleDotError)
return t.memberExpression(
t.identifier(objectName),
t.identifier(propertyName)
)
}
type MakeStyledComponent = {
t: typeof T
secondArg: T.Expression | T.StringLiteral | T.Identifier
jsxPath: NodePath<T.JSXOpeningElement>
program: NodePath<T.Program>
state: State
coreContext: CoreContext
fromProp: 'tw' | 'css'
}
type CreateStyledProps = Pick<
MakeStyledComponent,
'jsxPath' | 't' | 'secondArg'
> & {
stateStyled: T.Identifier
constName: T.Identifier
firstArg: T.MemberExpression | T.Identifier | T.StringLiteral
}
function createStyledPropsForTw({
t,
stateStyled,
firstArg,
secondArg,
constName,
}: CreateStyledProps): T.VariableDeclaration {
const callee = t.callExpression(stateStyled, [firstArg])
const declarations = [
t.variableDeclarator(constName, t.callExpression(callee, [secondArg])),
]
return t.variableDeclaration('const', declarations)
}
function createStyledPropsForCss(
args: CreateStyledProps
): T.VariableDeclaration | undefined {
const cssPropAttribute = args.jsxPath
.get('attributes')
.find(
p =>
p.isJSXAttribute() &&
p.get('name').isJSXIdentifier() &&
p.get('name')?.node.name === 'css'
)
const cssPropValue = cssPropAttribute?.get(
'value'
) as NodePath<T.JSXExpressionContainer>
const expression = cssPropValue?.node?.expression
if (!expression || expression.type === 'JSXEmptyExpression') return
cssPropAttribute?.remove()
return createStyledPropsForTw({ ...args, secondArg: expression })
}
function makeStyledComponent({
t,
secondArg,
jsxPath,
program,
state,
coreContext,
fromProp,
}: MakeStyledComponent): void {
const constName = program.scope.generateUidIdentifier('TwComponent')
if (!state.styledIdentifier) {
state.styledIdentifier = generateUid('styled', program)
state.isImportingStyled = true
}
const firstArg = getFirstStyledArgument(jsxPath, t, coreContext.assert)
let styledDefinition = null
const stateStyled: T.Identifier = state.styledIdentifier
if (coreContext.packageUsed.isSolid) {
const params = { jsxPath, t, stateStyled, firstArg, secondArg, constName }
styledDefinition =
fromProp === 'tw'
? createStyledPropsForTw(params)
: createStyledPropsForCss(params)
} else {
const args = [firstArg, secondArg].filter(Boolean)
const init = t.callExpression(stateStyled, args)
const declarations = [t.variableDeclarator(constName, init)]
styledDefinition = t.variableDeclaration('const', declarations)
}
if (!styledDefinition) return
const rootParentPath = jsxPath.findParent(p =>
p.parentPath ? p.parentPath.isProgram() : false
) as NodePath<T.Program>
if (rootParentPath) rootParentPath.insertBefore(styledDefinition)
if (t.isMemberExpression(firstArg)) {
// Replace components with a dot, eg: Dialog.blah
const id = t.jsxIdentifier(constName.name)
jsxPath.get('name').replaceWith(id)
if (jsxPath.node.selfClosing) return
;(jsxPath.parentPath.get('closingElement.name') as NodePath).replaceWith(id)
} else {
;(jsxPath.node.name as T.JSXIdentifier).name = constName.name
if (jsxPath.node.selfClosing) return
// @ts-expect-error Untyped name replacement
jsxPath.parentPath.node.closingElement.name.name = constName.name
}
}
function getJsxAttributes(
path: NodePath<T.JSXElement>
): Array<NodePath<T.JSXAttribute>> {
const attributes = path.get('openingElement.attributes') as Array<
NodePath<T.JSXAttribute>
>
return attributes.filter(a => a.isJSXAttribute())
}
export {
addImport,
astify,
parseTte,
replaceWithLocation,
setStyledIdentifier,
setCssIdentifier,
generateUid,
getParentJSX,
getAttributeNames,
getCssAttributeData,
getFunctionValue,
getTaggedTemplateValue,
getMemberExpression,
generateTaggedTemplateExpression,
makeStyledComponent,
getJsxAttributes,
}
================================================
FILE: src/macro/lib/util/get.ts
================================================
import get from 'lodash.get'
// eslint-disable-next-line unicorn/prefer-export-from
export default get
================================================
FILE: src/macro/lib/util/isEmpty.ts
================================================
function isEmpty(value: unknown): boolean {
return (
value === undefined ||
value === null ||
(typeof value === 'object' && Object.keys(value).length === 0) ||
(typeof value === 'string' && value.trim().length === 0)
)
}
export default isEmpty
================================================
FILE: src/macro/lib/validateImports.ts
================================================
import type { CoreContext, MacroParams } from 'macro/types'
const validImports = new Set([
'default',
'styled',
'css',
'theme',
'screen',
'TwStyle',
'TwComponent',
'ThemeStyle',
'GlobalStyles',
'globalStyles',
])
export default function validateImports(
imports: MacroParams['references'],
coreContext: CoreContext
): void {
const importTwAsNamedNotDefault = Object.keys(imports).find(
reference => reference === 'tw'
)
coreContext.assert(
!importTwAsNamedNotDefault,
({ color }) =>
`${color(
`✕ import { tw } from 'twin.macro'`
)}\n\nUse the default export for \`tw\`:\n\n${color(
`import tw from 'twin.macro'`,
'success'
)}`
)
const unsupportedImport = Object.keys(imports).find(
reference => !validImports.has(reference)
)
coreContext.assert(
!unsupportedImport,
({ color }) =>
`${color(
`✕ Twin doesn't recognize { ${String(unsupportedImport)} }`
)}\n\nTry one of these imports:\n\nimport ${color(
'tw',
'success'
)}, { ${color('styled', 'success')}, ${color('css', 'success')}, ${color(
'theme',
'success'
)}, ${color('screen', 'success')}, ${color(
'GlobalStyles',
'success'
)}, ${color('globalStyles', 'success')} } from 'twin.macro'`
)
}
================================================
FILE: src/macro/screen.ts
================================================
import {
replaceWithLocation,
astify,
getFunctionValue,
getTaggedTemplateValue,
getMemberExpression,
} from './lib/astHelpers'
import type {
AdditionalHandlerParameters,
T,
NodePath,
CoreContext,
} from './types'
type GetDirectReplacement = Pick<
HandleDefinition,
'mediaQuery' | 'parent' | 't'
>
function getDirectReplacement({
mediaQuery,
parent,
t,
}: GetDirectReplacement): Expression {
return {
newPath: parent,
replacement: astify(mediaQuery, t),
}
}
type ScreenValues =
| string
| { raw?: string; min?: string; max?: string }
| Array<{ raw?: string; min?: string; max?: string }>
type GetMediaQuery = {
input: string | string[]
screens: Record<string, ScreenValues>
assert: CoreContext['assert']
}
type Expression = {
newPath: NodePath
replacement: T.TemplateLiteral | T.ObjectExpression | T.Expression
}
type HandleDefinition = {
mediaQuery: string
parent: NodePath
type: string
t: typeof T
}
function handleDefinition({
mediaQuery,
parent,
type,
t,
}: HandleDefinition): undefined | (() => Expression) {
return {
TaggedTemplateExpression(): {
newPath: NodePath
replacement: T.TemplateLiteral
} {
const newPath = parent.findParent(x =>
x.isTaggedTemplateExpression()
) as NodePath<T.TaggedTemplateExpression>
const query = [`${mediaQuery} { `, ` }`]
const quasis = [
t.templateElement({ raw: query[0], cooked: query[0] }, false),
t.templateElement({ raw: query[1], cooked: query[1] }, true),
]
const expressions = [newPath.get('quasi').node]
const replacement = t.templateLiteral(quasis, expressions)
return { newPath, replacement }
},
CallExpression(): { newPath: NodePath; replacement: T.ObjectExpression } {
const newPath = parent.findParent(x =>
x.isCallExpression()
) as NodePath<T.CallExpression>
const value = newPath.get('arguments')[0].node as T.Expression
const replacement = t.objectExpression([
t.objectProperty(t.stringLiteral(mediaQuery), value),
])
return { newPath, replacement }
},
ObjectProperty(): Expression {
// Remove brackets around keys so merges work with tailwind screens
// styled.div({ [screen`2xl`]: tw`block`, ...tw`2xl:inline` })
// https://github.com/ben-rogerson/twin.macro/issues/379
// @ts-expect-error unsure of parent type
parent.parent.computed = false
return getDirectReplacement({ mediaQuery, parent, t })
},
ExpressionStatement: () => getDirectReplacement({ mediaQuery, parent, t }),
ArrowFunctionExpression: () =>
getDirectReplacement({ mediaQuery, parent, t }),
ArrayExpression: () => getDirectReplacement({ mediaQuery, parent, t }),
BinaryExpression: () => getDirectReplacement({ mediaQuery, parent, t }),
LogicalExpression: () => getDirectReplacement({ mediaQuery, parent, t }),
ConditionalExpression: () =>
getDirectReplacement({ mediaQuery, parent, t }),
VariableDeclarator: () => getDirectReplacement({ mediaQuery, parent, t }),
TemplateLiteral: () => getDirectReplacement({ mediaQuery, parent, t }),
TSAsExpression: () => getDirectReplacement({ mediaQuery, parent, t }),
}[type]
}
function getMediaQuery({ input, screens, assert }: GetMediaQuery): string {
const _input =
typeof input === 'string' ? input.split(',').map(s => s.trim()) : input
const _screens = _input.map(s => screens[s])
_input.forEach(i => {
assert(
Boolean(screens[i]),
({ color }) =>
`${color(
`${
input
? `✕ ${color(i, 'errorLight')} wasn’t found in your`
: 'Specify a screen value from your'
} tailwind config`
)}\n\nTry one of these values:\n\n${Object.entries(screens)
.map(
([k, v]) =>
`${color('-', 'subdued')} screen(${color(
`'${k}'`,
'success'
)})({ ... }) (${String(v)})`
)
.join('\n')}`
)
})
const mediaQuery = _screens
.map(screen => {
if (typeof screen === 'string') return `(min-width: ${screen})`
if (!Array.isArray(screen) && typeof screen.raw === 'string')
return screen.raw
return (Array.isArray(screen) ? screen : [screen])
.map(range =>
[
typeof range.min === 'string' ? `(min-width: ${range.min})` : null,
typeof range.max === 'string' ? `(max-width: ${range.max})` : null,
]
.filter(Boolean)
.join(' and ')
)
.join(', ')
})
.join(', ')
return mediaQuery ? `@media ${mediaQuery}` : ''
}
function handleScreenFunction({
references,
t,
coreContext,
}: AdditionalHandlerParameters): void {
if (!references.screen) return
const screens = coreContext.theme('screens') as Record<string, string>
references.screen.forEach(path => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { input, parent } = getTaggedTemplateValue(path) ?? // screen.lg``
getFunctionValue(path) ?? // screen.lg({ })
getMemberExpression(path) ?? {
// screen`lg`
input: null,
parent: null,
}
const definition = handleDefinition({
type: (parent as NodePath).parent.type,
mediaQuery: getMediaQuery({
input: input as string,
screens,
assert: coreContext.assert,
}),
parent: parent as NodePath,
t,
})
coreContext.assert(
Boolean(definition),
({ color }) =>
`${color(
`✕ The screen import doesn’t support that syntax`
)}\n\nTry using it like this: ${color(
[Object.keys(screens)[0]].map(f => `screen("${f}")`).join(''),
'success'
)}`
)
const { newPath, replacement } = (definition as () => Expression)()
replaceWithLocation(newPath, replacement)
})
}
export { handleScreenFunction }
================================================
FILE: src/macro/shortCss.ts
================================================
/* eslint-disable @typescript-eslint/no-unsafe-call */
// eslint-disable-next-line import/no-relative-parent-imports
import { getStyles } from '../core'
import isEmpty from './lib/util/isEmpty'
import { addDataTwPropToPath, addDataPropToExistingPath } from './dataProp'
import {
astify,
getParentJSX,
getAttributeNames,
getCssAttributeData,
} from './lib/astHelpers'
import type { NodePath, T, JSXAttributeHandler } from './types'
function handleCsProperty({
path,
t,
state,
coreContext,
}: JSXAttributeHandler): void {
if (coreContext.twinConfig.disableCsProp) return
if (!path.node || path.node.name.name !== 'cs') return
const nodeValue = path.node.value
const nodeExpression = (nodeValue as T.JSXExpressionContainer).expression
// Allow cs={"property[value]"}
const expressionValue =
nodeExpression &&
nodeExpression.type === 'StringLiteral' &&
nodeExpression.value
if (nodeExpression)
coreContext.assert(
Boolean(expressionValue),
({ color }) =>
`${color(
`✕ Only complete classes can be used with the "cs" prop`
)}\n\nTry using it like this: ${color(
'<div cs="maxWidth[30rem]" />',
'success'
)}\n\nRead more at https://twinredirect.page.link/cs-classes`
)
const rawClasses =
expressionValue || (nodeValue as T.StringLiteral).value || ''
const { styles } = getStyles(rawClasses, {
isShortCssOnly: true,
...coreContext,
})
const astStyles = astify(isEmpty(styles) ? {} : styles, t)
const jsxPath = getParentJSX(path)
const attributes = jsxPath.get('attributes')
const { attribute: cssAttribute } = getCssAttributeData(attributes)
if (!cssAttribute) {
// Replace the tw prop with the css prop
path.replaceWith(
t.jsxAttribute(
t.jsxIdentifier('css'),
t.jsxExpressionContainer(astStyles)
)
)
addDataTwPropToPath({
t,
attributes,
rawClasses,
path,
state,
coreContext,
propName: 'data-cs',
})
return
}
// The expression is the value as a NodePath
const attributeValuePath = cssAttribute.get('value')
// If it's not {} or "", get out of here
if (
!attributeValuePath ||
// @ts-expect-error The type checking functions don't exist on NodePath
(!attributeValuePath.isJSXExpressionContainer() &&
// @ts-expect-error The type checking functions don't exist on NodePath
!attributeValuePath.isStringLiteral())
)
return
// @ts-expect-error The type checking functions don't exist on NodePath
const existingCssAttribute = attributeValuePath.isStringLiteral()
? (attributeValuePath as unknown as NodePath<T.StringLiteral>)
: // @ts-expect-error get doesn’t exist on the types
(attributeValuePath.get(
'expression'
) as NodePath<T.JSXExpressionContainer>)
const attributeNames = getAttributeNames(jsxPath)
const isBeforeCssAttribute =
attributeNames.indexOf('cs') - attributeNames.indexOf('css') < 0
if (existingCssAttribute.isArrayExpression()) {
// The existing css prop is an array, eg: css={[...]}
if (isBeforeCssAttribute) {
// @ts-expect-error unshiftContainer doesn't exist on NodePath
existingCssAttribute.unshiftContainer('elements', astStyles)
} else {
// @ts-expect-error pushContainer doesn't exist on NodePath
existingCssAttribute.pushContainer('elements', astStyles)
}
} else {
// css prop is either:
// TemplateLiteral
// <div css={`...`} cs="..." />
// or an ObjectExpression
// <div css={{ ... }} cs="..." />
// or ArrowFunctionExpression/FunctionExpression
// <div css={() => (...)} cs="..." />
const existingCssAttributeNode = existingCssAttribute.node
// The existing css prop is an array, eg: css={[...]}
const styleArray = isBeforeCssAttribute
? [astStyles, existingCssAttributeNode]
: [existingCssAttributeNode, astStyles]
const arrayExpression = t.arrayExpression(styleArray as T.Expression[])
const { parent } = existingCssAttribute
const replacement =
parent.type === 'JSXAttribute'
? t.jsxExpressionContainer(arrayExpression)
: arrayExpression
existingCssAttribute.replaceWith(replacement)
}
path.remove() // remove the cs prop
addDataPropToExistingPath({
t,
attributes,
rawClasses,
path: jsxPath,
state,
coreContext,
propName: 'data-cs',
})
}
export { handleCsProperty }
================================================
FILE: src/macro/styled.ts
================================================
import { addImport, replaceWithLocation } from './lib/astHelpers'
import isEmpty from './lib/util/isEmpty'
import get from './lib/util/get'
import type { T, NodePath, AdditionalHandlerParameters } from './types'
function updateStyledReferences({
references,
state,
}: AdditionalHandlerParameters): void {
if (state.existingStyledIdentifier) return
const styledReferences = references.styled
if (isEmpty(styledReferences)) return
styledReferences.forEach(path => {
// @ts-expect-error Setting values is untyped
path.node.name = state.styledIdentifier.name
})
}
function addStyledImport({
references,
program,
t,
state,
coreContext,
}: AdditionalHandlerParameters): void {
if (!state.isImportingStyled) {
const shouldImport =
!isEmpty(references.styled) && !state.existingStyledIdentifier
if (!shouldImport) return
}
if (state.existingStyledIdentifier) return
addImport({
types: t,
program,
name: coreContext.importConfig.styled.import,
mod: coreContext.importConfig.styled.from,
identifier: state.styledIdentifier,
})
}
function moveDotElement({
path,
t,
moveToParam = true,
}: {
path: NodePath
t: typeof T
moveToParam: boolean
}): void {
if (path.parent.type !== 'MemberExpression') return
const parentCallExpression = path.findParent(x =>
x.isCallExpression()
) as NodePath<T.CallExpression>
if (!parentCallExpression) return
const styledName = get(path, 'parentPath.node.property.name') as string
const styledArgs = get(parentCallExpression, 'node.arguments.0') as
| T.Expression
| T.SpreadElement
| T.JSXNamespacedName
| T.ArgumentPlaceholder
| T.ArrowFunctionExpression
let replacement
if (moveToParam) {
// `styled('div', {})`
const args = [t.stringLiteral(styledName), styledArgs].filter(Boolean)
replacement = t.callExpression((path as NodePath<T.Expression>).node, args)
} else {
// `styled('div')({})`
const callee = t.callExpression((path as NodePath<T.Expression>).node, [
t.stringLiteral(styledName),
])
replacement = t.expressionStatement(t.callExpression(callee, [styledArgs]))
}
replaceWithLocation(parentCallExpression, replacement)
}
function handleStyledFunction({
references,
t,
coreContext,
}: AdditionalHandlerParameters): void {
if (
!coreContext.twinConfig.convertStyledDotToParam &&
!coreContext.twinConfig.convertStyledDotToFunction
)
return
if (isEmpty(references)) return
const defaultRefs = references.default || []
const styledRefs = references.styled || []
const refs = [...defaultRefs, ...styledRefs].filter(Boolean)
refs.forEach((path: NodePath): void => {
// convert tw.div`` & styled.div`` to styled('div', {}) / styled('div')({})
moveDotElement({
path,
t,
moveToParam: coreContext.twinConfig.convertStyledDotToParam ?? true,
})
})
}
export { updateStyledReferences, addStyledImport, handleStyledFunction }
================================================
FILE: src/macro/theme.ts
================================================
import {
replaceWithLocation,
astify,
getFunctionValue,
getTaggedTemplateValue,
} from './lib/astHelpers'
import type { AssertContext } from 'core/types'
import type { AdditionalHandlerParameters, NodePath } from 'macro/types'
function handleThemeFunction({
references,
t,
coreContext,
}: AdditionalHandlerParameters): void {
if (!references.theme) return
references.theme.forEach((path): never[] | [Node | NodePath] => {
const ttValue = getTaggedTemplateValue(path) ??
getFunctionValue(path) ?? { input: null, parent: null }
const { input, parent } = ttValue as {
parent: NodePath
input?: string
}
if (input !== '')
coreContext.assert(
Boolean(input),
({ color }: AssertContext) =>
`${color(`✕ The theme value doesn’t look right`)}\n\nTry ${color(
'theme`colors.black`',
'success'
)} or ${color(`theme('colors.black')`, 'success')}`
)
coreContext.assert(
Boolean(parent),
({ color }: AssertContext) =>
`${color(
`✕ The theme value ${color(
input as string,
'errorLight'
)} doesn’t look right`
)}\n\nTry ${color('theme`colors.black`', 'success')} or ${color(
`theme('colors.black')`,
'success'
)}`
)
const themeValue = coreContext.theme(input as string)
coreContext.assert(Boolean(themeValue), ({ color }: AssertContext) =>
color(
`✕ ${color(
input as string,
'errorLight'
)} doesn’t match a theme value from the config`
)
)
return replaceWithLocation(parent, astify(themeValue, t))
})
}
export { handleThemeFunction }
================================================
FILE: src/macro/tw.ts
================================================
// eslint-disable-next-line import/no-relative-parent-imports
import { getStyles } from '../core'
// eslint-disable-next-line import/no-relative-parent-imports
import getSuggestions from '../suggestions'
import {
astify,
getParentJSX,
parseTte,
replaceWithLocation,
getAttributeNames,
getCssAttributeData,
makeStyledComponent,
} from './lib/astHelpers'
import isEmpty from './lib/util/isEmpty'
import { addDataTwPropToPath, addDataPropToExistingPath } from './dataProp'
import type {
AdditionalHandlerParameters,
CoreContext,
JSXAttributeHandler,
NodePath,
State,
T,
} from './types'
type MoveTwPropToStyled = {
t: typeof T
state: State
program: NodePath<T.Program>
astStyles: T.Expression
jsxPath: NodePath<T.JSXOpeningElement>
coreContext: CoreContext
}
function moveTwPropToStyled(params: MoveTwPropToStyled): void {
const { jsxPath, astStyles } = params
makeStyledComponent({ ...params, secondArg: astStyles, fromProp: 'tw' })
// Remove the tw attribute
const tagAttributes = jsxPath.node.attributes
const twAttributeIndex = tagAttributes.findIndex(
n => n.type === 'JSXAttribute' && n.name && n.name.name === 'tw'
)
if (twAttributeIndex < 0) return
jsxPath.node.attributes.splice(twAttributeIndex, 1)
}
type MergeIntoCssAttribute = {
t: typeof T
path: NodePath<T.JSXOpeningElement>
astStyles: T.Expression
cssAttribute: NodePath<T.JSXAttribute> | undefined
}
function mergeIntoCssAttribute({
t,
path,
astStyles,
cssAttribute,
}: MergeIntoCssAttribute): void {
if (!cssAttribute) return
// The expression is the value as a NodePath
const attributeValuePath = cssAttribute.get('value')
// If it's not {} or "", get out of here
if (
!attributeValuePath ||
(!attributeValuePath.isJSXExpressionContainer() &&
!attributeValuePath.isStringLiteral())
)
return
const existingCssAttribute = attributeValuePath.isStringLiteral()
? (attributeValuePath as unknown as NodePath<T.StringLiteral>)
: // @ts-expect-error get doesn’t exist on the types
(attributeValuePath.get(
'expression'
) as NodePath<T.JSXExpressionContainer>)
const attributeNames = getAttributeNames(path)
const isBeforeCssAttribute =
attributeNames.indexOf('tw') - attributeNames.indexOf('css') < 0
if (existingCssAttribute.isArrayExpression()) {
// The existing css prop is an array, eg: css={[...]}
if (isBeforeCssAttribute) {
const attribute = existingCssAttribute as NodePath<
T.StringLiteral | T.JSXExpressionContainer
>
// @ts-expect-error never in arg0?
attribute.unshiftContainer('elements', astStyles)
} else {
const attribute = existingCssAttribute as NodePath<
T.StringLiteral | T.JSXExpressionContainer
>
// @ts-expect-error never in arg0?
attribute.pushContainer('elements', astStyles)
}
} else {
// css prop is either:
// TemplateLiteral
// <div css={`...`} tw="..." />
// or an ObjectExpression
// <div css={{ ... }} tw="..." />
// or ArrowFunctionExpression/FunctionExpression
// <div css={() => (...)} tw="..." />
const existingCssAttributeNode = existingCssAttribute.node
// The existing css prop is an array, eg: css={[...]}
const styleArray = isBeforeCssAttribute
? [astStyles, existingCssAttributeNode]
: [existingCssAttributeNode, astStyles]
const arrayExpression = t.arrayExpression(styleArray as T.Expression[])
const { parent } = existingCssAttribute
const replacement =
parent.type === 'JSXAttribute'
? t.jsxExpressionContainer(arrayExpression)
: arrayExpression
existingCssAttribute.replaceWith(replacement)
}
}
function handleTwProperty({
path,
t,
program,
state,
coreContext,
}: JSXAttributeHandler): void {
if (!path.node || path.node.name.name !== 'tw') return
state.hasTwAttribute = true
const nodeValue = path.node.value
if (!nodeValue) return
const nodeExpression = (nodeValue as T.JSXExpressionContainer).expression
// Handle `tw={"block"}`
const expressionValue =
nodeExpression &&
nodeExpression.type === 'StringLiteral' &&
nodeExpression.value
if (expressionValue === '') return // Allow `tw={""}`
// Feedback for unsupported usage
if (nodeExpression)
coreContext.assert(
Boolean(expressionValue),
({ color }) =>
`${color(
`✕ Only plain strings can be used with the "tw" prop`
)}\n\nTry using it like this: ${color(
`<div tw="text-black" />`,
'success'
)} or ${color(
`<div tw={"text-black"} />`,
'success'
)}\n\nRead more at https://twinredirect.page.link/template-literals`
)
const rawClasses =
expressionValue || (nodeValue as T.StringLiteral).value || ''
const { styles, unmatched } = getStyles(rawClasses, coreContext)
if (unmatched.length > 0) {
getSuggestions(unmatched, {
CustomError: coreContext.CustomError,
tailwindContext: coreContext.tailwindContext,
tailwindConfig: coreContext.tailwindConfig,
hasLogColors: coreContext.twinConfig.hasLogColors,
})
return
}
const astStyles = astify(isEmpty(styles) ? {} : styles, t)
const jsxPath = getParentJSX(path)
const attributes = jsxPath.get('attributes')
const { attribute: cssAttribute } = getCssAttributeData(attributes)
gitextract_n8pigfyt/
├── .babelrc
├── .eslintrc.js
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ └── config.yml
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── docs/
│ ├── advanced-theming.md
│ ├── arbitrary-values.md
│ ├── customizing-config.md
│ ├── fonts.md
│ ├── group.md
│ ├── index.md
│ ├── options.md
│ ├── prop-styling-guide.md
│ ├── screen-import.md
│ └── styled-component-guide.md
├── jest.config.ts
├── package.json
├── sandbox/
│ └── in.tsx
├── src/
│ ├── core/
│ │ ├── constants.ts
│ │ ├── createCoreContext.ts
│ │ ├── extractRuleStyles.ts
│ │ ├── getGlobalStyles.ts
│ │ ├── getStyles.ts
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── configHelpers.ts
│ │ │ ├── convertClassName.ts
│ │ │ ├── createAssert.ts
│ │ │ ├── createTheme.ts
│ │ │ ├── defaultTailwindConfig.ts
│ │ │ ├── expandVariantGroups.ts
│ │ │ ├── getStitchesPath.ts
│ │ │ ├── logging.ts
│ │ │ ├── twinConfig.ts
│ │ │ ├── userPresets.ts
│ │ │ └── util/
│ │ │ ├── camelize.ts
│ │ │ ├── deepMerge.ts
│ │ │ ├── escapeRegex.ts
│ │ │ ├── formatProp.ts
│ │ │ ├── get.ts
│ │ │ ├── isEmpty.ts
│ │ │ ├── isObject.ts
│ │ │ ├── isShortCss.ts
│ │ │ ├── replaceThemeValue.ts
│ │ │ ├── sassifySelector.ts
│ │ │ ├── splitOnFirst.ts
│ │ │ ├── toArray.ts
│ │ │ └── twImports.ts
│ │ └── types/
│ │ └── index.ts
│ ├── macro/
│ │ ├── className.ts
│ │ ├── css.ts
│ │ ├── dataProp.ts
│ │ ├── globalStyles.ts
│ │ ├── lib/
│ │ │ ├── astHelpers.ts
│ │ │ ├── util/
│ │ │ │ ├── get.ts
│ │ │ │ └── isEmpty.ts
│ │ │ └── validateImports.ts
│ │ ├── screen.ts
│ │ ├── shortCss.ts
│ │ ├── styled.ts
│ │ ├── theme.ts
│ │ ├── tw.ts
│ │ ├── twin.ts
│ │ └── types/
│ │ └── index.ts
│ ├── macro.ts
│ └── suggestions/
│ ├── index.ts
│ ├── lib/
│ │ ├── colors.ts
│ │ ├── extractors.ts
│ │ ├── getClassSuggestions.ts
│ │ ├── getPackageVersions.ts
│ │ ├── makeColor.ts
│ │ ├── validateVariants.ts
│ │ └── validators.ts
│ └── types/
│ └── index.ts
├── tests/
│ ├── @applyInPlugins.test.ts
│ ├── __fixtures__/
│ │ ├── !general.tsx
│ │ ├── !important.tsx
│ │ ├── !imports.tsx
│ │ ├── !namelessImport.tsx
│ │ ├── !ordering.tsx
│ │ ├── !properties.tsx
│ │ ├── !variantGrouping.tsx
│ │ ├── !variants.tsx
│ │ ├── .eslintrc.js
│ │ ├── addBase/
│ │ │ ├── addBase.tsx
│ │ │ └── tailwind.config.js
│ │ ├── arbitraryProperties/
│ │ │ └── arbitraryProperties.tsx
│ │ ├── arbitraryVariants/
│ │ │ ├── arbitraryVariants.tsx
│ │ │ └── config.json
│ │ ├── autoCssProp/
│ │ │ └── autoCssProp.tsx
│ │ ├── colorFunctions/
│ │ │ ├── colorFunctions.tsx
│ │ │ └── tailwind.config.js
│ │ ├── comments/
│ │ │ ├── comments.tsx
│ │ │ └── config.json
│ │ ├── config/
│ │ │ ├── config.tsx
│ │ │ └── tailwind.config.js
│ │ ├── configTS/
│ │ │ ├── configTS.tsx
│ │ │ └── tailwind.config.ts
│ │ ├── content/
│ │ │ ├── content.tsx
│ │ │ └── tailwind.config.js
│ │ ├── cssPropEmotion/
│ │ │ ├── autoCssProp.tsx
│ │ │ └── autoCssPropWithStyled.tsx
│ │ ├── cssPropStyledComponents/
│ │ │ ├── autoCssProp.tsx
│ │ │ ├── autoCssPropWithStyled.tsx
│ │ │ └── config.json
│ │ ├── darkLightModeArray/
│ │ │ ├── darkLightModeArray.tsx
│ │ │ └── tailwind.config.js
│ │ ├── directionalBorders/
│ │ │ └── directionalBorders.tsx
│ │ ├── fluidContainer/
│ │ │ ├── fluidContainer.tsx
│ │ │ └── tailwind.config.js
│ │ ├── globalStyles/
│ │ │ ├── config.json
│ │ │ ├── globalStyles.tsx
│ │ │ └── tailwind.config.js
│ │ ├── group/
│ │ │ └── group.tsx
│ │ ├── includeClassNames/
│ │ │ ├── config.json
│ │ │ └── includeClassNames.tsx
│ │ ├── lineClamp/
│ │ │ └── lineClamp.tsx
│ │ ├── negative/
│ │ │ ├── negative.tsx
│ │ │ └── tailwind.config.js
│ │ ├── peers/
│ │ │ └── peers.tsx
│ │ ├── pluginAspectRatio/
│ │ │ ├── pluginAspectRatio.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginDaisyUi/
│ │ │ ├── pluginDaisyUi.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginExamples/
│ │ │ ├── pluginExamples.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginForms/
│ │ │ ├── pluginForms.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginFormsClassStrategy/
│ │ │ ├── pluginTypography.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginGapFallback/
│ │ │ ├── pluginGapFallback.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginTypography/
│ │ │ ├── pluginTypography.tsx
│ │ │ └── tailwind.config.js
│ │ ├── pluginUserParentSelector/
│ │ │ ├── pluginUserParentSelector.tsx
│ │ │ └── tailwind.config.js
│ │ ├── plugins/
│ │ │ ├── config.json
│ │ │ ├── plugins.tsx
│ │ │ └── tailwind.config.js
│ │ ├── prefix/
│ │ │ ├── config.json
│ │ │ ├── prefix.tsx
│ │ │ └── tailwind.config.js
│ │ ├── preflight/
│ │ │ ├── preflight.tsx
│ │ │ └── tailwind.config.js
│ │ ├── presets/
│ │ │ ├── presets.tsx
│ │ │ └── tailwind.config.js
│ │ ├── sassyPseudo/
│ │ │ ├── config.json
│ │ │ ├── sassyPseudo.tsx
│ │ │ └── tailwind.config.js
│ │ ├── screenImport/
│ │ │ ├── screenImport.tsx
│ │ │ └── tailwind.config.js
│ │ ├── screens/
│ │ │ ├── screens.tsx
│ │ │ └── tailwind.config.js
│ │ ├── separator/
│ │ │ ├── separator.tsx
│ │ │ └── tailwind.config.js
│ │ ├── shortCss/
│ │ │ ├── config.json
│ │ │ └── shortCss.tsx
│ │ ├── stitches/
│ │ │ ├── config.json
│ │ │ ├── stitches.config.js
│ │ │ ├── stitchesDotSyntax.tsx
│ │ │ ├── stitchesGlobals.tsx
│ │ │ ├── stitchesImports.tsx
│ │ │ └── stitchesProps.tsx
│ │ ├── themeValuesToString/
│ │ │ ├── tailwind.config.js
│ │ │ └── themeValuesToString.tsx
│ │ ├── userPluginOrdering/
│ │ │ ├── tailwind.config.js
│ │ │ └── userPluginOrdering.tsx
│ │ ├── utilitiesAccessibility/
│ │ │ └── screenReaders.tsx
│ │ ├── utilitiesBackgrounds/
│ │ │ ├── backgroundAttachment.tsx
│ │ │ ├── backgroundClip.tsx
│ │ │ ├── backgroundColor.tsx
│ │ │ ├── backgroundImage.tsx
│ │ │ ├── backgroundOpacity.tsx
│ │ │ ├── backgroundOrigin.tsx
│ │ │ ├── backgroundPosition.tsx
│ │ │ ├── backgroundRepeat.tsx
│ │ │ ├── backgroundSize.tsx
│ │ │ ├── gradientColorStops.tsx
│ │ │ └── tailwind.config.js
│ │ ├── utilitiesBorders/
│ │ │ ├── borderColor.tsx
│ │ │ ├── borderOpacity.tsx
│ │ │ ├── borderRadius.tsx
│ │ │ ├── borderStyle.tsx
│ │ │ ├── borderWidth.tsx
│ │ │ ├── divideColor.tsx
│ │ │ ├── divideOpacity.tsx
│ │ │ ├── divideStyle.tsx
│ │ │ ├── divideWidth.tsx
│ │ │ ├── outlineColor.tsx
│ │ │ ├── outlineOffset.tsx
│ │ │ ├── outlineStyle.tsx
│ │ │ ├── outlineWidth.tsx
│ │ │ ├── ringColor.tsx
│ │ │ ├── ringMisc.tsx
│ │ │ ├── ringOffsetColor.tsx
│ │ │ ├── ringOffsetWidth.tsx
│ │ │ ├── ringOpacity.tsx
│ │ │ ├── ringWidth.tsx
│ │ │ └── tailwind.config.js
│ │ ├── utilitiesEffects/
│ │ │ ├── backgroundBlendMode.tsx
│ │ │ ├── boxShadow.tsx
│ │ │ ├── boxShadowColor.tsx
│ │ │ ├── mixBlendMode.tsx
│ │ │ └── opacity.tsx
│ │ ├── utilitiesFilters/
│ │ │ ├── backdropBlur.tsx
│ │ │ ├── backdropBrightness.tsx
│ │ │ ├── backdropContrast.tsx
│ │ │ ├── backdropGrayscale.tsx
│ │ │ ├── backdropHueRotate.tsx
│ │ │ ├── backdropInvert.tsx
│ │ │ ├── backdropOpacity.tsx
│ │ │ ├── backdropSaturate.tsx
│ │ │ ├── backdropSepia.tsx
│ │ │ ├── blur.tsx
│ │ │ ├── brightness.tsx
│ │ │ ├── contrast.tsx
│ │ │ ├── dropShadow.tsx
│ │ │ ├── grayscale.tsx
│ │ │ ├── hueRotate.tsx
│ │ │ ├── invert.tsx
│ │ │ ├── misc.tsx
│ │ │ ├── saturate.tsx
│ │ │ └── sepia.tsx
│ │ ├── utilitiesLayout/
│ │ │ ├── aspectRatio.tsx
│ │ │ ├── boxDecorationBreak.tsx
│ │ │ ├── boxSizing.tsx
│ │ │ ├── breakAfter.tsx
│ │ │ ├── breakBefore.tsx
│ │ │ ├── breakInside.tsx
│ │ │ ├── clear.tsx
│ │ │ ├── columns.tsx
│ │ │ ├── container.tsx
│ │ │ ├── display.tsx
│ │ │ ├── float.tsx
│ │ │ ├── isolation.tsx
│ │ │ ├── objectFit.tsx
│ │ │ ├── objectPosition.tsx
│ │ │ ├── overflow.tsx
│ │ │ ├── overscrollBehavior.tsx
│ │ │ ├── position.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── topRightBottomLeft.tsx
│ │ │ ├── visibility.tsx
│ │ │ └── zIndex.tsx
│ │ ├── utilitiesSpacing/
│ │ │ ├── margin.tsx
│ │ │ ├── padding.tsx
│ │ │ └── spaceBetween.tsx
│ │ ├── utilitiesSvg/
│ │ │ ├── fill.tsx
│ │ │ ├── stroke.tsx
│ │ │ ├── strokeWidth.tsx
│ │ │ └── tailwind.config.js
│ │ ├── utilitiesTransforms/
│ │ │ ├── misc.tsx
│ │ │ ├── rotate.tsx
│ │ │ ├── scale.tsx
│ │ │ ├── skew.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── transformOrigin.tsx
│ │ │ └── translate.tsx
│ │ ├── utilitiesTransitionsAnimation/
│ │ │ ├── animation.tsx
│ │ │ ├── misc.tsx
│ │ │ ├── transitionDelay.tsx
│ │ │ ├── transitionDuration.tsx
│ │ │ ├── transitionProperty.tsx
│ │ │ └── transitionTimingFunction.tsx
│ │ ├── utiltiesFlexboxGrid/
│ │ │ ├── alignContent.tsx
│ │ │ ├── alignItems.tsx
│ │ │ ├── alignSelf.tsx
│ │ │ ├── flex.tsx
│ │ │ ├── flexBasis.tsx
│ │ │ ├── flexDirection.tsx
│ │ │ ├── flexGrow.tsx
│ │ │ ├── flexShrink.tsx
│ │ │ ├── flexWrap.tsx
│ │ │ ├── gap.tsx
│ │ │ ├── gridAutoColumns.tsx
│ │ │ ├── gridAutoFlow.tsx
│ │ │ ├── gridAutoRows.tsx
│ │ │ ├── gridColumn.tsx
│ │ │ ├── gridRow.tsx
│ │ │ ├── gridTemplateColumns.tsx
│ │ │ ├── gridTemplateRows.tsx
│ │ │ ├── justifyContent.tsx
│ │ │ ├── justifyItems.tsx
│ │ │ ├── justifySelf.tsx
│ │ │ ├── misc.tsx
│ │ │ ├── order.tsx
│ │ │ ├── placeContent.tsx
│ │ │ ├── placeItems.tsx
│ │ │ └── placeSelf.tsx
│ │ ├── utiltiesInteractivity/
│ │ │ ├── accentColor.tsx
│ │ │ ├── appearance.tsx
│ │ │ ├── caretColor.tsx
│ │ │ ├── cursor.tsx
│ │ │ ├── pointerEvents.tsx
│ │ │ ├── resize.tsx
│ │ │ ├── scrollBehavior.tsx
│ │ │ ├── scrollMargin.tsx
│ │ │ ├── scrollPadding.tsx
│ │ │ ├── scrollSnapAlign.tsx
│ │ │ ├── scrollSnapStop.tsx
│ │ │ ├── scrollSnapType.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── touchAction.tsx
│ │ │ ├── userSelect.tsx
│ │ │ └── willChange.tsx
│ │ ├── utiltiesSizing/
│ │ │ ├── height.tsx
│ │ │ ├── maxHeight.tsx
│ │ │ ├── maxWidth.tsx
│ │ │ ├── minHeight.tsx
│ │ │ ├── minWidth.tsx
│ │ │ └── width.tsx
│ │ ├── utiltiesTables/
│ │ │ ├── borderCollapse.tsx
│ │ │ ├── borderSpacing.tsx
│ │ │ ├── captionSide.tsx
│ │ │ └── tableLayout.tsx
│ │ ├── utiltiesTypography/
│ │ │ ├── fontFamily.tsx
│ │ │ ├── fontSize.tsx
│ │ │ ├── fontSmoothing.tsx
│ │ │ ├── fontStyle.tsx
│ │ │ ├── fontVariantNumeric.tsx
│ │ │ ├── fontWeight.tsx
│ │ │ ├── hyphens.tsx
│ │ │ ├── letterSpacing.tsx
│ │ │ ├── lineHeight.tsx
│ │ │ ├── listStyleImage.tsx
│ │ │ ├── listStylePosition.tsx
│ │ │ ├── listStyleType.tsx
│ │ │ ├── placeholderColor.tsx
│ │ │ ├── placeholderOpacity.tsx
│ │ │ ├── tailwind.config.js
│ │ │ ├── textAlign.tsx
│ │ │ ├── textColor.tsx
│ │ │ ├── textDecoration.tsx
│ │ │ ├── textDecorationColor.tsx
│ │ │ ├── textDecorationStyle.tsx
│ │ │ ├── textDecorationThickness.tsx
│ │ │ ├── textIndent.tsx
│ │ │ ├── textOpacity.tsx
│ │ │ ├── textOverflow.tsx
│ │ │ ├── textTransform.tsx
│ │ │ ├── textUnderlineOffset.tsx
│ │ │ ├── verticalAlign.tsx
│ │ │ ├── whitespace.tsx
│ │ │ └── wordBreak.tsx
│ │ ├── variables/
│ │ │ ├── tailwind.config.js
│ │ │ └── variables.tsx
│ │ ├── variantOrdering/
│ │ │ └── variantOrdering.tsx
│ │ └── visitedOpacity/
│ │ └── visitedOpacity.tsx
│ ├── __snapshots__/
│ │ └── plugin.test.js.snap
│ ├── animations.test.ts
│ ├── arbitraryProperties.test.ts
│ ├── arbitraryValues.test.ts
│ ├── arbitraryVariants.test.ts
│ ├── config.test.ts
│ ├── containerQueries.test.ts
│ ├── dividers.test.ts
│ ├── escaping.test.ts
│ ├── fontSize.test.ts
│ ├── minMaxScreenVariants.test.ts
│ ├── plugin.test.js
│ ├── presetEmotion.test.ts
│ ├── presetGoober.test.ts
│ ├── presetSolid.test.ts
│ ├── presetStitches.test.ts
│ ├── presetStyledComponents.test.ts
│ ├── screens.test.ts
│ ├── stitches.config.js
│ ├── types/
│ │ ├── index.ts
│ │ └── types.d.ts
│ └── util/
│ ├── customMatchers.ts
│ └── run.ts
├── tsconfig.json
└── types/
├── index.d.ts
├── macro.d.ts
├── tests/
│ ├── __fixtures__/
│ │ ├── config/
│ │ │ └── tailwind.config.d.ts
│ │ └── configTS/
│ │ └── tailwind.config.d.ts
│ ├── basic/
│ │ ├── index.tsx
│ │ ├── noDefaultImport.tsx
│ │ └── tsconfig.json
│ ├── emotion/
│ │ ├── index.tsx
│ │ └── tsconfig.json
│ └── styled-components/
│ ├── index.tsx
│ └── tsconfig.json
├── tsconfig.base.json
└── tsconfig.json
SYMBOL INDEX (285 symbols across 60 files)
FILE: src/core/constants.ts
constant CLASS_SEPARATOR (line 1) | const CLASS_SEPARATOR = /\S+/g
constant DEFAULTS_UNIVERSAL (line 2) | const DEFAULTS_UNIVERSAL = '*, ::before, ::after'
constant EMPTY_CSS_VARIABLE_VALUE (line 3) | const EMPTY_CSS_VARIABLE_VALUE = 'var(--tw-empty,/*!*/ /*!*/)'
constant PRESERVED_ATRULE_TYPES (line 4) | const PRESERVED_ATRULE_TYPES = new Set([
constant LAYER_DEFAULTS (line 14) | const LAYER_DEFAULTS = 'defaults'
constant LINEFEED (line 15) | const LINEFEED = /\n/g
constant WORD_CHARACTER (line 16) | const WORD_CHARACTER = /\w/
constant SPACE_ID (line 17) | const SPACE_ID = '_'
constant SPACE (line 18) | const SPACE = /\s/
constant SPACES (line 19) | const SPACES = /\s+/g
FILE: src/core/createCoreContext.ts
function packageCheck (line 19) | function packageCheck(
type GetPackageConfig (line 34) | type GetPackageConfig = {
function getPackageUsed (line 40) | function getPackageUsed(params: GetPackageConfig): GetPackageUsed {
type ConfigParameters (line 50) | type ConfigParameters = {
function getStyledConfig (line 57) | function getStyledConfig({
function getCssConfig (line 82) | function getCssConfig({
function getGlobalConfig (line 107) | function getGlobalConfig(config: TwinConfig): PresetItem {
function createCoreContext (line 115) | function createCoreContext(params: CreateCoreContext): CoreContext {
FILE: src/core/extractRuleStyles.ts
constant ESC_COMMA (line 18) | const ESC_COMMA = /\\2c/g
constant ESC_DIGIT (line 19) | const ESC_DIGIT = /\\3(\d)/g
constant UNDERSCORE_ESCAPING (line 20) | const UNDERSCORE_ESCAPING = /\\+(_)/g
constant SLASH_DOT_ESCAPING (line 21) | const SLASH_DOT_ESCAPING = /\\\./g
constant BACKSLASH_ESCAPING (line 22) | const BACKSLASH_ESCAPING = /\\\\/g
function transformImportant (line 24) | function transformImportant(value: string, params: TransformDecl): string {
function transformEscaping (line 35) | function transformEscaping(value: string): string {
function transformDeclValue (line 53) | function transformDeclValue(value: string, params: TransformDecl): string {
function extractFromRule (line 69) | function extractFromRule(
function extractSelectorFromAtRule (line 82) | function extractSelectorFromAtRule(
method decl (line 107) | decl(decl: P.Declaration, params: ExtractRuleStyles): CssObject | undefi...
method rule (line 134) | rule(rule: P.Rule, params: ExtractRuleStyles): CssObject | undefined {
method atrule (line 201) | atrule(atrule: P.AtRule, params: ExtractRuleStyles): CssObject | undefin...
type Styles (line 267) | type Styles = CssObject | undefined
function extractRuleStyles (line 269) | function extractRuleStyles(nodes: P.Node[], params: ExtractRuleStyles): ...
FILE: src/core/getGlobalStyles.ts
function getGlobalStyles (line 6) | function getGlobalStyles(params: CoreContext): CssObject | undefined {
function globalKeyframeStyles (line 48) | function globalKeyframeStyles(
FILE: src/core/getStyles.ts
constant IMPORTANT_OUTSIDE_BRACKETS (line 20) | const IMPORTANT_OUTSIDE_BRACKETS =
constant COMMENTS_MULTI_LINE (line 22) | const COMMENTS_MULTI_LINE = /(?<!\/)\/(?!\/)\*[\S\s]*?\*\//g
constant COMMENTS_SINGLE_LINE (line 23) | const COMMENTS_SINGLE_LINE = /(?<!:)\/\/.*/g
constant CLASS_DIVIDER_PIPE (line 24) | const CLASS_DIVIDER_PIPE = / \| /g
constant ALL_BRACKET_SQUARE_LEFT (line 25) | const ALL_BRACKET_SQUARE_LEFT = /\[/g
constant ALL_BRACKET_SQUARE_RIGHT (line 26) | const ALL_BRACKET_SQUARE_RIGHT = /]/g
constant ALL_BRACKET_ROUND_LEFT (line 27) | const ALL_BRACKET_ROUND_LEFT = /\(/g
constant ALL_BRACKET_ROUND_RIGHT (line 28) | const ALL_BRACKET_ROUND_RIGHT = /\)/g
constant ESCAPE_CHARACTERS (line 29) | const ESCAPE_CHARACTERS = /\n|\t/g
function getStylesFromMatches (line 31) | function getStylesFromMatches(
function multilineReplaceWith (line 57) | function multilineReplaceWith(
function validateClasses (line 71) | function validateClasses(
function sortBigSign (line 133) | function sortBigSign(bigIntValue: bigint): number {
function getOrderedClassList (line 137) | function getOrderedClassList(
function getStyles (line 174) | function getStyles(
FILE: src/core/lib/configHelpers.ts
type Validator (line 19) | type Validator = [(value: unknown) => boolean, string]
type GetTailwindConfig (line 21) | type GetTailwindConfig = {
function getTailwindConfig (line 28) | function getTailwindConfig({
function runConfigValidator (line 87) | function runConfigValidator([item, value]: [
function getConfigTwin (line 105) | function getConfigTwin(
function getConfigTwinValidated (line 116) | function getConfigTwinValidated(
FILE: src/core/lib/convertClassName.ts
constant ALL_COMMAS (line 9) | const ALL_COMMAS = /,/g
constant ALL_AMPERSANDS (line 10) | const ALL_AMPERSANDS = /&/g
constant ENDING_AMP_THEN_WHITESPACE (line 11) | const ENDING_AMP_THEN_WHITESPACE = /&[\s_]*$/
constant ALL_CLASS_DOTS (line 12) | const ALL_CLASS_DOTS = /(?<!\\)(\.)(?=\w)/g
constant ALL_CLASS_ATS (line 13) | const ALL_CLASS_ATS = /(?<!\\)(@)(?=\w)(?!media)/g
constant ALL_WRAPPABLE_PARENT_SELECTORS (line 14) | const ALL_WRAPPABLE_PARENT_SELECTORS = /&(?=([^ $)*+,.:>[_~])[\w-])/g
constant BASIC_SELECTOR_TYPES (line 15) | const BASIC_SELECTOR_TYPES = /^#|^\\.|[^\W_]/
type ConvertShortCssToArbitraryPropertyParameters (line 17) | type ConvertShortCssToArbitraryPropertyParameters = {
function convertShortCssToArbitraryProperty (line 22) | function convertShortCssToArbitraryProperty(
type ConvertClassNameParameters (line 75) | type ConvertClassNameParameters = {
function checkForVariantSupport (line 82) | function checkForVariantSupport({
function convertClassName (line 116) | function convertClassName(
function isArbitraryVariant (line 173) | function isArbitraryVariant(variant: string): boolean {
function unbracket (line 177) | function unbracket(variant: string): string {
function sassifyArbitraryVariants (line 181) | function sassifyArbitraryVariants(
function addParentSelector (line 281) | function addParentSelector(
FILE: src/core/lib/createAssert.ts
function createAssert (line 4) | function createAssert(
FILE: src/core/lib/createTheme.ts
function createTheme (line 6) | function createTheme(
function sassifyValues (line 51) | function sassifyValues(
FILE: src/core/lib/defaultTailwindConfig.ts
constant AMPERSAND_AFTER (line 4) | const AMPERSAND_AFTER = /&(.+)/g
constant AMPERSAND (line 5) | const AMPERSAND = /&/g
function stripAmpersands (line 7) | function stripAmpersands(string: string): string {
constant EXTRA_VARIANTS (line 13) | const EXTRA_VARIANTS = [
constant EXTRA_NOT_VARIANTS (line 25) | const EXTRA_NOT_VARIANTS = [
function defaultVariants (line 67) | function defaultVariants({ config, addVariant }: PluginAPI): void {
FILE: src/core/lib/expandVariantGroups.ts
constant BRACKETED (line 4) | const BRACKETED = /^\(.*?\)$/
constant BRACKETED_MAYBE_IMPORTANT (line 5) | const BRACKETED_MAYBE_IMPORTANT = /\)!?$/
constant ESCAPE_CHARACTERS (line 6) | const ESCAPE_CHARACTERS = /\n|\t/g
type Context (line 8) | type Context = {
function spreadVariantGroups (line 16) | function spreadVariantGroups(classes: string, context: Context): string[] {
function expandVariantGroups (line 87) | function expandVariantGroups(classes: string, context: Context): string {
FILE: src/core/lib/getStitchesPath.ts
function getFirstValue (line 7) | function getFirstValue<ListItem>(
function checkExists (line 25) | function checkExists(
function getRelativePath (line 36) | function getRelativePath(comparePath: string, filename: string): string {
function getStitchesPath (line 41) | function getStitchesPath({
FILE: src/core/lib/logging.ts
function makeColor (line 18) | function makeColor(hasColor: boolean): MakeColor {
function spaced (line 25) | function spaced(string: string): string {
function warning (line 29) | function warning(string: string): string {
function logGeneralError (line 33) | function logGeneralError(error: string | [ColorValue, string]): string {
function createDebug (line 43) | function createDebug(isDev: boolean, twinConfig: TwinConfigAll) {
FILE: src/core/lib/twinConfig.ts
constant TWIN_CONFIG_DEFAULTS (line 3) | const TWIN_CONFIG_DEFAULTS = {
function configDefaultsTwin (line 53) | function configDefaultsTwin({
function isBoolean (line 71) | function isBoolean(value: unknown): boolean {
type ConfigTwinValidators (line 83) | type ConfigTwinValidators = Record<
FILE: src/core/lib/util/camelize.ts
constant CAMEL_FIND (line 1) | const CAMEL_FIND = /\W+(.)/g
function camelize (line 3) | function camelize(string: string): string {
FILE: src/core/lib/util/escapeRegex.ts
constant REGEX_SPECIAL_CHARACTERS (line 1) | const REGEX_SPECIAL_CHARACTERS = /[$()*+./?[\\\]^{|}-]/g
function escapeRegex (line 3) | function escapeRegex(string: string): string {
FILE: src/core/lib/util/formatProp.ts
constant EXTRA_WHITESPACE (line 4) | const EXTRA_WHITESPACE = /\s\s+/g
function formatProp (line 6) | function formatProp(classes: string): string {
FILE: src/core/lib/util/isEmpty.ts
function isEmpty (line 1) | function isEmpty(value: unknown): boolean {
FILE: src/core/lib/util/isObject.ts
function isObject (line 1) | function isObject(
FILE: src/core/lib/util/isShortCss.ts
function isShortCss (line 4) | function isShortCss(
FILE: src/core/lib/util/replaceThemeValue.ts
constant MATCH_THEME (line 3) | const MATCH_THEME = /theme\((.+?)\)/
constant MATCH_QUOTES (line 4) | const MATCH_QUOTES = /["'`]/g
function replaceThemeValue (line 6) | function replaceThemeValue(
FILE: src/core/lib/util/sassifySelector.ts
constant SELECTOR_PARENT_CANDIDATE (line 3) | const SELECTOR_PARENT_CANDIDATE = /^[ #.[]/
constant SELECTOR_SPECIAL_STARTS (line 4) | const SELECTOR_SPECIAL_STARTS = /^ [>@]/
constant SELECTOR_ROOT (line 5) | const SELECTOR_ROOT = /(^| ):root(?!\w)/g
constant UNDERSCORE_ESCAPING (line 6) | const UNDERSCORE_ESCAPING = /\\+(_)/g
constant WRAPPED_PARENT_SELECTORS (line 7) | const WRAPPED_PARENT_SELECTORS = /(\({3}&(.*?)\){3})/g
type OptionalSassifyContext (line 9) | type OptionalSassifyContext = {
type SassifySelectorTasks (line 15) | type SassifySelectorTasks = Array<
function sassifySelector (line 80) | function sassifySelector(
FILE: src/core/lib/util/splitOnFirst.ts
function splitOnFirst (line 2) | function splitOnFirst(input: string, delim: string): string[] {
FILE: src/core/lib/util/toArray.ts
function toArray (line 1) | function toArray<T>(array: T): T | [T] {
FILE: src/core/types/index.ts
type KeyValuePair (line 8) | type KeyValuePair<K extends keyof never = string, V = string> = Record<K...
type RecursiveKeyValuePair (line 11) | interface RecursiveKeyValuePair<K extends keyof never = string, V = stri...
type CssObject (line 14) | type CssObject = RecursiveKeyValuePair<string, string | string[]>
type Partial (line 17) | type Partial<T> = {
type ColorValue (line 21) | type ColorValue = (c: typeof colors) => string
type ColorType (line 23) | type ColorType = keyof typeof colors
type MakeColor (line 25) | type MakeColor = (message: string, type?: keyof typeof colors) => string
type PresetItem (line 27) | type PresetItem = { import: string; from: string }
type PresetConfig (line 29) | type PresetConfig = {
type TwinConfigAll (line 35) | type TwinConfigAll = {
type Candidate (line 56) | type Candidate = [
type TailwindContext (line 61) | type TailwindContext = {
type AssertContext (line 69) | type AssertContext = {
type Assert (line 73) | type Assert = (
type CoreContext (line 78) | type CoreContext = {
type ExtractRuleStyles (line 97) | type ExtractRuleStyles = {
type TransformDecl (line 116) | type TransformDecl = {
type CreateCoreContext (line 121) | type CreateCoreContext = {
type PossiblePresets (line 130) | type PossiblePresets = keyof typeof userPresets
type GetPackageUsed (line 132) | type GetPackageUsed = {
type TailwindMatchOptions (line 140) | type TailwindMatchOptions = {
type TailwindMatch (line 147) | type TailwindMatch = [
type GetConfigTwinValidatedParameters (line 152) | type GetConfigTwinValidatedParameters = GetPackageUsed & {
type TwinConfig (line 156) | type TwinConfig = Partial<TwinConfigAll>
FILE: src/macro/className.ts
function makeJsxAttribute (line 13) | function makeJsxAttribute(
function handleClassNameProperty (line 20) | function handleClassNameProperty({
FILE: src/macro/css.ts
function updateCssReferences (line 10) | function updateCssReferences({
function addCssImport (line 25) | function addCssImport({
function convertHtmlElementToStyled (line 50) | function convertHtmlElementToStyled(
FILE: src/macro/dataProp.ts
constant SPACE_ID (line 3) | const SPACE_ID = '_'
constant EXTRA_WHITESPACE (line 4) | const EXTRA_WHITESPACE = /\s\s+/g
constant LINEFEED (line 5) | const LINEFEED = /\n/g
function formatProp (line 7) | function formatProp(classes: string): string {
function addDataTwPropToPath (line 20) | function addDataTwPropToPath({
function addDataPropToExistingPath (line 53) | function addDataPropToExistingPath({
FILE: src/macro/globalStyles.ts
constant KEBAB_CANDIDATES (line 18) | const KEBAB_CANDIDATES = /([\da-z]|(?=[A-Z]))([A-Z])/g
type AddGlobalStylesImport (line 20) | type AddGlobalStylesImport = {
function addGlobalStylesImport (line 27) | function addGlobalStylesImport({
type DeclarationParameters (line 42) | type DeclarationParameters = {
function getGlobalDeclarationTte (line 50) | function getGlobalDeclarationTte({
function getGlobalDeclarationProperty (line 64) | function getGlobalDeclarationProperty(
function kebabize (line 100) | function kebabize(string: string): string {
function convert (line 104) | function convert(k: string, v: string | number): string {
function convertCssObjectToString (line 112) | function convertCssObjectToString(
function handleGlobalStylesFunction (line 121) | function handleGlobalStylesFunction(params: AdditionalHandlerParameters)...
function handleGlobalStylesVariable (line 127) | function handleGlobalStylesVariable(params: AdditionalHandlerParameters)...
function handleGlobalStylesJsx (line 143) | function handleGlobalStylesJsx(params: AdditionalHandlerParameters): void {
FILE: src/macro/lib/astHelpers.ts
function addImport (line 10) | function addImport({
function astify (line 38) | function astify(
function objectExpressionElements (line 85) | function objectExpressionElements(
function setStyledIdentifier (line 97) | function setStyledIdentifier({
function setCssIdentifier (line 141) | function setCssIdentifier({
function getStringFromTTE (line 181) | function getStringFromTTE(path: NodePath<T.TaggedTemplateExpression>): s...
function parseTte (line 208) | function parseTte(
function replaceWithLocation (line 261) | function replaceWithLocation<EmptyArray>(
function generateUid (line 276) | function generateUid(name: string, program: NodePath<T.Program>): T.Iden...
function getParentJSX (line 280) | function getParentJSX(path: NodePath): NodePath<T.JSXOpeningElement> {
function getAttributeNames (line 286) | function getAttributeNames(jsxPath: NodePath): string[] {
function getCssAttributeData (line 294) | function getCssAttributeData<NodeType extends NodePath>(
function getFunctionValue (line 313) | function getFunctionValue(
function getTaggedTemplateValue (line 330) | function getTaggedTemplateValue<Path extends NodePath>(
function getMemberExpression (line 345) | function getMemberExpression(
function generateTaggedTemplateExpression (line 362) | function generateTaggedTemplateExpression({
function isComponent (line 382) | function isComponent(name: string): boolean {
function getFirstStyledArgument (line 388) | function getFirstStyledArgument(
type MakeStyledComponent (line 414) | type MakeStyledComponent = {
type CreateStyledProps (line 424) | type CreateStyledProps = Pick<
function createStyledPropsForTw (line 433) | function createStyledPropsForTw({
function createStyledPropsForCss (line 448) | function createStyledPropsForCss(
function makeStyledComponent (line 472) | function makeStyledComponent({
function getJsxAttributes (line 526) | function getJsxAttributes(
FILE: src/macro/lib/util/isEmpty.ts
function isEmpty (line 1) | function isEmpty(value: unknown): boolean {
FILE: src/macro/lib/validateImports.ts
function validateImports (line 16) | function validateImports(
FILE: src/macro/screen.ts
type GetDirectReplacement (line 15) | type GetDirectReplacement = Pick<
function getDirectReplacement (line 19) | function getDirectReplacement({
type ScreenValues (line 30) | type ScreenValues =
type GetMediaQuery (line 35) | type GetMediaQuery = {
type Expression (line 41) | type Expression = {
type HandleDefinition (line 46) | type HandleDefinition = {
function handleDefinition (line 53) | function handleDefinition({
function getMediaQuery (line 109) | function getMediaQuery({ input, screens, assert }: GetMediaQuery): string {
function handleScreenFunction (line 159) | function handleScreenFunction({
FILE: src/macro/shortCss.ts
function handleCsProperty (line 14) | function handleCsProperty({
FILE: src/macro/styled.ts
function updateStyledReferences (line 6) | function updateStyledReferences({
function addStyledImport (line 21) | function addStyledImport({
function moveDotElement (line 45) | function moveDotElement({
function handleStyledFunction (line 85) | function handleStyledFunction({
FILE: src/macro/theme.ts
function handleThemeFunction (line 10) | function handleThemeFunction({
FILE: src/macro/tw.ts
type MoveTwPropToStyled (line 25) | type MoveTwPropToStyled = {
function moveTwPropToStyled (line 34) | function moveTwPropToStyled(params: MoveTwPropToStyled): void {
type MergeIntoCssAttribute (line 49) | type MergeIntoCssAttribute = {
function mergeIntoCssAttribute (line 56) | function mergeIntoCssAttribute({
function handleTwProperty (line 129) | function handleTwProperty({
function handleTwFunction (line 225) | function handleTwFunction({
FILE: src/macro/twin.ts
function twinMacro (line 43) | function twinMacro(params: MacroParams): void {
FILE: src/macro/types/index.ts
type Identifiers (line 6) | type Identifiers = {
type StateBase (line 11) | type StateBase = {
type State (line 25) | type State = StateBase & Identifiers
type HandlerParameters (line 27) | type HandlerParameters = {
type AddDataPropToExistingPath (line 34) | type AddDataPropToExistingPath = {
type JSXAttributeHandler (line 41) | type JSXAttributeHandler = HandlerParameters & {
type ImportDeclarationHandler (line 44) | type ImportDeclarationHandler = HandlerParameters & {
type AdditionalHandlerParameters (line 48) | type AdditionalHandlerParameters = {
FILE: src/suggestions/index.ts
constant ALL_SPACE_IDS (line 20) | const ALL_SPACE_IDS = /{{SPACE}}/g
constant OPTION_DEFAULTS (line 22) | const OPTION_DEFAULTS = {
function getVariantSuggestions (line 30) | function getVariantSuggestions(
function getClassError (line 58) | function getClassError(rawClass: string, context: ClassErrorContext): st...
type ErrorContext (line 86) | type ErrorContext = {
function createErrorContext (line 94) | function createErrorContext(
function getSuggestions (line 109) | function getSuggestions(classList: string[], options: Options): void {
FILE: src/suggestions/lib/extractors.ts
function extractClassCandidates (line 3) | function extractClassCandidates(
function extractVariantCandidates (line 15) | function extractVariantCandidates(
FILE: src/suggestions/lib/getClassSuggestions.ts
constant RATING_MINIMUM (line 4) | const RATING_MINIMUM = 0.2
type RateCandidate (line 6) | type RateCandidate = [number, string, string]
function rateCandidate (line 8) | function rateCandidate(
function extractCandidates (line 31) | function extractCandidates(
function getClassSuggestions (line 58) | function getClassSuggestions(
FILE: src/suggestions/lib/getPackageVersions.ts
type JSONPrimitive (line 1) | type JSONPrimitive = string | number | boolean | undefined
type JSONValue (line 2) | type JSONValue = JSONPrimitive | JSONObject
type JSONObject (line 5) | interface JSONObject extends Record<string, JSONValue> {}
function getPackageVersions (line 6) | function getPackageVersions(): Record<string, string> {
FILE: src/suggestions/lib/makeColor.ts
function makeColor (line 4) | function makeColor(hasColor: boolean): MakeColor {
FILE: src/suggestions/lib/validateVariants.ts
function validateVariants (line 4) | function validateVariants(
FILE: src/suggestions/types/index.ts
type Options (line 8) | type Options = {
type ClassErrorContext (line 17) | type ClassErrorContext = {
type MakeColor (line 26) | type MakeColor = (message: string, type?: keyof typeof colors) => string
FILE: tests/@applyInPlugins.test.ts
function tw (line 6) | function tw(...classes: string[]): Record<string, {}> {
FILE: tests/__fixtures__/addBase/tailwind.config.js
function addBasePlugin (line 1) | function addBasePlugin({ addBase }) {
FILE: tests/__fixtures__/fluidContainer/tailwind.config.js
function fluidContainer (line 1) | function fluidContainer({ addComponents, theme }) {
FILE: tests/__fixtures__/pluginExamples/tailwind.config.js
method sort (line 107) | sort(a, z) {
FILE: tests/__fixtures__/plugins/tailwind.config.js
function addUtilitiesTest (line 19) | function addUtilitiesTest({ addUtilities, theme }) {
function addUtilitiesTest2 (line 30) | function addUtilitiesTest2({ addUtilities }) {
function addComponentsTest (line 43) | function addComponentsTest({ addComponents }) {
function addComponentsTestElementPrefixes (line 69) | function addComponentsTestElementPrefixes({ addComponents }) {
function addComponentsTestElementScreenReplacements (line 93) | function addComponentsTestElementScreenReplacements({ addComponents }) {
function pluginBaseSelectors (line 122) | function pluginBaseSelectors({ addComponents, theme, e }) {
FILE: tests/__fixtures__/prefix/tailwind.config.js
function pluginClass (line 3) | function pluginClass({ addComponents }) {
FILE: tests/__fixtures__/stitches/stitchesGlobals.tsx
function App (line 7) | function App() {
FILE: tests/__fixtures__/userPluginOrdering/tailwind.config.js
function fluidContainer (line 1) | function fluidContainer({ addComponents, theme }) {
FILE: tests/__fixtures__/variables/tailwind.config.js
function addComponentsTestCssVariableAsRuleProperty (line 12) | function addComponentsTestCssVariableAsRuleProperty({ addComponents }) {
FILE: tests/types/index.ts
type TailwindConfig (line 4) | type TailwindConfig = Partial<Config>
type TwinConfig (line 5) | type TwinConfig = Partial<TwinConfigAll>
FILE: tests/types/types.d.ts
type Matchers (line 5) | interface Matchers<R> {
FILE: tests/util/customMatchers.ts
function formatJavascript (line 4) | function formatJavascript(input: string): string {
function formatError (line 11) | function formatError(input: string): string {
method toMatchFormattedError (line 20) | toMatchFormattedError(
method toMatchFormattedJavaScript (line 71) | toMatchFormattedJavaScript(
FILE: tests/util/run.ts
function run (line 9) | async function run(
function babelTransform (line 17) | async function babelTransform(
FILE: types/index.d.ts
type TwStyle (line 4) | interface TwStyle {
type TemplateFn (line 10) | type TemplateFn<R> = (
type TwFn (line 15) | type TwFn = TemplateFn<TwStyle>
type ThemeSearchFn (line 17) | type ThemeSearchFn<R> = (...values: readonly string[]) => R
type ThemeSearchTaggedFn (line 18) | type ThemeSearchTaggedFn<R> = (
type ThemeFn (line 22) | type ThemeFn = <T = string>(arg?: string | TemplateStringsArray) => T
type ScreenFn (line 24) | type ScreenFn = <T = string>(
type TwComponent (line 28) | type TwComponent<K extends keyof JSX.IntrinsicElements> = (
type TwComponentMap (line 32) | type TwComponentMap = {
type TwComponentWrapper (line 37) | type TwComponentWrapper = <T extends ComponentType<any>>(
type DOMAttributes (line 45) | interface DOMAttributes<T> {
type HTMLAttributes (line 48) | interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
type IntrinsicAttributes (line 55) | interface IntrinsicAttributes {
type Config (line 62) | type Config = {
Condensed preview — 391 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,839K chars).
[
{
"path": ".babelrc",
"chars": 131,
"preview": "{\n \"presets\": [\n \"@babel/preset-typescript\"\n ],\n \"plugins\": [\n \"@babel/plugin-syntax-jsx\",\n \"babel-plugin-ma"
},
{
"path": ".eslintrc.js",
"chars": 2947,
"preview": "module.exports = {\n parser: '@typescript-eslint/parser',\n parserOptions: {\n sourceType: 'module',\n project: './t"
},
{
"path": ".github/FUNDING.yml",
"chars": 68,
"preview": "# These are supported funding model platforms\n\ngithub: ben-rogerson\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.md",
"chars": 668,
"preview": "---\nname: 'Bug report'\nabout: 'Report a reproducible bug'\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n<!--\n\n Thanks for tak"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 264,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Help / Question / Feature request\n url: https://github.com/ben-r"
},
{
"path": ".github/workflows/main.yml",
"chars": 275,
"preview": "name: Notification on push\n\non:\n push:\n branches:\n - master\n\njobs:\n build:\n runs-on: ubuntu-latest\n\n ste"
},
{
"path": ".gitignore",
"chars": 282,
"preview": "node_modules\n/macro.js\n/macro.js.map\n/utils.umd.js\n/utils.umd.js.map\n.vscode\nyarn.lock\n\n# artifacts from type tests\n*.ts"
},
{
"path": ".nvmrc",
"chars": 7,
"preview": "16.14.0"
},
{
"path": ".prettierrc",
"chars": 179,
"preview": "{\n \"endOfLine\": \"lf\",\n \"semi\": false,\n \"singleQuote\": true,\n \"bracketSpacing\": true,\n \"tabWidth\": 2,\n \"trailingCom"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3355,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE",
"chars": 1036,
"preview": "MIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associate"
},
{
"path": "README.md",
"chars": 10784,
"preview": "<p align=\"center\">\n <a href=\"https://github.com/ben-rogerson/twin.macro#gh-light-mode-only\" target=\"_blank\">\n <img s"
},
{
"path": "docs/advanced-theming.md",
"chars": 976,
"preview": "# Theming with css variables\n\nThese examples show how to create themes using css variables rather than relying on the de"
},
{
"path": "docs/arbitrary-values.md",
"chars": 2196,
"preview": "# Arbitrary values\n\nTwin supports the same arbitrary values syntax popularized by Tailwind’s [jit (\"Just-in-Time\") mode]"
},
{
"path": "docs/customizing-config.md",
"chars": 1120,
"preview": "# Customizing the Tailwind config\n\nFor style customizations, add a `tailwind.config.js` in your project root.\n\n> It’s im"
},
{
"path": "docs/fonts.md",
"chars": 2979,
"preview": "# Fonts\n\nYou can add `@font-face` definitions either [in the global styles provider](#add-the-font-face-in-the-global-st"
},
{
"path": "docs/group.md",
"chars": 4045,
"preview": "# Using the group className\n\nThere’s a couple of Tailwind classes that need to be added to React elements as a `classNam"
},
{
"path": "docs/index.md",
"chars": 860,
"preview": "[](#documentation)\n\n# Documentation\n\n[](#usage)\n\n## Usage\n\n- [The prop styling guide](https://github.com/ben-rogerson/tw"
},
{
"path": "docs/options.md",
"chars": 7785,
"preview": "[](#twin-config-options)\n\n# Twin config options\n\nThese options are available in your [twin config](#twin-config-location"
},
{
"path": "docs/prop-styling-guide.md",
"chars": 10305,
"preview": "# The prop styling guide\n\n## Basic styling\n\nUse Twin’s tw prop to add Tailwind classes onto jsx elements:\n\n```js\nimport "
},
{
"path": "docs/screen-import.md",
"chars": 1798,
"preview": "# Screen import\n\nThe screen import creates media queries for custom css that sync with your tailwind config screen value"
},
{
"path": "docs/styled-component-guide.md",
"chars": 11517,
"preview": "# Styled component guide\n\n## Basic styling\n\nUse Twin’s `tw` import to create and style new components with Tailwind clas"
},
{
"path": "jest.config.ts",
"chars": 259,
"preview": "module.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n transform: {\n '^.+\\\\.ts?$': 'ts-jest',\n },\n tr"
},
{
"path": "package.json",
"chars": 4044,
"preview": "{\n \"name\": \"twin.macro\",\n \"version\": \"3.4.1\",\n \"description\": \"Twin blends the magic of Tailwind with the flexibility"
},
{
"path": "sandbox/in.tsx",
"chars": 501,
"preview": "/**\n * Twin Sandbox\n * A place to test the output twin creates.\n * Good for general testing or for developing new featur"
},
{
"path": "src/core/constants.ts",
"chars": 649,
"preview": "const CLASS_SEPARATOR = /\\S+/g\nconst DEFAULTS_UNIVERSAL = '*, ::before, ::after'\nconst EMPTY_CSS_VARIABLE_VALUE = 'var(-"
},
{
"path": "src/core/createCoreContext.ts",
"chars": 4170,
"preview": "/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */\nimport { getTailwindConfig, getConfigTwinValidated } f"
},
{
"path": "src/core/extractRuleStyles.ts",
"chars": 8146,
"preview": "import camelize from './lib/util/camelize'\nimport deepMerge from './lib/util/deepMerge'\nimport get from './lib/util/get'"
},
{
"path": "src/core/getGlobalStyles.ts",
"chars": 1861,
"preview": "import deepMerge from './lib/util/deepMerge'\nimport extractRuleStyles from './extractRuleStyles'\nimport { LAYER_DEFAULTS"
},
{
"path": "src/core/getStyles.ts",
"chars": 8875,
"preview": "import extractRuleStyles from './extractRuleStyles'\nimport createAssert from './lib/createAssert'\nimport expandVariantGr"
},
{
"path": "src/core/index.ts",
"chars": 240,
"preview": "export { default as createCoreContext } from './createCoreContext'\nexport { default as getGlobalStyles } from './getGlob"
},
{
"path": "src/core/lib/configHelpers.ts",
"chars": 3635,
"preview": "import { resolve, dirname } from 'path'\nimport { existsSync } from 'fs'\nimport escalade from 'escalade/sync'\nimport { co"
},
{
"path": "src/core/lib/convertClassName.ts",
"chars": 9858,
"preview": "import replaceThemeValue from './util/replaceThemeValue'\nimport isShortCss from './util/isShortCss'\nimport splitOnFirst "
},
{
"path": "src/core/lib/createAssert.ts",
"chars": 916,
"preview": "import type { AssertContext } from 'core/types'\nimport { makeColor } from './logging'\n\nfunction createAssert(\n CustomEr"
},
{
"path": "src/core/lib/createTheme.ts",
"chars": 1794,
"preview": "import dlv from 'dlv'\nimport { transformThemeValue, toPath } from './util/twImports'\nimport isObject from './util/isObje"
},
{
"path": "src/core/lib/defaultTailwindConfig.ts",
"chars": 4723,
"preview": "import toArray from './util/toArray'\nimport type { PluginAPI } from 'tailwindcss/types/config'\n\nconst AMPERSAND_AFTER = "
},
{
"path": "src/core/lib/expandVariantGroups.ts",
"chars": 2856,
"preview": "import type { Assert, AssertContext, TailwindConfig } from 'core/types'\nimport { splitAtTopLevelOnly } from './util/twIm"
},
{
"path": "src/core/lib/getStitchesPath.ts",
"chars": 1866,
"preview": "import { resolve, relative, parse } from 'path'\nimport { existsSync } from 'fs'\nimport { logGeneralError } from './loggi"
},
{
"path": "src/core/lib/logging.ts",
"chars": 1440,
"preview": "import chalk from 'chalk'\nimport type {\n MakeColor,\n ColorType,\n ColorValue,\n TwinConfigAll,\n} from 'core/types'\n\nco"
},
{
"path": "src/core/lib/twinConfig.ts",
"chars": 4795,
"preview": "import type { GetPackageUsed, TwinConfigAll } from 'core/types'\n\nconst TWIN_CONFIG_DEFAULTS = {\n allowStyleProp: false,"
},
{
"path": "src/core/lib/userPresets.ts",
"chars": 1323,
"preview": "/**\n * Config presets\n *\n * To change the preset, add the following in `package.json`:\n * `{ \"babelMacros\": { \"twin\": { "
},
{
"path": "src/core/lib/util/camelize.ts",
"chars": 167,
"preview": "const CAMEL_FIND = /\\W+(.)/g\n\nexport default function camelize(string: string): string {\n return string?.replace(CAMEL_"
},
{
"path": "src/core/lib/util/deepMerge.ts",
"chars": 118,
"preview": "import deepMerge from 'lodash.merge'\n\n// eslint-disable-next-line unicorn/prefer-export-from\nexport default deepMerge\n"
},
{
"path": "src/core/lib/util/escapeRegex.ts",
"chars": 180,
"preview": "const REGEX_SPECIAL_CHARACTERS = /[$()*+./?[\\\\\\]^{|}-]/g\n\nexport default function escapeRegex(string: string): string {\n"
},
{
"path": "src/core/lib/util/formatProp.ts",
"chars": 446,
"preview": "// eslint-disable-next-line import/no-relative-parent-imports\nimport { SPACE_ID, LINEFEED } from '../../constants'\n\ncons"
},
{
"path": "src/core/lib/util/get.ts",
"chars": 104,
"preview": "import get from 'lodash.get'\n\n// eslint-disable-next-line unicorn/prefer-export-from\nexport default get\n"
},
{
"path": "src/core/lib/util/isEmpty.ts",
"chars": 256,
"preview": "export default function isEmpty(value: unknown): boolean {\n return (\n value === undefined ||\n value === null ||\n "
},
{
"path": "src/core/lib/util/isObject.ts",
"chars": 217,
"preview": "export default function isObject(\n value: unknown\n): value is Record<string, unknown> {\n // eslint-disable-next-line e"
},
{
"path": "src/core/lib/util/isShortCss.ts",
"chars": 981,
"preview": "import { splitAtTopLevelOnly } from './twImports'\nimport type { TailwindConfig } from 'core/types'\n\nexport default funct"
},
{
"path": "src/core/lib/util/replaceThemeValue.ts",
"chars": 1360,
"preview": "import type { AssertContext, CoreContext } from 'core/types'\n\nconst MATCH_THEME = /theme\\((.+?)\\)/\nconst MATCH_QUOTES = "
},
{
"path": "src/core/lib/util/sassifySelector.ts",
"chars": 3101,
"preview": "import type { ExtractRuleStyles } from 'core/types'\n\nconst SELECTOR_PARENT_CANDIDATE = /^[ #.[]/\nconst SELECTOR_SPECIAL_"
},
{
"path": "src/core/lib/util/splitOnFirst.ts",
"chars": 252,
"preview": "// Split a string at a value and return an array of the two parts\nexport default function splitOnFirst(input: string, de"
},
{
"path": "src/core/lib/util/toArray.ts",
"chars": 116,
"preview": "export default function toArray<T>(array: T): T | [T] {\n if (Array.isArray(array)) return array\n return [array]\n}\n"
},
{
"path": "src/core/lib/util/twImports.ts",
"chars": 2269,
"preview": "import type { Config } from 'tailwindcss'\nimport type { TailwindConfig, TailwindContext, TailwindMatch } from 'core/type"
},
{
"path": "src/core/types/index.ts",
"chars": 3995,
"preview": "import type { MacroParams } from 'babel-plugin-macros'\nimport type { NodePath, types as T } from '@babel/core'\nimport ty"
},
{
"path": "src/macro/className.ts",
"chars": 3515,
"preview": "// eslint-disable-next-line import/no-relative-parent-imports\nimport { getStyles } from '../core'\nimport { addDataTwProp"
},
{
"path": "src/macro/css.ts",
"chars": 1551,
"preview": "import { addImport, makeStyledComponent } from './lib/astHelpers'\nimport isEmpty from './lib/util/isEmpty'\nimport type {"
},
{
"path": "src/macro/dataProp.ts",
"chars": 3588,
"preview": "import type { AddDataPropToExistingPath, T } from './types'\n\nconst SPACE_ID = '_'\nconst EXTRA_WHITESPACE = /\\s\\s+/g\ncons"
},
{
"path": "src/macro/globalStyles.ts",
"chars": 5856,
"preview": "// eslint-disable-next-line import/no-relative-parent-imports\nimport { getGlobalStyles } from '../core'\nimport template "
},
{
"path": "src/macro/lib/astHelpers.ts",
"chars": 15176,
"preview": "import get from './util/get'\nimport type {\n T,\n State,\n NodePath,\n CoreContext,\n ImportDeclarationHandler,\n} from '"
},
{
"path": "src/macro/lib/util/get.ts",
"chars": 104,
"preview": "import get from 'lodash.get'\n\n// eslint-disable-next-line unicorn/prefer-export-from\nexport default get\n"
},
{
"path": "src/macro/lib/util/isEmpty.ts",
"chars": 265,
"preview": "function isEmpty(value: unknown): boolean {\n return (\n value === undefined ||\n value === null ||\n (typeof valu"
},
{
"path": "src/macro/lib/validateImports.ts",
"chars": 1341,
"preview": "import type { CoreContext, MacroParams } from 'macro/types'\n\nconst validImports = new Set([\n 'default',\n 'styled',\n '"
},
{
"path": "src/macro/screen.ts",
"chars": 6016,
"preview": "import {\n replaceWithLocation,\n astify,\n getFunctionValue,\n getTaggedTemplateValue,\n getMemberExpression,\n} from '."
},
{
"path": "src/macro/shortCss.ts",
"chars": 4496,
"preview": "/* eslint-disable @typescript-eslint/no-unsafe-call */\n// eslint-disable-next-line import/no-relative-parent-imports\nimp"
},
{
"path": "src/macro/styled.ts",
"chars": 2989,
"preview": "import { addImport, replaceWithLocation } from './lib/astHelpers'\nimport isEmpty from './lib/util/isEmpty'\nimport get fr"
},
{
"path": "src/macro/theme.ts",
"chars": 1726,
"preview": "import {\n replaceWithLocation,\n astify,\n getFunctionValue,\n getTaggedTemplateValue,\n} from './lib/astHelpers'\nimport"
},
{
"path": "src/macro/tw.ts",
"chars": 8932,
"preview": "// eslint-disable-next-line import/no-relative-parent-imports\nimport { getStyles } from '../core'\n// eslint-disable-next"
},
{
"path": "src/macro/twin.ts",
"chars": 3562,
"preview": "// eslint-disable-next-line import/no-relative-parent-imports\nimport { createCoreContext } from '../core'\nimport { Macro"
},
{
"path": "src/macro/types/index.ts",
"chars": 1613,
"preview": "import type { NodePath, types as T } from '@babel/core'\nimport type { MacroParams } from 'babel-plugin-macros'\nimport ty"
},
{
"path": "src/macro.ts",
"chars": 150,
"preview": "import { createMacro } from 'babel-plugin-macros'\nimport twinMacro from './macro/twin'\n\nexport default createMacro(twinM"
},
{
"path": "src/suggestions/index.ts",
"chars": 3651,
"preview": "import { MacroError } from 'babel-plugin-macros'\nimport { validators } from './lib/validators'\nimport { getClassSuggesti"
},
{
"path": "src/suggestions/lib/colors.ts",
"chars": 257,
"preview": "import chalk from 'chalk'\n\nconst colors = {\n error: chalk.hex('#ff8383'),\n errorLight: chalk.hex('#ffd3d3'),\n warn: c"
},
{
"path": "src/suggestions/lib/extractors.ts",
"chars": 665,
"preview": "import type { TailwindContext, TailwindMatch } from 'suggestions/types'\n\nexport function extractClassCandidates(\n tailw"
},
{
"path": "src/suggestions/lib/getClassSuggestions.ts",
"chars": 3150,
"preview": "import stringSimilarity from 'string-similarity'\nimport type { ClassErrorContext } from 'suggestions/types'\n\nconst RATIN"
},
{
"path": "src/suggestions/lib/getPackageVersions.ts",
"chars": 598,
"preview": "export type JSONPrimitive = string | number | boolean | undefined\nexport type JSONValue = JSONPrimitive | JSONObject\n\n//"
},
{
"path": "src/suggestions/lib/makeColor.ts",
"chars": 282,
"preview": "import colors from './colors'\nimport type { MakeColor } from 'suggestions/types'\n\nexport function makeColor(hasColor: bo"
},
{
"path": "src/suggestions/lib/validateVariants.ts",
"chars": 1753,
"preview": "import stringSimilarity from 'string-similarity'\nimport type { ClassErrorContext } from 'suggestions/types'\n\nexport func"
},
{
"path": "src/suggestions/lib/validators.ts",
"chars": 5998,
"preview": "import { validateVariants } from './validateVariants'\nimport type { ClassErrorContext } from 'suggestions/types'\n\nconst "
},
{
"path": "src/suggestions/types/index.ts",
"chars": 698,
"preview": "import type colors from '../lib/colors'\nimport type {\n TailwindMatch,\n TailwindContext,\n TailwindConfig,\n} from '../."
},
{
"path": "tests/@applyInPlugins.test.ts",
"chars": 9796,
"preview": "/* eslint-disable @typescript-eslint/ban-types */\nimport plugin from 'tailwindcss/plugin'\nimport type { TailwindConfig }"
},
{
"path": "tests/__fixtures__/!general.tsx",
"chars": 600,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n/**\n * Misc usage tests\n */\n\nconst styles = tw`uppercase`\nconst Box = tw.div`t"
},
{
"path": "tests/__fixtures__/!important.tsx",
"chars": 1772,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\nconst Box = tw.div`text-red-500`\n\nconst Important = tw`lg:uppercase!`\nconst Me"
},
{
"path": "tests/__fixtures__/!imports.tsx",
"chars": 1255,
"preview": "// @ts-nocheck\nimport tw, { theme, styled, css, GlobalStyles } from '../macro'\n\nconst twPropertyTest = <div tw=\"text-pur"
},
{
"path": "tests/__fixtures__/!namelessImport.tsx",
"chars": 148,
"preview": "// @ts-nocheck\nimport '../macro'\n\nconst twPropertyString = <div tw=\"text-purple-500\" />\nconst twPropertyExpression = <di"
},
{
"path": "tests/__fixtures__/!ordering.tsx",
"chars": 322,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// Test the screen ordering - they are ordered by screens in tailwind.config.j"
},
{
"path": "tests/__fixtures__/!properties.tsx",
"chars": 568,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\nconst Component1 = () => <div tw=\"uppercase\" />\n\nconst Component2 = () => <div"
},
{
"path": "tests/__fixtures__/!variantGrouping.tsx",
"chars": 1274,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\nconst basic = tw`group-hover:(flex m-10)`\nconst subMediaQuery = tw`focus-withi"
},
{
"path": "tests/__fixtures__/!variants.tsx",
"chars": 2823,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// Pseudo element variants\ntw`first-letter:block`\ntw`first-line:block`\ntw`mark"
},
{
"path": "tests/__fixtures__/.eslintrc.js",
"chars": 211,
"preview": "module.exports = {\n rules: {\n '@typescript-eslint/no-unused-vars': 'off',\n 'react/react-in-jsx-scope': 'off',\n "
},
{
"path": "tests/__fixtures__/addBase/addBase.tsx",
"chars": 123,
"preview": "// @ts-nocheck\nimport tw, { GlobalStyles, globalStyles } from '../macro'\n\ntw`base-selector`\n;<GlobalStyles />\nglobalStyl"
},
{
"path": "tests/__fixtures__/addBase/tailwind.config.js",
"chars": 795,
"preview": "function addBasePlugin({ addBase }) {\n const baseStyles = {\n ':root': {\n '--color-pink-900': '#831843',\n },\n"
},
{
"path": "tests/__fixtures__/arbitraryProperties/arbitraryProperties.tsx",
"chars": 273,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`[mask-image:linear-gradient(180deg,white, rgba(255,255,255,0))]`\ntw`[-webki"
},
{
"path": "tests/__fixtures__/arbitraryVariants/arbitraryVariants.tsx",
"chars": 1405,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`[section]:hover:block`\ntw`[section&]:hover:block`\n\ntw`[p]:hover:block`\ntw`h"
},
{
"path": "tests/__fixtures__/arbitraryVariants/config.json",
"chars": 57,
"preview": "{\n \"disableCsProp\": false,\n \"disableShortCss\": false\n}\n"
},
{
"path": "tests/__fixtures__/autoCssProp/autoCssProp.tsx",
"chars": 120,
"preview": "// @ts-nocheck\nimport '../macro' // twinImport\n\n// Css prop isn't handled by twin\n;<div css=\"\" />\n;<div className=\"\" />\n"
},
{
"path": "tests/__fixtures__/colorFunctions/colorFunctions.tsx",
"chars": 146,
"preview": "// @ts-nocheck\nimport tw, { GlobalStyles } from '../macro' // twinImport\n\ntw`text-foreground text-opacity-40`\ntw`text-gr"
},
{
"path": "tests/__fixtures__/colorFunctions/tailwind.config.js",
"chars": 639,
"preview": "const color =\n name =>\n ({ opacityVariable, opacityValue }) => {\n if (opacityValue !== undefined) {\n return `r"
},
{
"path": "tests/__fixtures__/comments/comments.tsx",
"chars": 998,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n/**\n * Test comments\n */\n\n// singleline\n;<div css={tw`// comment`} />\n\n// mult"
},
{
"path": "tests/__fixtures__/comments/config.json",
"chars": 26,
"preview": "{\n \"dataTwProp\": \"all\"\n}\n"
},
{
"path": "tests/__fixtures__/config/config.tsx",
"chars": 882,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n/**\n * Test the config matching is working correctly\n */\n\ntw`animate-zoom-.5`\n"
},
{
"path": "tests/__fixtures__/config/tailwind.config.js",
"chars": 1401,
"preview": "module.exports = {\n theme: {\n animation: {\n 'zoom-.5': 'zoom-.5 2s',\n },\n colors: {\n number: 0,\n "
},
{
"path": "tests/__fixtures__/configTS/configTS.tsx",
"chars": 116,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n/**\n * Test the config matching is working correctly\n */\n\ntw`text-purple`\n"
},
{
"path": "tests/__fixtures__/configTS/tailwind.config.ts",
"chars": 155,
"preview": "import type { Config } from 'tailwindcss'\n\nexport default {\n content: [],\n theme: {\n colors: {\n purple: 'custo"
},
{
"path": "tests/__fixtures__/content/content.tsx",
"chars": 351,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/content\ntheme`content`\n\ntw`content`"
},
{
"path": "tests/__fixtures__/content/tailwind.config.js",
"chars": 136,
"preview": "module.exports = {\n theme: {\n extend: {\n content: {\n test: '\"hi\"',\n DEFAULT: '\"default\"',\n }"
},
{
"path": "tests/__fixtures__/cssPropEmotion/autoCssProp.tsx",
"chars": 85,
"preview": "// @ts-nocheck\nimport '../macro' // twinImport\n;<div css=\"\" />\n;<div className=\"\" />\n"
},
{
"path": "tests/__fixtures__/cssPropEmotion/autoCssPropWithStyled.tsx",
"chars": 168,
"preview": "// @ts-nocheck\nimport tw from '../macro' // twinImport\n\n// Css prop isn't handled by twin\ntw.div`block`\n;<div tw=\"block\""
},
{
"path": "tests/__fixtures__/cssPropStyledComponents/autoCssProp.tsx",
"chars": 85,
"preview": "// @ts-nocheck\nimport '../macro' // twinImport\n;<div css=\"\" />\n;<div className=\"\" />\n"
},
{
"path": "tests/__fixtures__/cssPropStyledComponents/autoCssPropWithStyled.tsx",
"chars": 134,
"preview": "// @ts-nocheck\nimport tw from '../macro' // twinImport\n\ntw.div`block`\n;<div tw=\"block\" />\n\nconst Test = tw.div``\n;<Test "
},
{
"path": "tests/__fixtures__/cssPropStyledComponents/config.json",
"chars": 60,
"preview": "{\n \"preset\": \"styled-components\",\n \"autoCssProp\": false\n}\n"
},
{
"path": "tests/__fixtures__/darkLightModeArray/darkLightModeArray.tsx",
"chars": 87,
"preview": "// @ts-nocheck\nimport tw from '../macro' // twinImport\n\ntw`dark:block`\ntw`light:block`\n"
},
{
"path": "tests/__fixtures__/darkLightModeArray/tailwind.config.js",
"chars": 97,
"preview": "module.exports = {\n darkMode: ['class', '.test-dark'],\n lightMode: ['class', '.test-light'],\n}\n"
},
{
"path": "tests/__fixtures__/directionalBorders/directionalBorders.tsx",
"chars": 466,
"preview": "// @ts-nocheck\nimport tw from '../macro' // twinImport\n\ntw`border-t`\ntw`border-r`\ntw`border-b`\ntw`border-l`\n\ntw`border-t"
},
{
"path": "tests/__fixtures__/fluidContainer/fluidContainer.tsx",
"chars": 68,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`fluid-container ml-10`\n"
},
{
"path": "tests/__fixtures__/fluidContainer/tailwind.config.js",
"chars": 1543,
"preview": "function fluidContainer({ addComponents, theme }) {\n const styles = [\n {\n '.fluid-container': {\n marginL"
},
{
"path": "tests/__fixtures__/globalStyles/config.json",
"chars": 26,
"preview": "{\n \"preset\": \"emotion\"\n}\n"
},
{
"path": "tests/__fixtures__/globalStyles/globalStyles.tsx",
"chars": 266,
"preview": "// @ts-nocheck\nimport { GlobalStyles } from '../macro'\nimport { css, Global } from '@emotion/react'\n\nconst MyGlobals = ("
},
{
"path": "tests/__fixtures__/globalStyles/tailwind.config.js",
"chars": 592,
"preview": "module.exports = {\n theme: {\n fontFamily: {\n // Tests the dynamic default html font\n sans: ['testSans', 't"
},
{
"path": "tests/__fixtures__/group/group.tsx",
"chars": 2337,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// Positional\ntw`group-first:block`\ntw`group-last:block`\ntw`group-only:block`\n"
},
{
"path": "tests/__fixtures__/includeClassNames/config.json",
"chars": 58,
"preview": "{\n \"includeClassNames\": true,\n \"disableCsProp\": false\n}\n"
},
{
"path": "tests/__fixtures__/includeClassNames/includeClassNames.tsx",
"chars": 2917,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\nconst SkipEmptyClassName = <div className=\"\" />\nconst OnlyUppercaseConverted ="
},
{
"path": "tests/__fixtures__/lineClamp/lineClamp.tsx",
"chars": 164,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`line-clamp-1`\ntw`line-clamp-2`\ntw`line-clamp-3`\ntw`line-clamp-4`\ntw`line-cl"
},
{
"path": "tests/__fixtures__/negative/negative.tsx",
"chars": 75,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`-z-1`\ntw`-z-10`\ntw`-inset-10`\n"
},
{
"path": "tests/__fixtures__/negative/tailwind.config.js",
"chars": 70,
"preview": "module.exports = {\n theme: { extend: { zIndex: { '-1': '-1' } } },\n}\n"
},
{
"path": "tests/__fixtures__/peers/peers.tsx",
"chars": 2258,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// Positional\ntw`peer-first:block`\ntw`peer-last:block`\ntw`peer-only:block`\ntw`"
},
{
"path": "tests/__fixtures__/pluginAspectRatio/pluginAspectRatio.tsx",
"chars": 536,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`aspect-w-1`\ntw`aspect-h-1`\ntw`aspect-w-2`\ntw`aspect-h-2`\ntw`aspect-w-3`\ntw`"
},
{
"path": "tests/__fixtures__/pluginAspectRatio/tailwind.config.js",
"chars": 132,
"preview": "// https://github.com/tailwindlabs/tailwindcss-aspect-ratio\nmodule.exports = {\n plugins: [require('@tailwindcss/aspect-"
},
{
"path": "tests/__fixtures__/pluginDaisyUi/pluginDaisyUi.tsx",
"chars": 21952,
"preview": "// @ts-nocheck\nimport { globalStyles } from '../macro'\n\nglobalStyles\n;() => (\n <>\n <div tw=\"p-5 m-5\">\n <div tw="
},
{
"path": "tests/__fixtures__/pluginDaisyUi/tailwind.config.js",
"chars": 109,
"preview": "module.exports = {\n plugins: [require('daisyui')],\n daisyui: { themes: false, logs: false, rtl: false },\n}\n"
},
{
"path": "tests/__fixtures__/pluginExamples/pluginExamples.tsx",
"chars": 593,
"preview": "// @ts-nocheck\nimport tw, { globalStyles } from '../macro'\n\ntw`content-auto`\ntw`content-hidden`\ntw`content-visible`\n\ntw`"
},
{
"path": "tests/__fixtures__/pluginExamples/tailwind.config.js",
"chars": 2792,
"preview": "// https://tailwindcss.com/docs/plugins\nconst plugin = require('tailwindcss/plugin')\n\nconst addUtilities = function ({ a"
},
{
"path": "tests/__fixtures__/pluginForms/pluginForms.tsx",
"chars": 73,
"preview": "// @ts-nocheck\nimport { GlobalStyles } from '../macro'\n;<GlobalStyles />\n"
},
{
"path": "tests/__fixtures__/pluginForms/tailwind.config.js",
"chars": 65,
"preview": "module.exports = {\n plugins: [require('@tailwindcss/forms')],\n}\n"
},
{
"path": "tests/__fixtures__/pluginFormsClassStrategy/pluginTypography.tsx",
"chars": 145,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`form-radio`\ntw`form-select`\ntw`form-checkbox`\ntw`form-input`\ntw`form-textar"
},
{
"path": "tests/__fixtures__/pluginFormsClassStrategy/tailwind.config.js",
"chars": 88,
"preview": "module.exports = {\n plugins: [require('@tailwindcss/forms')({ strategy: 'class' })],\n}\n"
},
{
"path": "tests/__fixtures__/pluginGapFallback/pluginGapFallback.tsx",
"chars": 234,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`flex-gap-0`\ntw`flex-gap-3`\ntw`flex-gap-x-3`\ntw`flex-gap-y-3`\ntw`flex-gap-px"
},
{
"path": "tests/__fixtures__/pluginGapFallback/tailwind.config.js",
"chars": 2412,
"preview": "const plugin = require('tailwindcss/plugin')\n\nconst half = value => value.replace(/\\d+(.\\d+)?/, number => number / 2)\n\n/"
},
{
"path": "tests/__fixtures__/pluginTypography/pluginTypography.tsx",
"chars": 166,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// From @tailwindcss/typography\ntw`prose sm:prose-sm lg:prose-lg xl:prose-xl`\n"
},
{
"path": "tests/__fixtures__/pluginTypography/tailwind.config.js",
"chars": 1564,
"preview": "const textStyles = theme => ({\n heading: {\n output: false,\n fontWeight: theme('fontWeight.bold'),\n lineHeight:"
},
{
"path": "tests/__fixtures__/pluginUserParentSelector/pluginUserParentSelector.tsx",
"chars": 126,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`my-class1`\ntw`my-class2`\ntw`my-class3`\ntw`my-class4`\ntw`my-class5`\ntw`my-cl"
},
{
"path": "tests/__fixtures__/pluginUserParentSelector/tailwind.config.js",
"chars": 993,
"preview": "const plugin = require('tailwindcss/plugin')\n\nmodule.exports = {\n plugins: [\n plugin(({ addComponents }) =>\n ad"
},
{
"path": "tests/__fixtures__/plugins/config.json",
"chars": 55,
"preview": "{\n \"includeClassNames\": true,\n \"dataTwProp\": \"all\"\n}\n"
},
{
"path": "tests/__fixtures__/plugins/plugins.tsx",
"chars": 737,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// Tailwind plugin tests\n\ntw`type-sm`\n\nconst addUtilitiesTest = tw`type-sm tex"
},
{
"path": "tests/__fixtures__/plugins/tailwind.config.js",
"chars": 2609,
"preview": "module.exports = {\n theme: {\n aspectRatio: {\n 2: '2',\n 4: '4',\n 6: '6',\n },\n },\n plugins: [\n "
},
{
"path": "tests/__fixtures__/prefix/config.json",
"chars": 83,
"preview": "{\n \"includeClassNames\": true,\n \"dataTwProp\": \"all\",\n \"disableShortCss\": false\n}\n"
},
{
"path": "tests/__fixtures__/prefix/prefix.tsx",
"chars": 982,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// tw prop prefix\n;<div tw=\"tw-text-black\" />\n\n// tw import prefix\n;<div css={"
},
{
"path": "tests/__fixtures__/prefix/tailwind.config.js",
"chars": 708,
"preview": "const plugin = require('tailwindcss/plugin')\n\nfunction pluginClass({ addComponents }) {\n addComponents([\n {\n '."
},
{
"path": "tests/__fixtures__/preflight/preflight.tsx",
"chars": 69,
"preview": "// @ts-nocheck\nimport { globalStyles } from '../macro'\n\nglobalStyles\n"
},
{
"path": "tests/__fixtures__/preflight/tailwind.config.js",
"chars": 65,
"preview": "module.exports = {\n corePlugins: {\n preflight: false,\n },\n}\n"
},
{
"path": "tests/__fixtures__/presets/presets.tsx",
"chars": 136,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`text-badass`\ntw`text-banana`\ntw`text-hamburger`\ntw`active:text-white`\ntw`ho"
},
{
"path": "tests/__fixtures__/presets/tailwind.config.js",
"chars": 392,
"preview": "const myConfigDefault = {\n theme: {\n extend: {\n colors: {\n hamburger: 'brown',\n },\n },\n },\n}\n\nc"
},
{
"path": "tests/__fixtures__/sassyPseudo/config.json",
"chars": 54,
"preview": "{\n \"sassyPseudo\": true,\n \"disableShortCss\": false\n}\n"
},
{
"path": "tests/__fixtures__/sassyPseudo/sassyPseudo.tsx",
"chars": 198,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`hover:block first:mt-2 last-of-type:max-width[20px]`\ntw`hover:block first:m"
},
{
"path": "tests/__fixtures__/sassyPseudo/tailwind.config.js",
"chars": 491,
"preview": "const plugin = require('tailwindcss/plugin')\n\nmodule.exports = {\n plugins: [\n plugin(({ addComponents }) =>\n ad"
},
{
"path": "tests/__fixtures__/screenImport/screenImport.tsx",
"chars": 2050,
"preview": "// @ts-nocheck\nimport tw, { styled, screen } from '../macro'\n\n// Media query only\nscreen`sm`\nscreen.md // Can't work wit"
},
{
"path": "tests/__fixtures__/screenImport/tailwind.config.js",
"chars": 135,
"preview": "module.exports = {\n theme: {\n screens: {\n sm: '100px',\n md: '200px',\n lg: '300px',\n xl: '400px',"
},
{
"path": "tests/__fixtures__/screens/screens.tsx",
"chars": 141,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`sm:block`\ntw`md:block`\ntw`lg:block`\ntw`xl:block`\ntw`2xl:block`\n\ntw`<sm:unde"
},
{
"path": "tests/__fixtures__/screens/tailwind.config.js",
"chars": 150,
"preview": "module.exports = {\n theme: {\n extend: {\n screens: {\n '<sm': { max: '399px' },\n 'md>': { min: '500"
},
{
"path": "tests/__fixtures__/separator/separator.tsx",
"chars": 97,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`[&[data-foo][data-bar]:not([data-baz])]__underline`\n"
},
{
"path": "tests/__fixtures__/separator/tailwind.config.js",
"chars": 40,
"preview": "module.exports = {\n separator: '__',\n}\n"
},
{
"path": "tests/__fixtures__/shortCss/config.json",
"chars": 57,
"preview": "{\n \"disableShortCss\": false,\n \"disableCsProp\": false\n}\n"
},
{
"path": "tests/__fixtures__/shortCss/shortCss.tsx",
"chars": 2201,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// within cs prop\n;<div cs=\"maxWidth[100vw - 2rem]\" />\n;<div cs=\"maxWidth[100v"
},
{
"path": "tests/__fixtures__/stitches/config.json",
"chars": 97,
"preview": "{\n \"preset\": \"stitches\",\n \"stitchesConfig\": \"tests/__fixtures__/stitches/stitches.config.js\"\n}\n"
},
{
"path": "tests/__fixtures__/stitches/stitches.config.js",
"chars": 17,
"preview": "// just for show\n"
},
{
"path": "tests/__fixtures__/stitches/stitchesDotSyntax.tsx",
"chars": 167,
"preview": "// @ts-nocheck\nimport tw, { styled } from '../macro'\n\ntw.div`block`\nstyled.div(tw`block`)\nstyled.div({ display: 'block' "
},
{
"path": "tests/__fixtures__/stitches/stitchesGlobals.tsx",
"chars": 184,
"preview": "// @ts-nocheck\nimport { globalStyles } from '../macro'\nimport { global } from './stitches.config'\n\nconst globals = globa"
},
{
"path": "tests/__fixtures__/stitches/stitchesImports.tsx",
"chars": 110,
"preview": "// @ts-nocheck\nimport tw, { css, styled } from '../macro'\n\ncss(tw`block`)\ntw.div`block`\nstyled.div(tw`block`)\n"
},
{
"path": "tests/__fixtures__/stitches/stitchesProps.tsx",
"chars": 589,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n// tw prop\n;<div tw=\"block\" />\n\n// tw + css prop\n;<div tw=\"block\" css={{ color:"
},
{
"path": "tests/__fixtures__/themeValuesToString/tailwind.config.js",
"chars": 331,
"preview": "module.exports = {\n theme: {\n colors: {},\n keyframes: {\n 'fade-up': {\n from: {\n transform: '"
},
{
"path": "tests/__fixtures__/themeValuesToString/themeValuesToString.tsx",
"chars": 102,
"preview": "// @ts-nocheck\nimport { globalStyles, theme } from '../macro'\n\nglobalStyles\n\ntheme`keyframes`\ntheme``\n"
},
{
"path": "tests/__fixtures__/userPluginOrdering/tailwind.config.js",
"chars": 1272,
"preview": "function fluidContainer({ addComponents, theme }) {\n const styles = [\n {\n '@media (min-width: 1px)': {\n "
},
{
"path": "tests/__fixtures__/userPluginOrdering/userPluginOrdering.tsx",
"chars": 55,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\ntw`selector`\n"
},
{
"path": "tests/__fixtures__/utilitiesAccessibility/screenReaders.tsx",
"chars": 117,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/screen-readers\ntw`sr-only`\ntw`not-sr-only`\n"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundAttachment.tsx",
"chars": 136,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/background-attachment\ntw`bg-fixed`\ntw`bg-local"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundClip.tsx",
"chars": 166,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/background-clip\ntw`bg-clip-border`\ntw`bg-clip-"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundColor.tsx",
"chars": 4566,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/background-color\ntheme`backgroundCo"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundImage.tsx",
"chars": 333,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/background-image\ntheme`backgroundIm"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundOpacity.tsx",
"chars": 492,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/background-opacity\ntw`bg-sky-500/100`\ntw`bg-sk"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundOrigin.tsx",
"chars": 157,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/background-origin\ntw`bg-origin-border`\ntw`bg-o"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundPosition.tsx",
"chars": 317,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/background-position\ntw`bg-bottom`\ntw`bg-center"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundRepeat.tsx",
"chars": 233,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/background-repeat\ntheme`backgroundP"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/backgroundSize.tsx",
"chars": 194,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/background-size\ntheme`backgroundSiz"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/gradientColorStops.tsx",
"chars": 13573,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/gradient-color-stops\ntheme`gradient"
},
{
"path": "tests/__fixtures__/utilitiesBackgrounds/tailwind.config.js",
"chars": 467,
"preview": "module.exports = {\n theme: {\n extend: {\n colors: {\n 'red-500/fromConfig': '#000',\n electric: ({ o"
},
{
"path": "tests/__fixtures__/utilitiesBorders/borderColor.tsx",
"chars": 36949,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/border-color\ntheme`borderColor.`\n\nt"
},
{
"path": "tests/__fixtures__/utilitiesBorders/borderOpacity.tsx",
"chars": 432,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/border-opacity\n// Deprecated\ntw`border-opacity"
},
{
"path": "tests/__fixtures__/utilitiesBorders/borderRadius.tsx",
"chars": 1690,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/border-radius\ntheme`borderRadius.`\n"
},
{
"path": "tests/__fixtures__/utilitiesBorders/borderStyle.tsx",
"chars": 192,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/border-style\ntw`border-solid`\ntw`border-dashed"
},
{
"path": "tests/__fixtures__/utilitiesBorders/borderWidth.tsx",
"chars": 912,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/border-width\ntheme`borderWidth.`\n\nt"
},
{
"path": "tests/__fixtures__/utilitiesBorders/divideColor.tsx",
"chars": 4922,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/divide-color\ntheme`divideColor.`\n\nt"
},
{
"path": "tests/__fixtures__/utilitiesBorders/divideOpacity.tsx",
"chars": 476,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/divide-opacity\n// Deprecated in fav"
},
{
"path": "tests/__fixtures__/utilitiesBorders/divideStyle.tsx",
"chars": 174,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/divide-style\ntw`divide-solid`\ntw`divide-dashed"
},
{
"path": "tests/__fixtures__/utilitiesBorders/divideWidth.tsx",
"chars": 460,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/divide-width\ntheme`divideWidth.`\n\nt"
},
{
"path": "tests/__fixtures__/utilitiesBorders/outlineColor.tsx",
"chars": 5399,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/outline-color\ntheme`outlineColor`\n\n"
},
{
"path": "tests/__fixtures__/utilitiesBorders/outlineOffset.tsx",
"chars": 276,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/outline-offset\ntheme`outlineOffset`"
},
{
"path": "tests/__fixtures__/utilitiesBorders/outlineStyle.tsx",
"chars": 174,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// https://tailwindcss.com/docs/outline-style\ntw`outline`\ntw`outline-none`\ntw`"
},
{
"path": "tests/__fixtures__/utilitiesBorders/outlineWidth.tsx",
"chars": 248,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/outline-width\ntheme`outlineWidth`\n\n"
},
{
"path": "tests/__fixtures__/utilitiesBorders/ringColor.tsx",
"chars": 4979,
"preview": "// @ts-nocheck\nimport tw, { theme } from '../macro'\n\n// https://tailwindcss.com/docs/ring-color\ntheme`ringColor.`\n\ntw`ri"
},
{
"path": "tests/__fixtures__/utilitiesBorders/ringMisc.tsx",
"chars": 594,
"preview": "// @ts-nocheck\nimport tw from '../macro'\n\n// Combined ring classes\ntw`ring ring-inset ring-purple-500 ring-offset-black "
}
]
// ... and 191 more files (download for full content)
About this extraction
This page contains the full source code of the ben-rogerson/twin.macro GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 391 files (2.6 MB), approximately 689.7k tokens, and a symbol index with 285 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.