diff --git a/README.md b/README.md index b04303e..ef5dda5 100644 --- a/README.md +++ b/README.md @@ -2441,6 +2441,18 @@ export default withShurikenUI({ bgHover: 'black/20', bgHoverDark: 'white/20', }, + themeToggle: { + size: '9', + rounded: 'full', + duration: '300', + ringDark: 'muted-900', + inner: { + size: '9', + rounded: 'full', + bg: 'white', + bgDark: 'muted-800', + border: 'muted-300', + borderDark: 'muted-700' tabSlider: { track: { bg: 'muted-100', @@ -2833,6 +2845,17 @@ export default withShurikenUI({ input: { size: 'full', }, + sun: { + size: '5', + duration: '300', + text: 'yellow-400', + }, + moon: { + size: '5', + duration: '300', + text: 'yellow-400', + }, + }, primary: 'primary-400', info: 'info-400', success: 'success-400', diff --git a/src/plugins/components/index.ts b/src/plugins/components/index.ts index d5d34d4..756f74f 100644 --- a/src/plugins/components/index.ts +++ b/src/plugins/components/index.ts @@ -37,6 +37,7 @@ import select from './select' import radio from './radio' import progressCircle from './progress-circle' import slimscroll from './slimscroll' +import themeToggle from './theme-toggle' import tabSlider from './tab-slider' import toast from './toast' import tabs from './tabs' @@ -85,6 +86,7 @@ const components = [ radio, progressCircle, slimscroll, + themeToggle, tabSlider, toast, tabs, diff --git a/src/plugins/components/theme-toggle.ts b/src/plugins/components/theme-toggle.ts new file mode 100644 index 0000000..4a6d410 --- /dev/null +++ b/src/plugins/components/theme-toggle.ts @@ -0,0 +1,92 @@ +import plugin from 'tailwindcss/plugin' +import { defu } from 'defu' +import { type PluginOption, defaultPluginOptions } from '../options' + +const defaultThemeToggleConfig = { + size: '9', + rounded: 'full', + duration: '300', + ringDark: 'muted-900', + inner: { + size: '9', + rounded: 'full', + bg: 'white', + bgDark: 'muted-800', + border: 'muted-300', + borderDark: 'muted-700', + }, + input: { + size: 'full', + }, + sun: { + size: '5', + duration: '300', + text: 'yellow-400', + }, + moon: { + size: '5', + duration: '300', + text: 'yellow-400', + }, +} + +export default plugin.withOptions( + function (options: PluginOption) { + const { prefix } = defu(options, defaultPluginOptions) + + return function ({ addComponents, theme }) { + const config = theme( + 'shurikenUi.themeToggle' + ) satisfies typeof defaultThemeToggleConfig + + addComponents({ + [`.${prefix}-theme-toggle`]: { + [`@apply ${prefix}-focus relative block h-${config.size} w-${config.size} shrink-0 overflow-hidden rounded-${config.rounded} transition-all duration-${config.duration} focus-visible:outline-2 dark:ring-offset-${config.ringDark}`]: + {}, + + [`.${prefix}-theme-toggle-inner`]: { + [`@apply relative block h-${config.inner.size} w-${config.inner.size} rounded-${config.inner.rounded} bg-${config.inner.bg} dark:bg-${config.inner.bgDark} border border-${config.inner.border} dark:border-${config.inner.borderDark}`]: + {}, + }, + [`.${prefix}-theme-toggle-input`]: { + [`@apply absolute start-0 top-0 z-[2] h-${config.input.size} w-${config.input.size} cursor-pointer opacity-0`]: + {}, + }, + [`.${prefix}-sun`]: { + [`@apply pointer-events-none absolute start-1/2 top-1/2 block h-${config.sun.size} w-${config.sun.size} text-${config.sun.text} transition-all duration-${config.sun.duration} -translate-y-1/2 translate-x-[-50%] rtl:translate-x-[50%]`]: + {}, + }, + [`.${prefix}-moon`]: { + [`@apply pointer-events-none absolute start-1/2 top-1/2 block h-${config.moon.size} w-${config.moon.size} text-${config.moon.text} transition-all duration-${config.moon.duration} translate-x-[-50%] rtl:translate-x-[45%]`]: + {}, + }, + [`.${prefix}-theme-toggle-input:not(:checked) ~ .${prefix}-theme-toggle-inner .${prefix}-sun`]: + { + [`@apply -translate-y-1/2 opacity-100`]: {}, + }, + [`.${prefix}-theme-toggle-input:checked ~ .${prefix}-theme-toggle-inner .${prefix}-sun`]: + { + [`@apply translate-y-[-150%] opacity-0`]: {}, + }, + [`.${prefix}-theme-toggle-input:not(:checked) ~ .${prefix}-theme-toggle-inner .${prefix}-moon`]: + { + [`@apply translate-y-[-150%] opacity-0`]: {}, + }, + [`.${prefix}-theme-toggle-input:checked ~ .${prefix}-theme-toggle-inner .${prefix}-moon`]: + { + [`@apply -translate-y-1/2 opacity-100`]: {}, + }, + }, + }) + } + }, + function () { + return { + theme: { + shurikenUi: { + themeToggle: defaultThemeToggleConfig, + }, + }, + } + } +)