From 2e8adc3e98ee11ab9521a91abcbd14c964592868 Mon Sep 17 00:00:00 2001 From: Biya Paul <77636493+bpsmartdesign@users.noreply.github.com> Date: Sun, 23 Jul 2023 23:15:30 +0100 Subject: [PATCH] feat: create dropdown plugin component (#20) Co-authored-by: Sacha Stafyniak --- README.md | 96 +++++++++ src/plugins/components/dropdown-divider.ts | 37 ++++ src/plugins/components/dropdown.ts | 223 ++++++++++++++++++++- src/plugins/components/index.ts | 2 + 4 files changed, 348 insertions(+), 10 deletions(-) create mode 100644 src/plugins/components/dropdown-divider.ts diff --git a/README.md b/README.md index 6bc9248..865ed1f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,102 @@ export default withShurikenUI({ theme: { extend: { shurikenUi: { + dropdown: { + textPosition: 'start', + contextButton: { + ringOffsetDark: 'muted-900', + size: '9', + rounded: 'full', + duration: '300', + ringGroupHover: 'offset-4', + inner: { + border: 'muted-200', + borderDark: 'muted-700', + bg: 'white', + bgDark: 'muted-800', + size: '9', + rounded: 'full', + }, + icon: { + text: 'muted-400', + size: '5', + duration: '300', + }, + }, + textButton: { + text: 'muted-400', + inner: { + font: 'sans', + }, + }, + chevron: { + size: '4', + duration: '300', + }, + dropdownMenu: { + shadow: 'lg', + shadowColor: 'muted-500/10', + shadowColorDark: 'muted-800/10', + headerTitle: { + font: 'heading', + fontSize: 'medium', + text: 'muted-500', + textDark: 'muted-200', + textSize: 'xs', + }, + rounded: { + default: 'md', + smooth: 'lg', + curved: 'xl', + }, + white: { + bg: 'white', + bgDark: 'muted-800', + border: 'muted-200', + borderDark: 'muted-700', + }, + whiteContrast: { + bg: 'white', + bgDark: 'muted-950', + border: 'muted-200', + borderDark: 'muted-800', + }, + muted: { + bg: 'muted-100', + bgDark: 'muted-800', + border: 'muted-200', + borderDark: 'muted-700', + }, + mutedContrast: { + bg: 'muted-100', + bgDark: 'muted-950', + border: 'muted-200', + borderDark: 'muted-800', + }, + primary: { + bg: 'primary-500/10', + border: 'primary-500', + }, + info: { + bg: 'info-500/10', + border: 'info-500', + }, + success: { + bg: 'success-500/10', + border: 'success-500', + }, + warning: { + bg: 'warning-500/10', + border: 'warning-500', + }, + danger: { + bg: 'danger-500/10', + border: 'danger-500', + }, + }, + hover: { + ring: 'muted-200', + ringDark: 'muted-700/70' card: { size: 'full', duration: '300', diff --git a/src/plugins/components/dropdown-divider.ts b/src/plugins/components/dropdown-divider.ts new file mode 100644 index 0000000..4ddc949 --- /dev/null +++ b/src/plugins/components/dropdown-divider.ts @@ -0,0 +1,37 @@ +import plugin from 'tailwindcss/plugin' +import { defu } from 'defu' +import { type PluginOption, defaultPluginOptions } from '../options' + +const defaultDropdownDividerConfig = { + space: '2', + border: 'muted-200', + borderDark: 'muted-600', +} + +export default plugin.withOptions( + function (options: PluginOption) { + const { prefix } = defu(options, defaultPluginOptions) + + return function ({ addComponents, theme }) { + const config = theme( + 'shurikenUi.dropdownDivider' + ) satisfies typeof defaultDropdownDividerConfig + + addComponents({ + [`.${prefix}-dropdown-divider`]: { + [`@apply my-${config.space} block h-px w-full border-t border-${config.border} dark:border-${config.borderDark}`]: + {}, + }, + }) + } + }, + function () { + return { + theme: { + shurikenUi: { + dropdownDivider: defaultDropdownDividerConfig, + }, + }, + } + } +) diff --git a/src/plugins/components/dropdown.ts b/src/plugins/components/dropdown.ts index 4ddc949..7c55828 100644 --- a/src/plugins/components/dropdown.ts +++ b/src/plugins/components/dropdown.ts @@ -2,10 +2,103 @@ import plugin from 'tailwindcss/plugin' import { defu } from 'defu' import { type PluginOption, defaultPluginOptions } from '../options' -const defaultDropdownDividerConfig = { - space: '2', - border: 'muted-200', - borderDark: 'muted-600', +const defaultDropdownConfig = { + textPosition: 'start', + contextButton: { + ringOffsetDark: 'muted-900', + size: '9', + rounded: 'full', + duration: '300', + ringGroupHover: 'offset-4', + inner: { + border: 'muted-200', + borderDark: 'muted-700', + bg: 'white', + bgDark: 'muted-800', + size: '9', + rounded: 'full', + }, + icon: { + text: 'muted-400', + size: '5', + duration: '300', + }, + }, + textButton: { + text: 'muted-400', + inner: { + font: 'sans', + }, + }, + chevron: { + size: '4', + duration: '300', + }, + dropdownMenu: { + shadow: 'lg', + shadowColor: 'muted-500/10', + shadowColorDark: 'muted-800/10', + headerTitle: { + font: 'heading', + fontSize: 'medium', + text: 'muted-500', + textDark: 'muted-200', + textSize: 'xs', + }, + rounded: { + default: 'md', + smooth: 'lg', + curved: 'xl', + }, + white: { + bg: 'white', + bgDark: 'muted-800', + border: 'muted-200', + borderDark: 'muted-700', + }, + whiteContrast: { + bg: 'white', + bgDark: 'muted-950', + border: 'muted-200', + borderDark: 'muted-800', + }, + muted: { + bg: 'muted-100', + bgDark: 'muted-800', + border: 'muted-200', + borderDark: 'muted-700', + }, + mutedContrast: { + bg: 'muted-100', + bgDark: 'muted-950', + border: 'muted-200', + borderDark: 'muted-800', + }, + primary: { + bg: 'primary-500/10', + border: 'primary-500', + }, + info: { + bg: 'info-500/10', + border: 'info-500', + }, + success: { + bg: 'success-500/10', + border: 'success-500', + }, + warning: { + bg: 'warning-500/10', + border: 'warning-500', + }, + danger: { + bg: 'danger-500/10', + border: 'danger-500', + }, + }, + hover: { + ring: 'muted-200', + ringDark: 'muted-700/70', + }, } export default plugin.withOptions( @@ -14,13 +107,123 @@ export default plugin.withOptions( return function ({ addComponents, theme }) { const config = theme( - 'shurikenUi.dropdownDivider' - ) satisfies typeof defaultDropdownDividerConfig + 'shurikenUi.dropdown' + ) satisfies typeof defaultDropdownConfig addComponents({ - [`.${prefix}-dropdown-divider`]: { - [`@apply my-${config.space} block h-px w-full border-t border-${config.border} dark:border-${config.borderDark}`]: - {}, + [`.${prefix}-dropdown`]: { + [`@apply text-${config.textPosition}`]: {}, + + [`.${prefix}-menu`]: { + [`@apply relative`]: {}, + }, + [`.${prefix}-context-button`]: { + [`@apply dark:ring-offset-${config.contextButton.ringOffsetDark} inline-flex h-${config.contextButton.size} w-${config.contextButton.size} items-center justify-center rounded-${config.contextButton.rounded} ring-1 ring-transparent transition-all duration-${config.contextButton.duration} group-hover:ring-${config.contextButton.ringGroupHover}`]: + {}, + [`.${prefix}-context-button-inner`]: { + [`@apply border-${config.contextButton.inner.border} dark:border-${config.contextButton.inner.borderDark} dark:bg-${config.contextButton.inner.bg} flex h-${config.contextButton.inner.size} w-${config.contextButton.inner.size} items-center justify-center rounded-${config.contextButton.inner.rounded} border bg-${config.contextButton.inner.bg}`]: + {}, + }, + [`.${prefix}-context-icon`]: { + [`@apply text-${config.contextButton.icon.text} h-${config.contextButton.icon.size} w-${config.contextButton.icon.size} transition-transform duration-${config.contextButton.icon.duration}`]: + {}, + }, + }, + [`.${prefix}-text-button`]: { + [`@apply flex items-center space-x-1 text-${config.textButton.text}`]: + {}, + [`.${prefix}-text-button-inner`]: { + [`@apply font-${config.textButton.inner.font}`]: {}, + }, + }, + [`.${prefix}-chevron`]: { + [`@apply h-${config.chevron.size} w-${config.chevron.size} transition-transform duration-${config.chevron.duration}`]: + {}, + }, + [`.${prefix}-dropdown-menu`]: { + [`@apply absolute z-50 mt-2 shadow-${config.dropdownMenu.shadow} shadow-${config.dropdownMenu.shadowColor} dark:shadow-${config.dropdownMenu.shadowColorDark} focus:outline-none`]: + {}, + [`.${prefix}-menu-header`]: { + [`@apply px-4 pt-5`]: {}, + }, + [`.${prefix}-menu-header-inner`]: { + [`@apply relative flex items-center justify-between`]: {}, + }, + [`.${prefix}-menu-header-title`]: { + [`@apply font-${config.dropdownMenu.headerTitle.font} font-${config.dropdownMenu.headerTitle.fontSize} text-${config.dropdownMenu.headerTitle.text} dark:text-${config.dropdownMenu.headerTitle.textDark} text-${config.dropdownMenu.headerTitle.textSize} uppercase`]: + {}, + }, + [`.${prefix}-menu-content`]: { + [`@apply p-2 space-y-1`]: {}, + }, + [`&.${prefix}-menu-md`]: { + [`@apply w-56`]: {}, + }, + [`&.${prefix}-menu-lg`]: { + [`@apply w-72`]: {}, + }, + [`&.${prefix}-menu-rounded`]: { + [`@apply rounded-${config.dropdownMenu.rounded.default}`]: {}, + }, + [`&.${prefix}-menu-smooth`]: { + [`@apply rounded-${config.dropdownMenu.rounded.smooth}`]: {}, + }, + [`&.${prefix}-menu-curved`]: { + [`@apply rounded-${config.dropdownMenu.rounded.curved}`]: {}, + }, + [`&.${prefix}-menu-white`]: { + [`@apply border bg-${config.dropdownMenu.white.bg} border-${config.dropdownMenu.white.border} dark:border-${config.dropdownMenu.white.borderDark} dark:bg-${config.dropdownMenu.white.bgDark}`]: + {}, + }, + [`&.${prefix}-menu-white-contrast`]: { + [`@apply border bg-${config.dropdownMenu.whiteContrast.bg} border-${config.dropdownMenu.whiteContrast.border} dark:border-${config.dropdownMenu.whiteContrast.borderDark} dark:bg-${config.dropdownMenu.whiteContrast.bgDark}`]: + {}, + }, + [`&.${prefix}-menu-muted`]: { + [`@apply border bg-${config.dropdownMenu.muted.bg} border-${config.dropdownMenu.muted.border} dark:border-${config.dropdownMenu.muted.borderDark} dark:bg-${config.dropdownMenu.muted.bgDark}`]: + {}, + }, + [`&.${prefix}-menu-muted-contrast`]: { + [`@apply border bg-${config.dropdownMenu.mutedContrast.bg} border-${config.dropdownMenu.mutedContrast.border} dark:border-${config.dropdownMenu.mutedContrast.borderDark} dark:bg-${config.dropdownMenu.mutedContrast.bgDark}`]: + {}, + }, + [`&.${prefix}-menu-primary`]: { + [`@apply border bg-${config.dropdownMenu.primary.bg} border-${config.dropdownMenu.primary.border}`]: + {}, + }, + [`&.${prefix}-menu-info`]: { + [`@apply border bg-${config.dropdownMenu.info.bg} border-${config.dropdownMenu.info.border}`]: + {}, + }, + [`&.${prefix}-menu-success`]: { + [`@apply border bg-${config.dropdownMenu.success.bg} border-${config.dropdownMenu.success.border}`]: + {}, + }, + [`&.${prefix}-menu-warning`]: { + [`@apply border bg-${config.dropdownMenu.warning.bg} border-${config.dropdownMenu.warning.border}`]: + {}, + }, + [`&.${prefix}-menu-danger`]: { + [`@apply border bg-${config.dropdownMenu.danger.bg} border-${config.dropdownMenu.danger.border}`]: + {}, + }, + }, + [`&.${prefix}-dropdown-start`]: { + [`.${prefix}-dropdown-menu`]: { + [`@apply start-0 origin-top-left`]: {}, + }, + }, + [`&.${prefix}-dropdown-end`]: { + [`.${prefix}-dropdown-menu`]: { + [`@apply end-0 origin-top-right`]: {}, + }, + }, + [`&:hover`]: { + [`.${prefix}-context-button`]: { + [`@apply ring-${config.hover.ring} dark:ring-${config.hover.ringDark}`]: + {}, + }, + }, }, }) } @@ -29,7 +232,7 @@ export default plugin.withOptions( return { theme: { shurikenUi: { - dropdownDivider: defaultDropdownDividerConfig, + dropdown: defaultDropdownConfig, }, }, } diff --git a/src/plugins/components/index.ts b/src/plugins/components/index.ts index 2b30557..1c9d0bb 100644 --- a/src/plugins/components/index.ts +++ b/src/plugins/components/index.ts @@ -11,6 +11,7 @@ import buttonClose from './button-close' import buttonIcon from './button-icon' import buttonGroup from './button-group' import dropdown from './dropdown' +import dropdownDivider from './dropdown-divider' import inputFile from './input-file' import focus from './focus' import heading from './heading' @@ -50,6 +51,7 @@ const components = [ buttonIcon, buttonGroup, dropdown, + dropdownDivider, inputFile, focus, heading,