UNPKG

@kanton-basel-stadt/designsystem

Version:

Unplugin to install the digital design system of the canton of Basel-Stadt

405 lines (389 loc) 10.4 kB
import type { Config } from 'tailwindcss' import fs from 'node:fs' import path from 'node:path' import plugin from 'tailwindcss/plugin.js' import colors from './colors.ts' const customContent: Record<string, string> = { 'arrow-east': '"→"', 'arrow-west': '"←"', 'arrow-north-east': '"↗"', 'arrow-south': '"↓"', 'underscore-long': '""', 'cross': '"✗"', 'plus': '""', 'reload': '"↻"', 'check': '"✓"', 'caret-south': '"⌄"', 'caret-north': '"⌃"', 'dot': '"•"', 'empty': '""', } // Automatically define z-index for global components. // The order is from bottom to top. const zIndex = ['app-top', 'alva', 'search-input-suggestions'].reduce< Record<string, string> >((acc, key, index) => { // We start our automatic z-indexes at 300 and increase by 10. acc[key] = (300 + index * 10).toString() return acc }, {}) const fontSize: Record<string, [string, string]> = { '9xl': ['128px', '128px'], '8xl': ['96px', '96px'], '7xl': ['72px', '72px'], '6xl': ['60px', '60px'], '5xl': ['48px', '48px'], '4xl': ['36px', '40px'], '3xl': ['30px', '34px'], '2xl': ['24px', '32px'], 'xl': ['20px', '28px'], 'lg': ['18px', '24px'], 'base': ['16px', '22px'], 'sm': ['14px', '20px'], 'xs': ['12px', '18px'], } const colorsShaded = Object.keys(colors).reduce<Record<string, string>>( (acc, color) => { const shades = colors[color] Object.entries(shades).forEach(([shade, hex]) => { acc[`${color}-${shade}`] = hex }) return acc }, {}, ) const projectRoot = path.resolve('.') function getContentDependencies(path: string) { const fileEndings = [ 'html', 'js', 'ts', 'jsx', 'tsx', 'vue', 'astro', 'svelte', 'mdx', 'twig', 'hbs', // Short form of handlebars 'handlebars', 'pug', 'blade.php', // Laravel Blade 'tpl', // Smarty ].join(',') const dirCandidates = [ 'components', 'pages', 'layouts', 'helpers', 'stories', // Storybook 'dist', 'src', ] // We need to explicitly filter for existing directories/files, // because otherwise esbuild-plugin-postcss2 will try to scan // directories that don't exist and will fall flat on its face. return [ `./*.{${fileEndings}}`, ...dirCandidates.map(d => `${path}/${d}`) .filter(d => fs.existsSync(d)) .map(d => `${d}/**/*.{${fileEndings}}`), ] } const config: Config = { content: getContentDependencies(projectRoot), safelist: ['h-0'], blocklist: [], plugins: [ /** * Various additional variants */ plugin(({ addVariant }) => { addVariant( 'mobile-only', '@media screen and (max-width: theme(\'screens.md\'))', ) addVariant('not-last', '&:not(:last-child)') addVariant('not-first', '&:not(:first-child)') }), plugin(({ matchUtilities, theme }) => { matchUtilities( { 'animation-rotation': (value: string) => ({ '--animation-rotation': value, }), }, { values: theme('rotate'), type: 'any', }, ) matchUtilities( { 'animation-duration': (value: string) => ({ '--animation-duration': value, }), }, { values: theme('transitionDuration'), type: 'any', }, ) // Custom implementation of the `content-` utility class that adds support // for alt text in content. matchUtilities( { content: (content: string) => { return { // `/ ""` acts as an alt text for the `content`, which is then read by screen readers instead. // If empty, the content will be ignored. See https://developer.mozilla.org/en-US/docs/Web/CSS/content // Defining an array here will create two CSS content properties, where the first one is the fallback // for browsers that don't support the syntax with alt text. content: [content, `${content} / ""`], } }, }, { values: theme('customContent'), }, ) }), ], corePlugins: { textOpacity: false, container: false, // Disabled because we have our own implementation that adds a fallback. content: false, }, theme: { customContent, screens: { sm: '480px', md: '768px', lg: '1024px', xl: '1210px', xxl: '1920px', }, spacing: { '220': '220px', '140': '140px', '120': '120px', '100': '100px', '90': '90px', '80': '80px', '70': '70px', '60': '60px', '50': '50px', '40': '40px', '35': '35px', '30': '30px', '25': '25px', '20': '20px', '15': '15px', '10': '10px', '8': '8px', '6': '6px', '5': '5px', '3': '3px', '2': '2px', '1': '1px', '0': '0px', 'sticky-top': 'var(--base-sticky-top)', }, borderWidth: { DEFAULT: '1px', 0: '0', 2: '2px', 3: '3px', 4: '4px', }, maxWidth: { reduced: '970px', // @todo: Consolidate sizing with container, max-width and other layout classes. prose: '836px', box: '610px', fit: 'fit-content', }, lineHeight: { none: '1', tight: '1.2', snug: '1.3', normal: '1.4', }, fontSize, fontFamily: { sans: [ 'Inter', 'Inter Fallback', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif', ], }, fontWeight: { normal: '400', medium: '500', bold: '700', }, borderRadius: { none: '0px', full: '9999px', large: '10px', DEFAULT: '4px', }, colors: { // Monochrome colors. white: '#ffffff', current: 'currentColor', transparent: 'transparent', body: 'black', ...colorsShaded, // The dynamic CSS variable based primary color which is overridden by the Bettingen site at runtime. primary: { 50: 'rgb(var(--color-primary-50) / <alpha-value>)', 100: 'rgb(var(--color-primary-100) / <alpha-value>)', 200: 'rgb(var(--color-primary-200) / <alpha-value>)', 300: 'rgb(var(--color-primary-300) / <alpha-value>)', 400: 'rgb(var(--color-primary-400) / <alpha-value>)', 500: 'rgb(var(--color-primary-500) / <alpha-value>)', 600: 'rgb(var(--color-primary-600) / <alpha-value>)', 700: 'rgb(var(--color-primary-700) / <alpha-value>)', 800: 'rgb(var(--color-primary-800) / <alpha-value>)', 900: 'rgb(var(--color-primary-900) / <alpha-value>)', }, }, extend: { zIndex, gap: { DEFAULT: '20px', }, transitionDuration: { 250: '250ms', }, transitionTimingFunction: { swing: 'cubic-bezier(0.56, 0.04, 0.25, 1)', momentum: 'cubic-bezier(1,-0.76,.46,1.01)', }, boxShadow: { 'purple-600': '0 0 10px 0 #9156B4', 'purple-600-small': '0 0 5px 0 #9156B4', 'white': '0 0 10px 0 #fff', 'none': '0 0 0 0 #000', }, keyframes: { 'jump-x': { '0%': { transform: 'translateX(0)', }, '20%': { transform: 'translateX(0.15em)', }, '60%': { transform: 'translateX(-0.15em)', }, '100%': { transform: 'translateX(0)', }, }, 'jump-y': { '0%': { transform: 'translateY(0)', }, '20%': { transform: 'translateY(0.15em)', }, '60%': { transform: 'translateY(-0.15em)', }, '100%': { transform: 'translateY(0)', }, }, 'jump-x-reverse': { '0%': { transform: 'translateX(0)', }, '20%': { transform: 'translateX(-0.15em)', }, '60%': { transform: 'translateX(0.15em)', }, '100%': { transform: 'translateX(0)', }, }, 'jump-xy': { '0%': { transform: 'translate(0, 0)', }, '20%': { transform: 'translate(0.15em, -0.15em)', }, '60%': { transform: 'translate(-0.1em, 0.1em)', }, '100%': { transform: 'translate(0, 0)', }, }, 'jump-scale': { '0%': { transform: 'scale(1)', }, '20%': { transform: 'scale(1.3)', }, '60%': { transform: 'scale(0.9)', }, '100%': { transform: 'scale(1)', }, }, 'wiggle': { '0%': { transform: 'rotate(0deg)', }, '20%': { transform: 'rotate(var(--animation-rotation, 15deg))', }, '40%': { transform: 'rotate(calc(-1 * var(--animation-rotation, 15deg)))', }, '60%': { transform: 'rotate(var(--animation-rotation, 15deg))', }, '80%': { transform: 'rotate(calc(-1 * var(--animation-rotation, 15deg)))', }, '100%': { transform: 'rotate(0deg)', }, }, 'rotate': { from: { transform: 'rotate(0deg)', }, to: { transform: 'rotate(var(--animation-rotation, 360deg))', }, }, }, animation: { 'jump-x': `jump-x var(--animation-duration, 0.4s) ease-in-out`, 'jump-y': `jump-y var(--animation-duration, 0.5s) ease-in-out`, 'jump-scale': `jump-scale var(--animation-duration, 0.5s) ease-in-out`, 'jump-x-reverse': 'jump-x-reverse var(--animation-duration, 0.5s) ease-in-out', 'jump-xy': 'jump-xy var(--animation-duration, 0.5s) ease-in-out', 'wiggle': 'wiggle var(--animation-duration, 0.5s) linear', 'rotate': 'rotate var(--animation-duration, 0.5s) ease-in-out', 'rotate-infinite': 'rotate var(--animation-duration, 0.5s) linear infinite', }, }, }, } export default config