ui: share tailwind config across packages

pull/305/head
Yangshun Tay 2 years ago
parent 5734758f96
commit de33d38e1b

@ -30,10 +30,7 @@
"zod": "^3.18.0"
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"@tih/tailwind-config": "*",
"@tih/tsconfig": "*",
"@types/node": "^18.0.0",
"@types/react": "^18.0.21",
@ -42,7 +39,6 @@
"postcss": "^8.4.16",
"prettier-plugin-tailwindcss": "^0.1.13",
"prisma": "^4.4.0",
"tailwindcss": "^3.1.8",
"typescript": "4.8.3"
},
"ct3aMetadata": {

@ -1,6 +1,13 @@
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
const config = require('@tih/tailwind-config/tailwind.config.js');
module.exports = {
plugins: {
tailwindcss: {},
// Specifying the config is not necessary in most cases, but it is included
// here to share the same config across the entire monorepo
tailwindcss: { config },
autoprefixer: {},
},
};

@ -12,6 +12,7 @@ import AppShell from '~/components/global/AppShell';
import type { AppRouter } from '~/server/router';
import '~/styles/globals.css';
import '@tih/ui/styles.css';
const MyApp: AppType<{ session: Session | null }> = ({
Component,

@ -1,8 +1,16 @@
import { Button, CounterButton } from '@tih/ui';
export default function HomePage() {
return (
<main className="flex-1 overflow-y-auto">
<div className="flex h-full items-center justify-center">
<h1 className="text-center font-bold text-4xl">Homepage</h1>
<div>
<h1 className="text-center text-4xl font-bold text-red-600">
Homepage
</h1>
<CounterButton />
<Button label="Button text" size="md" variant="primary" />
</div>
</div>
</main>
);

@ -0,0 +1 @@
storybook-static

@ -0,0 +1 @@
import '@tih/ui/dist/styles.css';

@ -20,6 +20,7 @@
"@storybook/addon-links": "^6.4.18",
"@storybook/builder-vite": "^0.1.33",
"@storybook/react": "^6.4.18",
"@tih/tailwind-config": "*",
"@tih/tsconfig": "*",
"@vitejs/plugin-react": "^1.3.2",
"eslint-config-tih": "*",

@ -0,0 +1,13 @@
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
const config = require('@tih/tailwind-config/tailwind.config.js');
module.exports = {
plugins: {
// Specifying the config is not necessary in most cases, but it is included
// here to share the same config across the entire monorepo
tailwindcss: { config },
autoprefixer: {},
},
};

@ -0,0 +1,33 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Button } from '@tih/ui';
import React from 'react';
//👇 This default export determines where your story goes in the story list
export default {
/* 👇 The title prop is optional.
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'Button',
component: Button,
} as ComponentMeta<typeof Button>;
//👇 We create a “template” of how args map to rendering
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
export const PrimaryButton = Template.bind({});
PrimaryButton.args = {
label: 'Button text',
size: 'md',
variant: 'primary',
};
export const SecondaryButton = Template.bind({});
SecondaryButton.args = {
label: 'Button text',
size: 'md',
variant: 'secondary',
};

@ -12,8 +12,8 @@
"build": "turbo build",
"ci": "yarn lint && yarn tsc",
"clean": "turbo clean",
"dev": "turbo dev --filter=portal...",
"dev:ui": "turbo dev --filter=storybook...",
"dev": "turbo dev --filter=portal... --filter=ui...",
"dev:ui": "turbo dev --filter=storybook... --filter=ui...",
"dev:website": "turbo dev --filter=website...",
"dev:all": "turbo dev --no-cache --parallel --continue",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",

@ -0,0 +1,13 @@
{
"name": "@tih/tailwind-config",
"version": "0.0.0",
"private": true,
"main": "index.js",
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"tailwindcss": "^3.1.8"
}
}

@ -7,20 +7,30 @@
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist",
"./styles.css": "./dist/styles.css"
},
"files": [
"dist/**"
],
"scripts": {
"build": "tsup src/index.tsx --format esm,cjs --dts --external react",
"build": "tsup src/index.tsx --format esm,cjs --dts --external react && tailwindcss -i ./src/styles.css -o ./dist/styles.css",
"dev": "concurrently \"tsup src/index.tsx --format esm,cjs --dts --external react --watch\" \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch\"",
"clean": "rm -rf dist",
"tsc": "tsc",
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
"lint": "eslint src/**/*.ts* --fix"
},
"dependencies": {
"clsx": "^1.2.1",
"next": "^12.3.1"
},
"devDependencies": {
"@tih/tailwind-config": "*",
"@tih/tsconfig": "*",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"concurrently": "^7.4.0",
"eslint": "^8.24.0",
"eslint-config-tih": "*",
"prettier-plugin-tailwindcss": "^0.1.13",

@ -0,0 +1,155 @@
import clsx from 'clsx';
import Link from 'next/link';
import type { UrlObject } from 'url';
import Spinner from '../Spinner';
export type ButtonDisplay = 'block' | 'inline';
export type ButtonSize = 'lg' | 'md' | 'sm';
export type ButtonVariant =
| 'primary'
| 'secondary'
| 'special'
| 'success'
| 'tertiary';
type Props = Readonly<{
addonPosition?: 'end' | 'start';
'aria-controls'?: string;
className?: string;
display?: ButtonDisplay;
href?: UrlObject | string;
icon?: (props: React.ComponentProps<'svg'>) => JSX.Element;
isDisabled?: boolean;
isLabelHidden?: boolean;
isLoading?: boolean;
label: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
size?: ButtonSize;
type?: 'button' | 'submit';
variant: ButtonVariant;
}>;
const sizeClasses: Record<ButtonSize, string> = {
lg: 'px-5 py-2.5',
md: 'px-4 py-2',
sm: 'px-2.5 py-1.5',
};
const iconOnlySizeClasses: Record<ButtonSize, string> = {
lg: 'p-3',
md: 'p-2',
sm: 'p-1.5',
};
const baseClasses: Record<ButtonSize, string> = {
lg: 'text-base rounded-xl',
md: 'text-sm rounded-lg',
sm: 'text-xs rounded-md',
};
const sizeIconSpacingEndClasses: Record<ButtonSize, string> = {
lg: 'ml-3 -mr-1 ',
md: 'ml-2 -mr-1 ',
sm: 'ml-2 -mr-0.5',
};
const sizeIconSpacingStartClasses: Record<ButtonSize, string> = {
lg: 'mr-3 -ml-1 ',
md: 'mr-2 -ml-1 ',
sm: 'mr-2 -ml-0.5',
};
const sizeIconClasses: Record<ButtonSize, string> = {
lg: '!h-5 !w-5',
md: '!h-5 !w-5',
sm: '!h-4 !w-4',
};
const variantClasses: Record<ButtonVariant, string> = {
primary: 'border-transparent text-white bg-primary-600 hover:bg-primary-500',
secondary:
'border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200',
special: 'border-slate-900 text-white bg-slate-900 hover:bg-slate-700',
success: 'border-transparent text-white bg-emerald-600 hover:bg-emerald-500',
tertiary: 'border-slate-300 text-slate-700 bg-white hover:bg-slate-50',
};
const variantDisabledClasses: Record<ButtonVariant, string> = {
primary: 'border-transparent text-slate-500 bg-slate-300',
secondary: 'border-transparent text-slate-400 bg-slate-200',
special: 'border-transparent text-slate-500 bg-slate-300',
success: 'border-transparent text-slate-500 bg-slate-300',
tertiary: 'border-slate-300 text-slate-400 bg-slate-100',
};
export default function Button({
addonPosition = 'end',
'aria-controls': ariaControls,
className,
display = 'inline',
href,
icon: Icon,
isDisabled = false,
isLabelHidden = false,
isLoading = false,
label,
size = 'md',
type = 'button',
variant,
onClick,
}: Props) {
const iconSpacingClass = (() => {
if (!isLabelHidden && addonPosition === 'start') {
return sizeIconSpacingStartClasses[size];
}
if (!isLabelHidden && addonPosition === 'end') {
return sizeIconSpacingEndClasses[size];
}
})();
const addOnClass = clsx(iconSpacingClass, sizeIconClasses[size]);
const addOn = isLoading ? (
<Spinner className={addOnClass} color="inherit" size="xs" />
) : Icon != null ? (
<Icon aria-hidden="true" className={addOnClass} />
) : null;
const children = (
<>
{addonPosition === 'start' && addOn}
{!isLabelHidden && label}
{addonPosition === 'end' && addOn}
</>
);
const commonProps = {
'aria-controls': ariaControls ?? undefined,
'aria-label': isLabelHidden ? label : undefined,
children,
className: clsx(
display === 'block' ? 'flex w-full justify-center' : 'inline-flex',
'whitespace-nowrap items-center border font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500',
isDisabled ? variantDisabledClasses[variant] : variantClasses[variant],
isDisabled && 'pointer-events-none',
isLabelHidden ? iconOnlySizeClasses[size] : sizeClasses[size],
baseClasses[size],
className,
),
disabled: isDisabled,
onClick,
};
if (href == null) {
return (
<button type={type === 'button' ? 'button' : 'submit'} {...commonProps} />
);
}
return (
<Link href={href}>
<a {...commonProps} />
</Link>
);
}

@ -0,0 +1,4 @@
import Button from './Button';
export * from './Button';
export default Button;

@ -10,7 +10,7 @@ export function CounterButton() {
fontWeight: 500,
padding: '1.5rem',
}}>
<p style={{ margin: '0 0 1.5rem 0' }}>
<p className="text-green-500" style={{ margin: '0 0 1.5rem 0' }}>
This component is from{' '}
<code
style={{

@ -0,0 +1,52 @@
import clsx from 'clsx';
export type SpinnerColor = 'default' | 'inherit';
export type SpinnerSize = 'lg' | 'md' | 'sm' | 'xs';
export type SpinnerDisplay = 'block' | 'inline';
type Props = Readonly<{
className?: string;
color?: SpinnerColor;
display?: SpinnerDisplay;
label?: string;
size: SpinnerSize;
}>;
const colorClasses: Record<SpinnerColor, string> = {
default: 'text-slate-400',
inherit: '',
};
const sizeClasses: Record<SpinnerSize, string> = {
lg: 'w-12 h-12 border-[6px]',
md: 'w-8 h-8 border-4',
sm: 'w-6 h-6 border-[3px]',
xs: 'w-4 h-4 border-2',
};
export default function Spinner({
className,
color = 'default',
display = 'inline',
label = 'Loading...',
size,
}: Props) {
const spinner = (
<div
className={clsx(
'inline-block animate-spin rounded-full border-current border-r-transparent',
colorClasses[color],
sizeClasses[size],
className,
)}
role="status">
<span className="sr-only">{label}</span>
</div>
);
if (display === 'block') {
return <div className="text-center">{spinner}</div>;
}
return spinner;
}

@ -0,0 +1,4 @@
import Spinner from './Spinner';
export * from './Spinner';
export default Spinner;

@ -1,2 +1,4 @@
export { default as Button } from './Button';
export * from './Button';
export { CounterButton } from './CounterButton';
export { NewTabLink } from './NewTabLink';

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,3 @@
const config = require('@tih/tailwind-config/tailwind.config.js');
module.exports = config;

@ -5332,6 +5332,15 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
@ -5515,6 +5524,21 @@ concat-stream@^1.5.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
concurrently@^7.4.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.4.0.tgz#bb0e344964bc172673577c420db21e963f2f7368"
integrity sha512-M6AfrueDt/GEna/Vg9BqQ+93yuvzkSKmoTixnwEJkH0LlcGrRC2eCmjeG1tLLHIYfpYJABokqSGyMcXjm96AFA==
dependencies:
chalk "^4.1.0"
date-fns "^2.29.1"
lodash "^4.17.21"
rxjs "^7.0.0"
shell-quote "^1.7.3"
spawn-command "^0.0.2-1"
supports-color "^8.1.0"
tree-kill "^1.2.2"
yargs "^17.3.1"
configstore@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
@ -5961,6 +5985,11 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
date-fns@^2.29.1:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -10217,7 +10246,7 @@ next-auth@~4.10.3:
preact-render-to-string "^5.1.19"
uuid "^8.3.2"
next@12.3.1:
next@12.3.1, next@^12.3.1:
version "12.3.1"
resolved "https://registry.yarnpkg.com/next/-/next-12.3.1.tgz#127b825ad2207faf869b33393ec8c75fe61e50f1"
integrity sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==
@ -12493,7 +12522,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^7.5.4:
rxjs@^7.0.0, rxjs@^7.5.4:
version "7.5.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39"
integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==
@ -13007,6 +13036,11 @@ space-separated-tokens@^1.0.0:
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
spawn-command@^0.0.2-1:
version "0.0.2-1"
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@ -13381,7 +13415,7 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
supports-color@^8.0.0:
supports-color@^8.0.0, supports-color@^8.1.0:
version "8.1.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
@ -14784,6 +14818,11 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.9:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^21.0.0:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
@ -14797,6 +14836,19 @@ yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.3.1:
version "17.6.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c"
integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.0.0"
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"

Loading…
Cancel
Save