Primary
@@ -32,9 +40,16 @@ To customize theme color, set a CSS variable in your CSS with color name prefixe
{{ isPrimaryChanged ? 'Reset' : 'Change' }} primary
+
+
+
+Also checkout related documentation:
+
+- [`useAnu` composable](/guide/composables/useAnu.md)
+
## CSS variables
-Besides colors, Anu uses CSS variables for other stuff for providing maximum flexibility and customization on the fly. All anu's CSS variables are prefixed with `a-`.
+For the most part, Anu uses CSS variables for other stuff to providing maximum flexibility and customization on the fly. All anu's CSS variables are prefixed with `a-`.
:::details View all CSS vars
Below is CSS vars defined for preset theme default's light theme:
@@ -69,7 +84,7 @@ Just change the colors to Bootstrap's color and see the magic đ
![Bootstrap buttons using anu](/images/guide/anu-bootstrap-btns.png)
:::
-You can refer to available shortcuts in [this](https://github.com/jd-solanki/anu/blob/main/packages/anu-vue/src/presets/theme-default/index.ts) file.
+You can refer to available shortcuts in [this](https://github.com/jd-solanki/anu/blob/main/packages/preset-theme-default/src/shortcuts.ts) file.
If you like this simple customization don't forget to give a **star on Github**. If you don't like it give a triple star đ.
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index 32823de0..8619e57a 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -16,6 +16,9 @@
"paths": {
"@anu/*": [
"../packages/anu-vue/*"
+ ],
+ "anu-vue": [
+ "../packages/anu-vue"
]
}
},
@@ -29,4 +32,4 @@
"**/*.md",
".vitepress/shims.d.ts"
]
-}
\ No newline at end of file
+}
diff --git a/packages/anu-vue/src/composables/index.ts b/packages/anu-vue/src/composables/index.ts
index e4a23d61..0bccdf70 100644
--- a/packages/anu-vue/src/composables/index.ts
+++ b/packages/anu-vue/src/composables/index.ts
@@ -1,3 +1,4 @@
+export * from './useAnu'
export * from './useColor'
export * from './useConfigurable'
export * from './useDOMScrollLock'
diff --git a/packages/anu-vue/src/composables/useAnu.ts b/packages/anu-vue/src/composables/useAnu.ts
new file mode 100644
index 00000000..a1971b90
--- /dev/null
+++ b/packages/anu-vue/src/composables/useAnu.ts
@@ -0,0 +1,64 @@
+import type { ConfigThemes, PluginOptions } from '../plugin'
+import { createCustomGlobalState } from '@/composables/useCustomCreateGlobalState'
+
+const getThemeColorsCss = (themes: ConfigThemes) => {
+ return Object.entries(themes)
+ .map(
+ ([themeName, theme]) => `${theme.class ? `.${theme.class}` : ':root'}{${Object.entries(theme.colors).concat(Object.entries(theme.cssVars)).map(([varName, val]) => `--a-${varName}:${val};`).join('')}}`,
+ )
+ .join('')
+}
+
+export interface AnuComposableOptions {
+ themes: PluginOptions['themes']
+ initialTheme: PluginOptions['initialTheme']
+}
+
+export const useAnu = createCustomGlobalState((options: AnuComposableOptions) => {
+ console.log('useAnu called with options', options)
+
+ const themes = ref(options.themes)
+ const activeThemeName = ref(options.initialTheme)
+ const activeTheme = computed(() => ({
+ name: activeThemeName.value,
+ theme: themes.value[activeThemeName.value],
+ }))
+ const themeColorsCss = ref('')
+ useStyleTag(themeColorsCss)
+
+ /*
+ âšī¸ `Object.values(themes.value).map(theme => [theme.colors, theme.cssVars]).flat()` will allow watching for colors & css vars of all themes đŽ
+
+ Object.values(themes.value) => [
, ] => Each theme's theme value/config
+ Object.values(themes.value).map(theme => [theme.colors, theme.cssVars]) => [[, ], [, ]] => Will return nested array with colors & css variables
+ Finally flat the array to get list of each theme's color & css var [, , , ] â¨
+ */
+ watch(
+ () => Object.values(themes.value).map(theme => [theme.colors, theme.cssVars]).flat(),
+ () => {
+ themeColorsCss.value = getThemeColorsCss(themes.value)
+ },
+ { deep: true, immediate: true },
+ )
+
+ // Toggle theme class
+ watch(activeThemeName, (newThemeName, oldThemeName) => {
+ const newTheme = themes.value[newThemeName]
+
+ if (newTheme && newTheme.class)
+ document.documentElement.classList.toggle(newTheme.class)
+
+ // âšī¸ Initially, `oldThemeName` will be undefined
+ if (oldThemeName) {
+ const oldTheme = themes.value[oldThemeName]
+ if (oldTheme && oldTheme.class)
+ document.documentElement.classList.toggle(oldTheme.class)
+ }
+ }, { immediate: true })
+
+ return {
+ themes,
+ activeThemeName,
+ activeTheme,
+ }
+})
diff --git a/packages/anu-vue/src/composables/useColor.ts b/packages/anu-vue/src/composables/useColor.ts
index a7ddec55..0c25e392 100644
--- a/packages/anu-vue/src/composables/useColor.ts
+++ b/packages/anu-vue/src/composables/useColor.ts
@@ -1,17 +1,27 @@
import type { MaybeRef } from '@vueuse/core'
-import type { StyleValue } from 'vue'
+import type { ComputedRef, StyleValue } from 'vue'
+import { useAnu } from '@/composables/useAnu'
import type { ColorProp } from '@/composables/useProps'
+export const isThemeColor = (color: ColorProp | null): ComputedRef => computed(() => {
+ let activeThemeColors: string[] = []
+
+ const { activeTheme } = useAnu()
+ activeThemeColors = Object.keys(activeTheme.value.theme.colors)
+
+ return !!(color && (activeThemeColors as ColorProp[]).includes(color))
+})
+
export const useColor = (color: MaybeRef, cssVarName: MaybeRef, as: 'text' | 'bg' = 'text') => {
const styles = computed(() => {
const _color = unref(color)
const cssVar = computed(() => `--a-${unref(cssVarName)}`)
const property = as === 'bg' ? 'background-color' : 'color'
- const isThemeColor = ['primary', 'success', 'info', 'warning', 'danger'].includes(_color as string)
+ const _isThemeColor = isThemeColor(_color)
const _styles = {
- [cssVar.value]: isThemeColor ? `hsl(var(--a-${_color}))` : _color,
+ [cssVar.value]: _isThemeColor.value ? `hsl(var(--a-${_color}))` : _color,
[property]: `var(${cssVar.value})`,
} as StyleValue
diff --git a/packages/anu-vue/src/composables/useCustomCreateGlobalState.ts b/packages/anu-vue/src/composables/useCustomCreateGlobalState.ts
new file mode 100644
index 00000000..6b618503
--- /dev/null
+++ b/packages/anu-vue/src/composables/useCustomCreateGlobalState.ts
@@ -0,0 +1,26 @@
+// âšī¸ Waiting for: https://github.com/vueuse/vueuse/pull/2790
+
+export type CreateGlobalStateReturn = (...args: any[]) => T
+
+/**
+ * Keep states in the global scope to be reusable across Vue instances.
+ *
+ * @see https://vueuse.org/createGlobalState
+ * @param stateFactory A factory function to create the state
+ */
+export function createCustomGlobalState(
+ stateFactory: (...args: any[]) => T,
+): CreateGlobalStateReturn {
+ let initialized = false
+ let state: T
+ const scope = effectScope(true)
+
+ return (...args: any[]) => {
+ if (!initialized) {
+ state = scope.run(() => stateFactory(...args))!
+ initialized = true
+ }
+
+ return state
+ }
+}
diff --git a/packages/anu-vue/src/composables/useLayer.ts b/packages/anu-vue/src/composables/useLayer.ts
index 0af46771..74c6fc72 100644
--- a/packages/anu-vue/src/composables/useLayer.ts
+++ b/packages/anu-vue/src/composables/useLayer.ts
@@ -2,10 +2,10 @@ import type { MaybeRef } from '@vueuse/core'
import { defu } from 'defu'
import type { ComponentObjectPropsOptions } from 'vue'
import { ref, unref, watch } from 'vue'
+import { isThemeColor } from '@/composables/useColor'
import type { ColorProp } from '@/composables/useProps'
import { color } from '@/composables/useProps'
import { useTypographyColor } from '@/composables/useTypographyColor'
-import { isThemeColor } from '@/utils/color'
import { colord } from '@/utils/colord'
export interface LayerProps {
@@ -84,7 +84,7 @@ export const useLayer = () => {
We also have colord as dependency for now. We might remove this in future once Anu is more popular and mature.
*/
- if (_isThemeColor) {
+ if (_isThemeColor.value) {
styles.push({ '--a-layer-c': `var(--a-${propColor})` })
if (propVariant === 'fill') {
diff --git a/packages/anu-vue/src/composables/useProps.ts b/packages/anu-vue/src/composables/useProps.ts
index 85db3c33..321dcb57 100644
--- a/packages/anu-vue/src/composables/useProps.ts
+++ b/packages/anu-vue/src/composables/useProps.ts
@@ -1,12 +1,11 @@
import { createDefu } from 'defu'
import type { PropType } from 'vue'
import type { ConfigurableValue } from '@/composables/useConfigurable'
+import type { ThemeColors } from '@/plugin'
import type { NamedColors } from '@/utils/color'
import type { LooseAutocomplete } from '@/utils/typescripts'
-export const themeColors = ['primary', 'success', 'info', 'warning', 'danger'] as const
-export type ThemeColor = typeof themeColors[number]
-export type ColorProp = LooseAutocomplete | undefined
+export type ColorProp = LooseAutocomplete | undefined
export const color = {
type: [String, undefined] as PropType,
diff --git a/packages/anu-vue/src/composables/useTypographyColor.ts b/packages/anu-vue/src/composables/useTypographyColor.ts
index 8fb5a3a1..6d3820db 100644
--- a/packages/anu-vue/src/composables/useTypographyColor.ts
+++ b/packages/anu-vue/src/composables/useTypographyColor.ts
@@ -2,7 +2,7 @@ import type { MaybeComputedRef } from '@vueuse/core'
import { resolveUnref } from '@vueuse/core'
import type { ColorProp } from './useProps'
import { colord } from '@/utils/colord'
-import { isThemeColor } from '@/utils/color'
+import { isThemeColor } from '@/composables/useColor'
const calculateColor = (_isThemeColor: boolean, _color: ColorProp | null, _variant: string) => {
const classes = []
@@ -48,7 +48,7 @@ export const useTypographyColor = (color: MaybeComputedRef, va
const _isThemeColor = isThemeColor(_color)
watch([() => color, () => variant], () => {
- const calculatedColor = calculateColor(_isThemeColor, _color, _variant)
+ const calculatedColor = calculateColor(_isThemeColor.value, _color, _variant)
typographyClasses.value = calculatedColor.classes
typographyStyles.value = calculatedColor.styles
}, { immediate: true })
diff --git a/packages/anu-vue/src/index.ts b/packages/anu-vue/src/index.ts
index 87ea372c..fa7a0982 100644
--- a/packages/anu-vue/src/index.ts
+++ b/packages/anu-vue/src/index.ts
@@ -1,37 +1,14 @@
-import { defu } from 'defu'
-import type { App } from 'vue'
-import * as components from './components'
-import './scss/index.scss'
-
-export interface PluginOptions {
- registerComponents: boolean
-}
-
-const optionsDefaults: Partial = {
- registerComponents: true,
-}
-
-const plugin = {
- install(app: App, options: Partial = {}) {
- const _options = defu(options, optionsDefaults)
-
- if (_options.registerComponents) {
- for (const prop in components) {
- // @ts-expect-error: I want to index import using string
- const component = components[prop]
- app.component(component.name, component)
- }
- }
- },
-}
+import '@/scss/index.scss'
export { AnuComponentResolver } from './componentResolver'
export * from './components'
export * from './composables'
export * as composables from './composables'
-export { presetAnu } from './preset'
+export { plugin as anu } from './plugin'
+export type { PluginOptions, ThemeColors, ThemeOptions as ThemeOption } from './plugin'
+export { defaultThemeColors, presetAnu } from './preset'
export * from './symbols'
-export { plugin as anu }
+
export const presetIconExtraProperties = {
'height': '1.2em',
'width': '1.2em',
diff --git a/packages/anu-vue/src/plugin.ts b/packages/anu-vue/src/plugin.ts
new file mode 100644
index 00000000..92988bfe
--- /dev/null
+++ b/packages/anu-vue/src/plugin.ts
@@ -0,0 +1,74 @@
+import { defu } from 'defu'
+import type { PartialDeep } from 'type-fest'
+import type { App } from 'vue'
+import * as components from '@/components'
+import { useAnu } from '@/composables/useAnu'
+import { ANU_CONFIG } from '@/symbols'
+
+export type ThemeColors = 'primary' | 'success' | 'info' | 'warning' | 'danger'
+export type DefaultThemes = 'light' | 'dark'
+
+export interface ThemeOptions {
+ class: string
+ colors: Record
+ cssVars: Record
+}
+
+export type ConfigThemes = Record
+
+export interface PluginOptions {
+ registerComponents: boolean
+ initialTheme: keyof ConfigThemes
+ themes: ConfigThemes
+}
+
+const configDefaults: PluginOptions = {
+ registerComponents: true,
+ initialTheme: 'light',
+ themes: {
+ light: {
+ class: '',
+ colors: {
+ primary: '265, 97.7%, 66.3%',
+ success: '94.5, 100%, 39.6%',
+ info: '200.1, 100%, 54.3%',
+ warning: '42.4, 100%, 50%',
+ danger: '358.3, 100%, 64.9%',
+ },
+ cssVars: {},
+ },
+ dark: {
+ class: 'dark',
+ colors: {
+ primary: '261, 73%, 66.3%',
+ success: '94.5, 73%, 39.6%',
+ info: '200.1, 73%, 54.3%',
+ warning: '42.4, 73%, 50%',
+ danger: '358.3, 73%, 64.9%',
+ },
+ cssVars: {},
+ },
+ },
+}
+
+export const plugin = {
+ install(app: App, options: PartialDeep = {}) {
+ const config = defu(options, configDefaults)
+
+ if (config.registerComponents) {
+ for (const prop in components) {
+ // @ts-expect-error: I want to index import using string
+ const component = components[prop]
+ app.component(component.name, component)
+ }
+ }
+
+ app.provide(ANU_CONFIG, config)
+
+ // Initialize Anu instance with config values
+ useAnu({
+ initialTheme: config.initialTheme,
+ themes: config.themes,
+ })
+ },
+}
diff --git a/packages/anu-vue/src/preset/index.ts b/packages/anu-vue/src/preset/index.ts
index e3e9a525..695c2318 100644
--- a/packages/anu-vue/src/preset/index.ts
+++ b/packages/anu-vue/src/preset/index.ts
@@ -1,8 +1,37 @@
import type { Preset } from '@unocss/core'
+import { defu } from 'defu'
+
+export const defaultThemeColors = ['primary', 'success', 'info', 'warning', 'danger']
+
+export const presetDefaults = {
+ colors: defaultThemeColors,
+}
+
+export function presetAnu(options: Partial = {}): Preset {
+ const _options: typeof presetDefaults = defu(options, presetDefaults)
-export function presetAnu(): Preset {
return {
name: '@anu-vue/preset-core',
+ theme: {
+ colors: Object.fromEntries(
+ _options.colors.map(c => [c, `hsl(var(--a-${c}))`]),
+ ),
+ },
+ safelist: [
+ // TODO: We can remove this color safelist if we use leverage `--a-color` CSS var
+ ..._options.colors.map(c => `bg-${c}`),
+ ..._options.colors.map(c => `hover:bg-${c}`),
+
+ ..._options.colors.map(c => `border-${c}`),
+ ..._options.colors.map(c => `text-${c}`),
+ ..._options.colors.map(c => `shadow-${c}`),
+ ..._options.colors.map(c => `after:bg-${c}`),
+
+ // Typography
+ ..._options.colors.map(c => `a-title-${c}`),
+ ..._options.colors.map(c => `a-subtitle-${c}`),
+ ...['top', 'right', 'bottom', 'left'].map(dir => `a-drawer-anchor-${dir}`),
+ ],
variants: [
(matcher: string) => {
if (!matcher.startsWith('i:'))
diff --git a/packages/anu-vue/src/symbols.ts b/packages/anu-vue/src/symbols.ts
index 4395ed6d..5f8af358 100644
--- a/packages/anu-vue/src/symbols.ts
+++ b/packages/anu-vue/src/symbols.ts
@@ -1,3 +1,4 @@
import type { InjectionKey } from 'vue'
+import type { PluginOptions } from '@/plugin'
-export const spacingSymbol = Symbol('spacingSymbol') as InjectionKey
+export const ANU_CONFIG = Symbol('ANU_CONFIG') as InjectionKey
diff --git a/packages/anu-vue/src/utils/color.ts b/packages/anu-vue/src/utils/color.ts
index ea93d49d..78d284b7 100644
--- a/packages/anu-vue/src/utils/color.ts
+++ b/packages/anu-vue/src/utils/color.ts
@@ -1,5 +1,3 @@
-import type { ColorProp } from '@/composables/useProps'
-
// âšī¸ Extracted from https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
export const namedColors = Object.freeze({
// CSS Level 1
@@ -271,5 +269,3 @@ export const getContrastColor = (color: string): string => {
// If the color could not be parsed, return black as the contrast color
return darkContrastColor
}
-
-export const isThemeColor = (color: ColorProp | null): boolean => !!(color && (['primary', 'success', 'info', 'warning', 'danger'] as ColorProp[]).includes(color))
diff --git a/packages/anu-vue/src/utils/helpers.ts b/packages/anu-vue/src/utils/helpers.ts
index 4b3047e3..4b087aaf 100644
--- a/packages/anu-vue/src/utils/helpers.ts
+++ b/packages/anu-vue/src/utils/helpers.ts
@@ -5,7 +5,7 @@ export const isEmpty = (value: unknown): boolean => {
if (value === null || value === undefined || value === '')
return true
- return !!(Array.isArray(value) && value.length === 0)
+ return !!(Array.isArray(value) && value.length === 0) || JSON.stringify(value) === '{}'
}
// đ IsNullOrUndefined
diff --git a/packages/anu-vue/tsconfig.json b/packages/anu-vue/tsconfig.json
index 39b6616c..2ef7c02c 100644
--- a/packages/anu-vue/tsconfig.json
+++ b/packages/anu-vue/tsconfig.json
@@ -22,7 +22,7 @@
"*"
],
"anu-vue": [
- "./src"
+ "."
]
},
"types": [
@@ -47,11 +47,12 @@
"src/**/*.tsx",
"src/**/*.vue",
"auto-imports.d.ts",
- "shims.d.ts"
+ "shims.d.ts",
+ "volar.d.ts"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
-}
\ No newline at end of file
+}
diff --git a/packages/preset-theme-default/src/index.ts b/packages/preset-theme-default/src/index.ts
index 8a8c517f..625267e3 100644
--- a/packages/preset-theme-default/src/index.ts
+++ b/packages/preset-theme-default/src/index.ts
@@ -3,44 +3,18 @@ import { rules } from './rules'
import { shortcuts } from './shortcuts'
import { variants } from './variants'
-// âšī¸ We will need this import to generate the CSS file for preset-theme-default. We are just temporary disabling this.
-// import './scss/index.scss'
-
interface PresetOptions {
shortcutOverrides?: Exclude
}
-// TODO: Pass this to Anu plugin so that it can use the classes defined by theme preset
-export const colors = ['primary', 'success', 'info', 'warning', 'danger'] as const
-export type Colors = typeof colors
-
export function presetThemeDefault(options: PresetOptions = {}): Preset {
return {
name: '@anu-vue/preset-theme-default',
theme: {
colors: {
- primary: 'hsl(var(--a-primary))',
- success: 'hsl(var(--a-success))',
- info: 'hsl(var(--a-info))',
- warning: 'hsl(var(--a-warning))',
- danger: 'hsl(var(--a-danger))',
a: { border: 'hsla(var(--a-base-c),var(--a-border-opacity))' },
},
},
- safelist: [
- ...colors.map(c => `bg-${c}`),
- ...colors.map(c => `hover:bg-${c}`),
-
- ...colors.map(c => `border-${c}`),
- ...colors.map(c => `text-${c}`),
- ...colors.map(c => `shadow-${c}`),
- ...colors.map(c => `after:bg-${c}`),
-
- // Typography
- ...colors.map(c => `a-title-${c}`),
- ...colors.map(c => `a-subtitle-${c}`),
- ...['top', 'right', 'bottom', 'left'].map(dir => `a-drawer-anchor-${dir}`),
- ],
rules,
shortcuts: options.shortcutOverrides === undefined
? shortcuts
diff --git a/packages/preset-theme-default/src/scss/index.scss b/packages/preset-theme-default/src/scss/index.scss
index aa161ab2..1097289b 100644
--- a/packages/preset-theme-default/src/scss/index.scss
+++ b/packages/preset-theme-default/src/scss/index.scss
@@ -28,12 +28,6 @@ $body-color: hsla(var(--a-base-c), 0.68);
--a-backdrop-c: 0, 0%, 0%;
--a-backdrop-opacity: 0.35;
- --a-primary: 265, 97.7%, 66.3%;
- --a-success: 94.5, 100%, 39.6%;
- --a-info: 200.1, 100%, 54.3%;
- --a-warning: 42.4, 100%, 50%;
- --a-danger: 358.3, 100%, 64.9%;
-
// TODO: Add support to customize contrast color in => packages/anu-vue/src/utils/colord.ts
// Contrast colors
// --a-contrast-dark: #000;
@@ -79,12 +73,6 @@ $body-color: hsla(var(--a-base-c), 0.68);
--a-loader-overlay-bg-opacity: 0.75;
--a-backdrop-opacity: 0.5;
- --a-primary: 261, 73%, 66.3%;
- --a-success: 94.5, 73%, 39.6%;
- --a-info: 200.1, 73%, 54.3%;
- --a-warning: 42.4, 73%, 50%;
- --a-danger: 358.3, 73%, 64.9%;
-
// SECTION Components
// đ Switch
diff --git a/packages/preset-theme-default/src/shortcuts.ts b/packages/preset-theme-default/src/shortcuts.ts
index 3d198ac1..399f9061 100644
--- a/packages/preset-theme-default/src/shortcuts.ts
+++ b/packages/preset-theme-default/src/shortcuts.ts
@@ -132,7 +132,7 @@ const shortcuts: Exclude = [
// đ Select
'a-select-floating': '[--a-transition-slide-up-transform:6px]',
- 'a-select-options-container': '',
+ 'a-select-options-container': 'z-10',
'a-select-options-list': 'spacing-75',
// đ Switch
diff --git a/tsconfig.json b/tsconfig.json
index 59965976..0e4627ac 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,7 +16,7 @@
"jsx": "preserve",
"types": [
"node",
- "unplugin-vue-macros/macros-global",
+ "unplugin-vue-macros/macros-global"
]
},
"vueCompilerOptions": {
@@ -35,4 +35,4 @@
"**/node_modules/**",
"**/playground/**"
]
-}
\ No newline at end of file
+}