I want to make an animated tab like:
I am using React with Tailwind. This is my code:
import React from 'react'
import clsx from 'clsx'
export const Modal = () => {
const [theme, setTheme] = React.useState<'light' | 'dark' | 'system'>('light')
return (
<div className="flex mx-2 mt-2 rounded-md bg-blue-gray-100">
<div
className={clsx('flex-1 py-1 my-2 ml-2 text-center rounded-md', {
'bg-white': theme === 'light',
'transition duration-1000 ease-out transform translate-x-10':
theme !== 'light',
})}
>
<button
className={clsx(
'w-full text-sm cursor-pointer select-none focus:outline-none',
{
'font-bold text-blue-gray-900': theme === 'light',
'text-blue-gray-600': theme !== 'light',
}
)}
onClick={() => {
setTheme('light')
}}
>
Light
</button>
</div>
<div
className={clsx('flex-1 py-1 my-2 ml-2 text-center rounded-md', {
'bg-white': theme === 'dark',
})}
>
<button
className={clsx(
'w-full text-sm cursor-pointer select-none focus:outline-none',
{
'font-bold text-blue-gray-900': theme === 'dark',
'text-blue-gray-600': theme !== 'dark',
}
)}
onClick={() => {
setTheme('dark')
}}
>
Dark
</button>
</div>
<div
className={clsx('flex-1 py-1 my-2 mr-2 text-center rounded-md', {
'bg-white': theme === 'system',
})}
>
<button
className={clsx(
'w-full text-sm cursor-pointer select-none focus:outline-none',
{
'font-bold text-blue-gray-900': theme === 'system',
'text-blue-gray-600': theme !== 'system',
}
)}
onClick={() => {
setTheme('system')
}}
>
System
</button>
</div>
</div>
)
}
But it looks like:
As I use translate-x-10
when the theme is not light
, therefore the text moves as well.
I would love to make the UI exactly as the above one while still using buttons for the actual tabs.
Minimal Codesandbox → https://codesandbox.io/s/mobx-theme-change-n1nvg?file=/src/App.tsx
How do I do it?
Turns out, it is possible with pure Tailwind.
module.exports = {
theme: {
extend: {
translate: {
200: '200%',
},
},
},
}
import * as React from "react"
import { observer } from "mobx-react"
import clsx from "clsx"
import { useStore } from "./context"
const AppTheme = observer(() => {
const {
theme: { app },
updateTheme,
} = useStore()
return (
<>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-2">
<h4 className="text-xl font-bold text-gray-800">Background</h4>
</div>
</div>
<div className="relative mx-2 mt-2 rounded-md bg-gray-100">
<div
id="slider"
className={clsx(
"absolute inset-y-0 w-1/3 h-full px-4 py-1 transition-transform transform",
{
"translate-x-0": app === "light",
"translate-x-full": app === "dark",
"translate-x-200": app === "system",
},
)}
style={
app === "system"
? {
transform: "translateX(200%)", // if you added `translate-x-200` to `tailwind.config.js` then you can remove the `style` tag completely
}
: {}
}
>
<div
className={clsx(
"w-full h-full bg-white rounded-md",
{
active: app === "light",
"bg-gray-600": app === "dark",
},
{
// needs to be separate object otherwise dark/light & system keys overlap resulting in a visual bug
["bg-gray-600"]: app === "system",
},
)}
></div>
</div>
<div className="relative flex w-full h-full">
<button
tabIndex={0}
className={clsx(
"py-1 my-2 ml-2 w-1/3 text-sm cursor-pointer select-none focus:outline-none",
{
active: app === "light",
"font-bold text--gray-900": app === "light",
"text--gray-600": app !== "light",
},
)}
onKeyUp={(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Tab")
updateTheme({
app: "light",
})
}}
onClick={() => {
updateTheme({
app: "light",
})
}}
>
Light
</button>
<button
tabIndex={0}
className={clsx(
"py-1 my-2 ml-2 w-1/3 text-sm cursor-pointer select-none focus:outline-none",
{
active: app === "dark",
"font-bold text-white": app === "dark",
"text--gray-600": app !== "dark",
},
)}
onKeyUp={(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Tab")
updateTheme({
app: "dark",
})
}}
onClick={() => {
updateTheme({
app: "dark",
})
}}
>
Dark
</button>
<button
tabIndex={0}
className={clsx(
"py-1 my-2 ml-2 w-1/3 text-sm cursor-pointer select-none focus:outline-none",
{
active: app === "system",
"font-bold text-white": app === "system",
"text--gray-600": app !== "system",
},
)}
onKeyUp={(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Tab")
updateTheme({
app: "system",
})
}}
onClick={() => {
updateTheme({
app: "system",
})
}}
>
System
</button>
</div>
</div>
</>
)
})
export default observer(function App() {
return <AppTheme />
})
Codesandbox → https://codesandbox.io/s/mobx-theme-change-animated-18gc6?file=/src/App.tsx
Idk why it isn't animating on Codesandbox but it works locally. Maybe a Codesandbox bug :)