Inspired by Steve Mould's video: Self-assembling material pops into 3D (YouTube)
Deployed on Vercel: https://cubes.hanl.in/
- Real-time Transformation Preview
- Create and design with a live preview, ensuring your creations come to life as you envision them.
- Synced Brush Cursor
- Improve precision and accuracy with a synced brush cursor that's visible in the preview.
- *Persistent Brush Settings
- Your preferred brush color, size, and opacity settings will remain intact even after reloading the app.
- Adaptable Dark and Light Themes
- Switch between dark and light themes or use your system settings by default.
- Smooth State Transitions
- Switch between different states with smooth animations or manually scrub through transitions for precise control.
- Optimized Responsive Design
- Maximizes screen real estate usage to provide an optimal design experience.
- Offline support
- Able to load and reload without an active network connection.
- Multilanguage support
- Available in English, German, and Taiwanese Mandarin.
Responsive Design with CSS aspect-ratio @media query
2023-10-01.12-56-48.mp4
- Next.js: React framework with server-side rendering (SSR) and static site generation (SSG).
- Single component dark mode toggle
- Simple i18n middleware implementation without using 3rd party libraries.
- Tailwind CSS: Utility-first CSS framework.
- Canvas API: Simple custom drawing implementation.
- CSS animations with custom
@keyframes
- Progressive Web App (PWA) optimized with offline capability.
- TypeScript
- Support static site generation (SSG), automatically generated as static HTML + JSON (uses
getStaticProps
). - Auto-detect the user's locale by default.
- Allow users to select their preferred locale and remember their preference.
- Display the default locale without a URL prefix.
- Allow users to manually change the locale in the URL and override the detected and saved locales.
Locale Priority | Description |
---|---|
URL locale (/locale) | The locale specified in the URL, e.g. /de or /en/about. |
Preferred locale | The user's preferred locale, saved in a cookie. |
Detected locale | The user's locale, detected using the accept-language header. |
Default locale | The website's default locale. |
Scenario | Routing Behavior |
---|---|
User visits the website for the first time and their locale is de. | The user is redirected to /de. |
User visits the website and their preferred locale is fr. | The user is redirected to /fr, even if their detected locale is de. |
User visits /en and their preferred locale is fr. | The user's preferred locale is changed to en, and they are redirected to /. |
User visits /de and their preferred locale is fr. | The user's preferred locale is ignored, and /de is displayed. |
Suppose a website has the following configuration:
- Default locale:
en
- Supported locales:
en
,de
,fr
A user visits the website for the first time and their locale is de
. The user is redirected to /de
because their detected locale is de
and there is a supported locale for that language.
The user then selects fr
as their preferred locale. The next time the user visits the website, they are redirected to /fr
because their preferred locale is fr
.
The user then visits /en
. Their preferred locale is changed to en
and they are redirected to /
. This is because the user's preferred locale has precedence over the detected locale.
npm i @formatjs/intl-localematcher negotiator
npm i -D @types/negotiator
Configure your default locale (defaultLocale
) and supported locales (locales
)
Server Components: Use getDictionary
to get the translation dictionary t
for the current locale
origami-moving-cubes-drawing-designer/src/i18n.ts
Lines 1 to 19 in 83eae6c
Handles locale detection, redirects, and rewrites, and sets cookies if manually navigated to /defaultLocale
origami-moving-cubes-drawing-designer/src/middleware.ts
Lines 1 to 79 in 84f6817
Translations are stored in /src/dictionaries
as JSON files
origami-moving-cubes-drawing-designer/src/dictionaries/tw.json
Lines 1 to 10 in 83eae6c
Client Components: Use DictionaryContext
to get the translation dictionary t
for the current locale
Move your layout.tsx
and page.tsx
files inside app/
into app/[lang]
, icons (favicon.ico
, icon.png
, apple-icon.png
) and manifest.ts
should stay in app/
.
In the root layout, use generateStaticParams
to statically generate all locale routes at build time (SSG) instead of on-demand at request time (SSR).
origami-moving-cubes-drawing-designer/src/app/[lang]/layout.tsx
Lines 1 to 10 in a3d20ea
In the root layout, set up DictionaryContext
by passing the translation dictionary t
from getDictionary
to DictionaryProvider
.
origami-moving-cubes-drawing-designer/src/app/[lang]/layout.tsx
Lines 58 to 74 in a3d20ea
You will likely need to implement your own locale selector component so it matches the styling of your site.
This example uses simple HTML <select> and <options> without any external libraries.
Set LOCALE_COOKIE
when the user chooses a new locale.
- Does not flash the wrong mode on page load. The website should immediately render in the light or dark mode that matches the user's system settings.
- Adds or removes the
dark
class for Tailwind CSS and also sets the CSScolor-scheme
property for scrollbars on the<html>
element. - The toggle button allows the user to override the system settings. The toggle button has three states:
Light
,System
, andDark
, withSystem
as the default. - If the toggle is set to
System
, the page should live update if the system settings are changed. - Theme selection is persisted through page changes and separate sessions using local storage.
- Does not block rendering of the UI until the React app is hydrated and renders. Only a few lines of JavaScript that set the light/dark mode attribute on the
<html>
element should block rendering. - Does not cause any terminal or browser errors. This includes errors during the server component render and the rendering of any of the React components in the browser.
- The first time the light/dark mode toggle is rendered on the client, it should already be in the correct state. Since this cannot be determined on the server, the toggle component may flash briefly on the client.
https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually
This property only applies one level deep to just the <html>
element, so it won't block hydration warnings on other elements.
The HydrationWarning
is caused by the few lines of JavaScript that add the dark
class to the <html>
element according to client settings before first rendering. This is used to prevent the wrong theme from flashing on page load.
Modify the styling to match your site.
This example is styled to match the toggle on the Official Next.js website, which is located in the bottom right corner.
All styling is done using Tailwind CSS classes and inline SVG for easy modification.