mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
feat(module): implement --ui-radius CSS variable (#2341)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -11,6 +11,12 @@ const config = useRuntimeConfig().public
|
||||
const navigation = inject<Ref<NavItem[]>>('navigation')
|
||||
|
||||
// const items = computed(() => props.links.map(({ icon, ...link }) => link))
|
||||
|
||||
defineShortcuts({
|
||||
meta_g: () => {
|
||||
window.open('https://github.com/nuxt/ui/tree/v3', '_blank')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -26,22 +32,22 @@ const navigation = inject<Ref<NavItem[]>>('navigation')
|
||||
<!-- <UNavigationMenu :items="items" variant="link" /> -->
|
||||
|
||||
<template #right>
|
||||
<ColorPicker />
|
||||
<ThemePicker />
|
||||
|
||||
<UTooltip text="Search" :kbds="['meta', 'K']">
|
||||
<UContentSearchButton />
|
||||
</UTooltip>
|
||||
|
||||
<ColorModeButton />
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="https://github.com/nuxt/ui/tree/v3"
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
<UTooltip text="Open on GitHub" :kbds="['meta', 'G']">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="https://github.com/nuxt/ui/tree/v3"
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<ClientOnly v-if="!colorMode?.forced">
|
||||
<UButton
|
||||
:icon="isDark ? appConfig.ui.icons.dark : appConfig.ui.icons.light"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
v-bind="{
|
||||
...$attrs
|
||||
}"
|
||||
:aria-label="`Switch to ${isDark ? 'light' : 'dark'} mode`"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
// Computed
|
||||
|
||||
const isDark = computed({
|
||||
get() {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set() {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,66 +0,0 @@
|
||||
<template>
|
||||
<UPopover mode="hover" :ui="{ content: 'p-2' }">
|
||||
<template #default="{ open }">
|
||||
<UButton
|
||||
icon="i-heroicons-swatch-20-solid"
|
||||
color="neutral"
|
||||
:variant="open ? 'soft' : 'ghost'"
|
||||
square
|
||||
aria-label="Color picker"
|
||||
:ui="{ leadingIcon: 'text-[--ui-primary]' }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<fieldset class="grid grid-cols-5 gap-px">
|
||||
<legend class="text-[11px] font-bold mb-1">
|
||||
Primary
|
||||
</legend>
|
||||
|
||||
<ColorPickerPill v-for="color in primaryColors" :key="color" :color="color" :selected="primary" @select="primary = color" />
|
||||
</fieldset>
|
||||
|
||||
<USeparator class="my-2" type="dashed" />
|
||||
|
||||
<fieldset class="grid grid-cols-5 gap-px">
|
||||
<legend class="text-[11px] font-bold mb-1">
|
||||
Neutral
|
||||
</legend>
|
||||
|
||||
<ColorPickerPill v-for="color in neutralColors" :key="color" :color="color" :selected="neutral" @select="neutral = color" />
|
||||
</fieldset>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from 'tailwindcss/colors'
|
||||
import { omit } from '#ui/utils'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
// Computed
|
||||
|
||||
const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
|
||||
const neutral = computed({
|
||||
get() {
|
||||
return appConfig.ui.colors.neutral
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.colors.neutral = option
|
||||
window.localStorage.setItem('nuxt-ui-neutral', appConfig.ui.colors.neutral)
|
||||
}
|
||||
})
|
||||
|
||||
const colorsToOmit = ['inherit', 'current', 'transparent', 'black', 'white', ...neutralColors]
|
||||
const primaryColors = Object.keys(omit(colors, colorsToOmit as any))
|
||||
const primary = computed({
|
||||
get() {
|
||||
return appConfig.ui.colors.primary
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.colors.primary = option
|
||||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.colors.primary)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<UTooltip :text="color" class="capitalize" :portal="false">
|
||||
<UButton
|
||||
color="neutral"
|
||||
square
|
||||
:variant="color === selected ? 'soft' : 'ghost'"
|
||||
@click.stop.prevent="$emit('select')"
|
||||
>
|
||||
<span
|
||||
class="inline-block w-3 h-3 rounded-full"
|
||||
:class="`bg-[--color-light] dark:bg-[--color-dark]`"
|
||||
:style="{
|
||||
'--color-light': `var(--color-${color}-500)`,
|
||||
'--color-dark': `var(--color-${color}-400)`
|
||||
}"
|
||||
/>
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ color: string, selected: string }>()
|
||||
defineEmits(['select'])
|
||||
</script>
|
||||
@@ -214,7 +214,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div>
|
||||
<div v-if="options.length" class="flex items-center gap-2.5 border border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] border-b-0 relative rounded-t-md px-4 py-2.5 overflow-x-auto">
|
||||
<div v-if="options.length" class="flex items-center gap-2.5 border border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
|
||||
<template v-for="option in options" :key="option.name">
|
||||
<UFormField
|
||||
:label="option.label"
|
||||
@@ -263,7 +263,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center border border-b-0 border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class]">
|
||||
<div class="flex justify-center border border-b-0 border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class]">
|
||||
<component :is="name" v-bind="{ ...componentProps, ...componentEvents }">
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<ContentSlot :name="slot" unwrap="p">
|
||||
|
||||
@@ -114,7 +114,7 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div v-if="preview">
|
||||
<div class="border border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] relative z-[1]" :class="[{ 'border-b-0 rounded-t-md': props.source, 'rounded-md': !props.source }]">
|
||||
<div class="border border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700]">
|
||||
<slot name="options" />
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded border border-dashed border-[--ui-border-accented] opacity-75 px-4 flex items-center justify-center">
|
||||
<div class="relative overflow-hidden rounded-[--ui-radius] border border-dashed border-[--ui-border-accented] opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-[--ui-border-inverted]/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
|
||||
140
docs/app/components/theme-picker/ThemePicker.vue
Normal file
140
docs/app/components/theme-picker/ThemePicker.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<UPopover :ui="{ content: 'w-72 px-6 py-4 flex flex-col gap-4' }">
|
||||
<template #default="{ open }">
|
||||
<UButton
|
||||
icon="i-heroicons-swatch"
|
||||
color="neutral"
|
||||
:variant="open ? 'soft' : 'ghost'"
|
||||
square
|
||||
aria-label="Color picker"
|
||||
:ui="{ leadingIcon: 'text-[--ui-primary]' }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<fieldset>
|
||||
<legend class="text-[11px] leading-none font-semibold mb-2">
|
||||
Primary
|
||||
</legend>
|
||||
|
||||
<div class="grid grid-cols-3 gap-1 -mx-2">
|
||||
<ThemePickerButton
|
||||
v-for="color in primaryColors"
|
||||
:key="color"
|
||||
:label="color"
|
||||
:chip="color"
|
||||
:selected="primary === color"
|
||||
@select="primary = color"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend class="text-[11px] leading-none font-semibold mb-2">
|
||||
Neutral
|
||||
</legend>
|
||||
|
||||
<div class="grid grid-cols-3 gap-1 -mx-2">
|
||||
<ThemePickerButton
|
||||
v-for="color in neutralColors"
|
||||
:key="color"
|
||||
:label="color"
|
||||
:chip="color"
|
||||
:selected="neutral === color"
|
||||
@select="neutral = color"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend class="text-[11px] leading-none font-semibold mb-2">
|
||||
Radius
|
||||
</legend>
|
||||
|
||||
<div class="grid grid-cols-5 gap-1 -mx-2">
|
||||
<ThemePickerButton
|
||||
v-for="r in radiuses"
|
||||
:key="r"
|
||||
:label="String(r)"
|
||||
class="justify-center px-0"
|
||||
:selected="radius === r"
|
||||
@select="radius = r"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend class="text-[11px] leading-none font-semibold mb-2">
|
||||
Theme
|
||||
</legend>
|
||||
|
||||
<div class="flex gap-1 -mx-2">
|
||||
<ThemePickerButton
|
||||
v-for="m in modes"
|
||||
:key="m.label"
|
||||
v-bind="m"
|
||||
:selected="mode === m.label"
|
||||
@select="mode = m.label"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from 'tailwindcss/colors'
|
||||
import { omit } from '#ui/utils'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
|
||||
const neutral = computed({
|
||||
get() {
|
||||
return appConfig.ui.colors.neutral
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.colors.neutral = option
|
||||
window.localStorage.setItem('nuxt-ui-neutral', appConfig.ui.colors.neutral)
|
||||
}
|
||||
})
|
||||
|
||||
const colorsToOmit = ['inherit', 'current', 'transparent', 'black', 'white', ...neutralColors]
|
||||
const primaryColors = Object.keys(omit(colors, colorsToOmit as any))
|
||||
const primary = computed({
|
||||
get() {
|
||||
return appConfig.ui.colors.primary
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.colors.primary = option
|
||||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.colors.primary)
|
||||
}
|
||||
})
|
||||
|
||||
const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
|
||||
const radius = computed({
|
||||
get() {
|
||||
return appConfig.theme.radius
|
||||
},
|
||||
set(option) {
|
||||
appConfig.theme.radius = option
|
||||
window.localStorage.setItem('nuxt-ui-radius', String(appConfig.theme.radius))
|
||||
}
|
||||
})
|
||||
|
||||
const modes = [
|
||||
{ label: 'light', icon: appConfig.ui.icons.light },
|
||||
{ label: 'dark', icon: appConfig.ui.icons.dark }
|
||||
]
|
||||
const mode = computed({
|
||||
get() {
|
||||
return colorMode.value
|
||||
},
|
||||
set(option) {
|
||||
colorMode.preference = option
|
||||
}
|
||||
})
|
||||
</script>
|
||||
32
docs/app/components/theme-picker/ThemePickerButton.vue
Normal file
32
docs/app/components/theme-picker/ThemePickerButton.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<UButton
|
||||
size="sm"
|
||||
color="neutral"
|
||||
:icon="icon"
|
||||
:label="label"
|
||||
:variant="selected ? 'soft' : 'outline'"
|
||||
class="capitalize ring-[--ui-border] rounded-[--ui-radius] text-[11px]"
|
||||
@click.stop.prevent="$emit('select')"
|
||||
>
|
||||
<template v-if="chip" #leading>
|
||||
<span
|
||||
class="inline-block w-2 h-2 rounded-full"
|
||||
:class="`bg-[--color-light] dark:bg-[--color-dark]`"
|
||||
:style="{
|
||||
'--color-light': `var(--color-${chip}-500)`,
|
||||
'--color-dark': `var(--color-${chip}-400)`
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
label: string
|
||||
icon?: string
|
||||
chip?: string
|
||||
selected?: boolean
|
||||
}>()
|
||||
defineEmits(['select'])
|
||||
</script>
|
||||
Reference in New Issue
Block a user