Move all files to inviqa-tailwindcss-example/

This commit is contained in:
Oliver Davies 2025-09-29 23:19:01 +01:00
parent 6d7993bbaa
commit 225421ac19
26 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,3 @@
/Dockerfile
/docker-compose.yaml
/node_modules/

View file

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

36
inviqa-tailwindcss-example/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -0,0 +1 @@
--modules-folder /node_modules

View file

@ -0,0 +1,16 @@
FROM node:16-bullseye AS base
ENV PATH=${PATH}:/node_modules/.bin
WORKDIR /app
RUN mkdir -p /node_modules \
&& chown node:node -R /app /node_modules
USER node
COPY --chown=node:node package.json *yarn* ./
RUN yarn install --frozen-lockfile
COPY --chown=node:node . .

View file

@ -0,0 +1,23 @@
# Inviqa Tailwind CSS example
This is a component from the [Inviqa](https://inviqa.com) website that I recreated as an example for a front-end community of practice session on utility-first CSS.
[The original version](https://play.tailwindcss.com/Yfmw8O5UNN) used a number of `@apply` rules to change the appearance based on a `data-theme` attribute.
After attending the [Pro Tailwind theming workshop](), I moved it into a Next.js application and refactored it to use CSS custom properties, and moved the button and corner styles into a custom plugin.
The `flex-basis` styles have also been moved into a separate plugin, and I've added dark mode support which wasn't in the original version.
## Themes
### Blue
![](./docs/blue.png)
### Purple
![](./docs/purple.png)
### Teal
![](./docs/teal.png)

View file

@ -0,0 +1,57 @@
function Button({ text, ...props }) {
return (
<a
className="inline-block py-2 px-5 mt-4 text-lg font-bold border-2 transition-colors duration-200 ease-in-out hover:bg-white focus:bg-white bg-secondary border-secondary text-tertiary hover:border-quinary hover:text-quinary focus:border-quinary focus:text-quinary"
href="#0"
>
{text}
</a>
);
}
export default function ImageAndText({
buttonText,
children,
imageUrl,
title,
...props
}) {
return (
<>
<div data-theme="teal" className="mx-auto max-w-6xl">
{/* Themes: blue, purple, teal */}
<h2 className="sr-only">Featured case study</h2>
<section className="font-sans text-black">
<div className="lg:flex lg:items-center fancy-corners fancy-corners--large fancy-corners--top-left fancy-corners--bottom-right">
<div className="flex-shrink-0 self-stretch sm:flex-basis-40 md:flex-basis-50 xl:flex-basis-60">
<div className="h-full">
<article className="h-full">
<div className="h-full">
<img
className="object-cover h-full w-full"
src={imageUrl}
width="733"
height="412"
alt='""'
typeof="foaf:Image"
/>
</div>
</article>
</div>
</div>
<div className="p-6 bg-grey dark:bg-[#222222]">
<div className="leading-relaxed dark:text-white">
<h2 className="text-4xl font-bold leading-tight">{title}</h2>
<div className="mt-4 markup">{children}</div>
<p>
<Button text="Explore this event" />
</p>
</div>
</div>
</div>
</section>
</div>
</>
);
}

View file

@ -0,0 +1,9 @@
services:
app:
build:
context: "."
volumes:
- "./:/app"
ports:
- "3000:3000"
command: ["next", "dev"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View file

@ -0,0 +1,14 @@
default:
just --list
down:
docker compose down
up *args:
docker compose up {{args}}
yarn *args:
just _run yarn {{args}}
_run +args:
docker compose run --rm --entrypoint bash app -c {{args}}

View file

@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}
module.exports = nextConfig

View file

@ -0,0 +1,24 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "12.3.0",
"postcss-nested": "^5.0.6",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"autoprefixer": "^10.4.8",
"eslint": "8.23.0",
"eslint-config-next": "12.3.0",
"postcss": "^8.4.16",
"tailwindcss": "^3.1.8"
}
}

View file

@ -0,0 +1,7 @@
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

View file

@ -0,0 +1,28 @@
import ImageAndText from "../components/ImageAndText";
export default function Home() {
return (
<div>
<div className="flex flex-col justify-center p-24 min-h-screen bg-white dark:bg-[#111111]">
<ImageAndText
buttonText={"Explore this event"}
imageUrl={"/image.jpeg"}
title={"CXcon: Experience Transformation"}
>
<p>
Our second CXcon in October was dedicated to experience
transformation. The free one-day virtual event&nbsp;brought together
230+ heads of digital, thought leaders, and UX practitioners to
discuss all aspects of experience design..
</p>
<p>
In a jam-packed day filled with keynote sessions, panels, and
virtual networking we explored topics including design leadership,
UX ethics, designing for emotion and innovation at scale.
</p>
</ImageAndText>
</div>
</div>
);
}

View file

@ -0,0 +1,21 @@
let plugin = require("tailwindcss/plugin");
let flexBasisPlugin = plugin.withOptions(
function (options) {
return function ({ addUtilities }) {
let values = options?.values ?? [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
values.forEach((value) => {
addUtilities({ [`.flex-basis-${value}`]: { flexBasis: `${value}%` } });
});
};
},
function (options) {
return {
values: options?.values ?? [],
};
}
);
module.exports = flexBasisPlugin;

View file

@ -0,0 +1,45 @@
let plugin = require("tailwindcss/plugin");
let multiThemePlugin = plugin.withOptions(
function (options) {
return function ({ addBase }) {
let themes = options?.themes ?? [];
addBase(generateForRoot(themes));
Object.keys(themes).forEach((themeName) => {
addBase(generateForTheme(themes, themeName));
});
};
},
function (options) {
return {
themes: options?.themes ?? [],
};
}
);
function generateForRoot(themes) {
let defaultThemeName = Object.keys(themes)[0];
return Object.entries(themes[defaultThemeName]).map(([colourName, value]) => {
return {
":root": {
[`--color-${colourName}`]: value,
},
};
});
}
function generateForTheme(themes, themeName) {
return Object.entries(themes[themeName]).map(([colourName, value]) => {
return {
[`[data-theme=${themeName}]`]: {
[`--color-${colourName}`]: value,
},
};
});
}
module.exports = multiThemePlugin;

View file

@ -0,0 +1,7 @@
module.exports = {
plugins: {
'postcss-nested': {},
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,129 @@
.container {
padding: 0 2rem;
}
.main {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.footer {
display: flex;
flex: 1;
padding: 2rem 0;
border-top: 1px solid #eaeaea;
justify-content: center;
align-items: center;
}
.footer a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
margin: 4rem 0;
line-height: 1.5;
font-size: 1.5rem;
}
.code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
}
.card {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
max-width: 300px;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
margin-left: 0.5rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}
@media (prefers-color-scheme: dark) {
.card,
.footer {
border-color: #222;
}
.code {
background: #111;
}
.logo img {
filter: invert(1);
}
}

View file

@ -0,0 +1,96 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.markup {
* + * {
@apply mt-4;
}
}
.fancy-corners {
position: relative;
background-color: #fff;
transform-style: preserve-3d;
}
.fancy-corners:after,
.fancy-corners:before {
position: absolute;
width: 8em;
height: 8em;
background-color: theme("colors.primary");
background-size: 100% 100%;
background-position: 0 0;
transform: translateZ(-1px);
z-index: -1;
}
.fancy-corners[class*="--bottom"]:after,
.fancy-corners[class*="--top"]:before {
content: "";
}
.fancy-corners--large[class*="--top"]:before {
top: -2.05882em;
}
.fancy-corners--large[class*="--bottom"]:after {
bottom: -2.05882em;
}
.fancy-corners--large[class*="--bottom"][class*="--bottom-right"]:after {
right: -2.05882em;
}
.fancy-corners[class*="--bottom"]:after {
bottom: -1.17647em;
}
.fancy-corners[class*="--bottom"][class*="--bottom-right"]:after {
right: -1.17647em;
}
.fancy-corners[class*="--top"]:before {
top: -1.17647em;
}
.fancy-corners[class*="--top"][class*="--top-left"]:before {
left: -1.17647em;
}
.fancy-corners--top-left:before {
-webkit-mask-image: linear-gradient(-45deg, #fff 85.5%, transparent 0);
mask-image: linear-gradient(-45deg, #fff 85.5%, transparent 0);
}
.fancy-corners--bottom-right:after {
-webkit-mask-image: linear-gradient(-45deg, transparent 14.5%, #fff 0);
mask-image: linear-gradient(-45deg, transparent 14.5%, #fff 0);
}
@screen lg {
.fancy-corners--large:after,
.fancy-corners--large:before {
width: 14em;
height: 14em;
}
.fancy-corners--large[class*="--top"][class*="--top-left"]:before {
left: -2.05882em;
}
.fancy-corners--large[class*="--top"]:before {
top: -2.05882em;
}
.fancy-corners--large[class*="--bottom"]:after {
bottom: -2.05882em;
}
.fancy-corners--large[class*="--bottom"][class*="--bottom-right"]:after {
right: -2.05882em;
}
}
}

View file

@ -0,0 +1,60 @@
let flexBasisPlugin = require("./plugins/flex-basis-plugin");
let multiThemePlugin = require("./plugins/multi-theme-plugin");
let colors = {
black: "#333333",
blue: "#334982",
grey: "#f3f3f3",
orange: "#fdb913",
pink: "#e40087",
purple: "#782b8f",
red: "#dd372f",
teal: "#00857d",
white: "#fff",
};
let themes = {
blue: {
quaternary: colors.red,
quinary: colors.red,
primary: colors.blue,
secondary: colors.red,
tertiary: colors.white,
},
purple: {
quaternary: colors.white,
quinary: colors.purple,
primary: colors.purple,
secondary: colors.orange,
tertiary: colors.purple,
},
teal: {
quaternary: colors.pink,
quinary: colors.pink,
primary: colors.teal,
secondary: colors.pink,
tertiary: colors.white,
},
};
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
colors: {
...colors,
quaternary: "var(--color-quaternary)",
quinary: "var(--color-quinary)",
primary: "var(--color-primary)",
secondary: "var(--color-secondary)",
tertiary: "var(--color-tertiary)",
},
},
plugins: [flexBasisPlugin, multiThemePlugin({ themes })],
};

File diff suppressed because it is too large Load diff