Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): component theming #5170

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5fa49fb
feat(ui): component theming (#4883)
dbanksdesign Apr 18, 2024
9084dc2
Merge branch 'main' into component-theming/main
dbanksdesign Apr 18, 2024
4652209
fixing test
dbanksdesign Apr 18, 2024
d675c4e
fixes
dbanksdesign Apr 19, 2024
89c5cfe
LFG
dbanksdesign Apr 23, 2024
bff60ab
updates
dbanksdesign Jun 19, 2024
014ab1b
Merge branch 'main' into component-theming/main
dbanksdesign Jun 19, 2024
3b7594b
updates
dbanksdesign Jun 20, 2024
861d1c6
adding more components and fixing things
dbanksdesign Jun 20, 2024
74cc803
fixes
dbanksdesign Jun 20, 2024
d07f5ae
starting to migrate React primitive classnames
dbanksdesign Jun 20, 2024
f38ebe1
more work
dbanksdesign Jun 21, 2024
3929d5b
working
dbanksdesign Jun 21, 2024
1027758
migrating more primitives
dbanksdesign Jun 21, 2024
10d4556
finishing removing ComponentClassNames
dbanksdesign Jun 24, 2024
dc83aa2
update accordion
dbanksdesign Jun 24, 2024
8569e78
undo primitives changes
dbanksdesign Jun 24, 2024
7031852
fixes
dbanksdesign Jun 24, 2024
c8ca4ba
fixing size increase
dbanksdesign Jun 25, 2024
1790554
fixing size increase
dbanksdesign Jun 25, 2024
098b9ce
fixing size increase
dbanksdesign Jun 25, 2024
57198dd
fixing docs page
dbanksdesign Jun 25, 2024
26e8fbd
updating internal names, removing unnecessary exports
dbanksdesign Jun 26, 2024
9b73ca0
final cleanup
dbanksdesign Jun 26, 2024
112be0f
add changeset
dbanksdesign Jun 26, 2024
2a490a8
removing one more type export
dbanksdesign Jun 26, 2024
fa620ea
changing to defineComponentTheme and adding containerProps function
dbanksdesign Jun 26, 2024
1b92c66
adding type benchmarks for themeing functions
dbanksdesign Jun 26, 2024
98b1142
cleanup
dbanksdesign Jun 26, 2024
745151e
Merge branch 'main' into component-theming/main
dbanksdesign Jun 26, 2024
6dfa6a5
fiddling with the yarn.lock
dbanksdesign Jun 26, 2024
86c1118
more fiddling
dbanksdesign Jun 26, 2024
c1bbcdb
please work
dbanksdesign Jun 26, 2024
badf3a7
fixing tests
dbanksdesign Jun 26, 2024
c3e0560
Merge branch 'main' into component-theming/main
dbanksdesign Jun 26, 2024
b68c1be
Merge branch 'main' into component-theming/main
dbanksdesign Jun 27, 2024
f937f27
adding animation support
dbanksdesign Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .changeset/rad-cat-shred.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
"@aws-amplify/ui": minor
"@aws-amplify/ui-react": minor
---

feat(ui): experimental component theming

This feature lets you fully style and theme built-in components even if there is no design token available. For example, previously you could not add a box shadow or gradient background to the built-in Button component unless you wrote plain CSS. Now you can style every CSS property for all the built-in components with type-safety!

This also lets you define your own components and style them in the same type-safe way with zero runtime computation.

### defineComponentTheme()

```ts
import { defineComponentTheme } from '@aws-amplify/ui-react/server';

export const buttonTheme = defineComponentTheme({
// because 'button' is a built-in component, we get type-safety and hints
// based on the theme shape of our button
name: 'button',
theme: (tokens) => {
return {
textAlign: 'center',
padding: tokens.space.xl,
_modifiers: {
primary: {
backgroundColor: tokens.colors.primary[20],
},
},
};
},
});
```


### createTheme()

The theme object passed to `createTheme` now has an optional `components` array which is an array of component themes.

```ts
export const theme = createTheme({
name: 'my-theme',
components: [
buttonTheme,
customComponentTheme,
]
})
```

### <Theme /> RSC

```tsx
import { Theme } from '@aws-amplify/ui-react/server';
import { theme } from '@/theme';

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<Theme theme={theme} colorMode="system">
{children}
</Theme>
)
}
```
6 changes: 6 additions & 0 deletions examples/next-app-router/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react-hooks/exhaustive-deps": "error" // override next eslint default
}
}
36 changes: 36 additions & 0 deletions examples/next-app-router/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
Empty file.
4 changes: 4 additions & 0 deletions examples/next-app-router/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = nextConfig;
22 changes: 22 additions & 0 deletions examples/next-app-router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "next-app-router",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@aws-amplify/ui-react": "^6.1.0",
"next": "^14.1.1",
"react": "18.2.0",
"react-dom": "^18",
"react-icons": "^4.3.1"
},
"devDependencies": {
"@types/node": "^14.14.31",
"eslint-config-next": "^13.5.5"
}
}
5 changes: 5 additions & 0 deletions examples/next-app-router/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {},
},
};
Binary file added examples/next-app-router/src/app/favicon.ico
Binary file not shown.
7 changes: 7 additions & 0 deletions examples/next-app-router/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import '@aws-amplify/ui-react/styles/reset.css';
@import '@aws-amplify/ui-react/styles.css';

[data-amplify-theme] {
background-color: var(--amplify-colors-background-primary);
color: var(--amplify-colors-font-primary);
}
19 changes: 19 additions & 0 deletions examples/next-app-router/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
title: 'Amplify UI Next App Router Example',
description: 'Generated by create next app',
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
19 changes: 19 additions & 0 deletions examples/next-app-router/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Avatar } from '@/components/Avatar';
import { theme } from '@/theme';
import { ThemeStyle } from '@aws-amplify/ui-react/server';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import ThemeStyle.
import Link from 'next/link';

export default function Home() {
return (
<main>
<Link href="/theme">Theme</Link>
<Link href="/theme-switcher">Theme Switcher</Link>
<Avatar />

<div {...theme.containerProps({ colorMode: 'dark' })}>
<h2>I'm dark</h2>
<Avatar />
</div>
</main>
);
}
25 changes: 25 additions & 0 deletions examples/next-app-router/src/app/theme-switcher/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';
import type { ColorMode } from '@aws-amplify/ui-react';
import { ThemeStyle } from '@aws-amplify/ui-react/server';
import { theme } from '@/theme';
import { Header } from '@/components/Header';
import ThemeToggle from '@/components/ThemeToggle';
import { cookies } from 'next/headers';

export default function Layout({ children }: { children: React.ReactNode }) {
const cookieStore = cookies();
const colorMode = cookieStore.get('colorMode');

return (
<div
{...theme.containerProps({ colorMode: colorMode?.value as ColorMode })}
>
<Header>
Amplify UI RSC
<ThemeToggle initialValue={colorMode?.value as ColorMode} />
</Header>
{children}
<ThemeStyle theme={theme} />
</div>
);
}
32 changes: 32 additions & 0 deletions examples/next-app-router/src/app/theme-switcher/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ThemeStyle } from '@aws-amplify/ui-react/server';
import { Avatar } from '@/components/Avatar';
import { theme } from '@/theme';

export default function ThemeSwitcherPage() {
console.log('server!');
return (
<div
className="flex w-full flex-row"
style={{
backgroundColor: `var(--breakpoint-large, #f90)`,
}}
>
<Avatar size="small" />
<Avatar />
<Avatar size="large" />
<div {...theme.containerProps({ colorMode: 'dark' })}>
<div
className="flex w-full flex-row"
style={{
backgroundColor: `${theme.tokens.colors.background.primary}`,
}}
>
<Avatar size="small" />
<Avatar />
<Avatar size="large" />
</div>
</div>
<ThemeStyle theme={theme} />
</div>
);
}
30 changes: 30 additions & 0 deletions examples/next-app-router/src/app/theme/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
ThemeStyle,
createComponentClasses,
} from '@aws-amplify/ui-react/server';
import { theme } from '@/theme';

const headingClasses = createComponentClasses({ name: 'heading' });

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex w-full flex-row">
<div className="flex-1 p-2">
<h2 className={headingClasses({ _modifiers: ['2'] })}>Custom theme</h2>

<ThemeStyle theme={theme} />
<section {...theme.containerProps({ colorMode: 'dark' })}>
{children}
</section>
</div>
<div className="flex-1 p-2">
<h2 className={headingClasses({ _modifiers: ['2'] })}>Default theme</h2>
{children}
</div>
</div>
);
}
75 changes: 75 additions & 0 deletions examples/next-app-router/src/app/theme/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use client';
Fixed Show fixed Hide fixed
import { Avatar } from '@/components/Avatar';
import { MyClientComponent } from '@/components/ClientComponent';
import { MyServerComponent } from '@/components/ServerComponent';
import { theme } from '@/theme';
import {
Alert,
Badge,
BadgeProps,
Button,
ButtonProps,
Flex,
Heading,
Text,
} from '@aws-amplify/ui-react';

const colorThemes = [undefined, 'success', 'info', 'warning', 'error'];

export default function ThemePage() {
return (
<Flex direction="column">
<Heading level={3}>Badges</Heading>
<Flex direction="row">
{colorThemes.map((colorTheme, i) => (
<Badge
key={`${i}-${colorTheme}`}
variation={colorTheme as BadgeProps['variation']}
>
{colorTheme || 'default'}
</Badge>
))}
</Flex>
<Heading level={3}>Buttons</Heading>
<Flex direction="row">
{colorThemes.map((colorTheme, i) => (
<Button
key={`${i}-${colorTheme}`}
colorTheme={colorTheme as ButtonProps['colorTheme']}
>
{colorTheme || 'default'}
</Button>
))}
</Flex>
<Flex direction="row">
{colorThemes.map((colorTheme, i) => (
<Button
key={`${i}-${colorTheme}`}
variation="link"
colorTheme={colorTheme as ButtonProps['colorTheme']}
>
{colorTheme || 'default'}
</Button>
))}
</Flex>
<Flex direction="row">
{colorThemes.map((colorTheme, i) => (
<Button
key={`${i}-${colorTheme}`}
variation="primary"
colorTheme={colorTheme as ButtonProps['colorTheme']}
>
{colorTheme || 'default'}
</Button>
))}
</Flex>
<Avatar isDisabled />
<MyClientComponent />
<MyServerComponent />
<Text color={theme.tokens.colors.font.success}>Success!</Text>
<Alert heading="Hello" />
<Alert heading="Hello success" variation="success" />
<Alert heading="Hello" variation="info" />
</Flex>
);
}
Loading
Loading