Skip to content

feat(Toolbar): new component #4382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions playground-vue/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const components = [
'textarea',
'timeline',
'toast',
'toolbar',
'tooltip',
'tree'
]
Expand Down
1 change: 1 addition & 0 deletions playground/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const components = [
'textarea',
'timeline',
'toast',
'toolbar',
'tooltip',
'tree'
]
Expand Down
105 changes: 105 additions & 0 deletions playground/app/pages/components/toolbar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
import theme from '#build/ui/toolbar'

const variants = Object.keys(theme.variants.variant)
const variant = ref(theme.defaultVariants.variant)

const sizes = Object.keys(theme.variants.size)
const size = ref(theme.defaultVariants.size)

const title = ref('Toolbar Title')
</script>

<template>
<div class=" space-y-4">
<div class="space-x-2">
<UButtonGroup>
<UBadge color="neutral" variant="outline" size="lg" label="title" />

<UInput v-model="title" :highlight="false" />
</UButtonGroup>

<UButtonGroup>
<UBadge color="neutral" variant="outline" size="lg" label="variant" />

<USelect v-model="variant" :items="variants" />
</UButtonGroup>
<UButtonGroup>
<UBadge color="neutral" variant="outline" size="lg" label="size" />

<USelect v-model="size" :items="sizes" />
</UButtonGroup>
</div>
<UToolbar :variant="variant" :size="size">
<Placeholder class="w-full h-8" />
</UToolbar>

<UToolbar :variant="variant" :size="size">
<template #left>
<Placeholder class="w-40 h-8" />
</template>
<template #center>
<Placeholder class="w-40 h-8" />
</template>
<template #right>
<Placeholder class="w-40 h-8" />
</template>
</UToolbar>

<UToolbar :variant="variant" :size="size">
<template #left>
<Placeholder class="w-40 h-8" />
</template>
</UToolbar>

<UToolbar :variant="variant" :size="size">
<template #center>
<Placeholder class="w-40 h-8" />
</template>
</UToolbar>

<UToolbar :variant="variant">
<template #right>
<Placeholder class="w-40 h-8" />
</template>
</UToolbar>

<h2 class="text-2xl font-semibold">
Variants:
</h2>
<UToolbar :title="title" />
<UToolbar :title="title" variant="outline" :ui="{ root: 'border-x-0' }" />
<UToolbar :title="title" variant="outline" class="border-0 border-b" />
<UToolbar :title="title" variant="soft" />
<UToolbar :title="title" variant="subtle" />
<UToolbar :title="title" variant="solid" />

<h2 class="text-2xl font-semibold">
Sizes:
</h2>
<UToolbar :title="title" variant="solid" size="sm">
<template #right>
<UButton label="Home" color="neutral" variant="soft" size="sm" />
<UButton label="About" color="neutral" variant="soft" size="sm" />
</template>
</UToolbar>
<UToolbar :title="title" variant="solid" size="md">
<template #right>
<UButton label="Home" color="neutral" variant="soft" size="md" />
<UButton label="About" color="neutral" variant="soft" size="md" />
</template>
</UToolbar>
<UToolbar :title="title" variant="solid" size="lg">
<template #right>
<UButton label="Home" color="neutral" variant="soft" size="lg" />
<UButton label="About" color="neutral" variant="soft" size="lg" />
</template>
</UToolbar>
<UToolbar :title="title" variant="solid" size="xl">
<template #right>
<UButton label="Home" color="neutral" variant="soft" size="xl" />
<UButton label="About" color="neutral" variant="soft" size="xl" />
</template>
</UToolbar>
</div>
</template>
71 changes: 71 additions & 0 deletions src/runtime/components/Toolbar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/toolbar'
import type { ComponentConfig } from '../types/utils'

type Toolbar = ComponentConfig<typeof theme, AppConfig, 'toolbar'>

export interface ToolbarProps {
/**
* The element or component this component should render as.
* @defaultValue 'div'
*/
as?: any
/**
* @defaultValue 'outline'
*/
title?: string
variant?: Toolbar['variants']['variant']
size?: Toolbar['variants']['size']
class?: any
ui?: Toolbar['slots']
}
export interface ToolbarSlots {
default(props?: {}): any
title(props?: {}): any
left(props?: {}): any
right(props?: {}): any
center(props?: {}): any
}
</script>

<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'

const props = defineProps<ToolbarProps>()
const slots = defineSlots<ToolbarSlots>()

const appConfig = useAppConfig() as Toolbar['AppConfig']

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.toolbar || {}) })({
variant: props.variant,
size: props.size
}))
</script>

<template>
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
<slot>
<div :class="ui.left({ class: [props.ui?.left] })">
<slot name="left">
<div v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
<slot name="title">
{{ title }}
</slot>
</div>
</slot>
</div>

<div :class="ui.center({ class: [props.ui?.center] })">
<slot name="center" />
</div>

<div :class="ui.right({ class: [props.ui?.right] })">
<slot name="right" />
</div>
</slot>
</Primitive>
</template>
1 change: 1 addition & 0 deletions src/runtime/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export * from '../components/Textarea.vue'
export * from '../components/Timeline.vue'
export * from '../components/Toast.vue'
export * from '../components/Toaster.vue'
export * from '../components/Toolbar.vue'
export * from '../components/Tooltip.vue'
export * from '../components/Tree.vue'
export * from './form'
Expand Down
1 change: 1 addition & 0 deletions src/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ export { default as textarea } from './textarea'
export { default as timeline } from './timeline'
export { default as toast } from './toast'
export { default as toaster } from './toaster'
export { default as toolbar } from './toolbar'
export { default as tooltip } from './tooltip'
export { default as tree } from './tree'
60 changes: 60 additions & 0 deletions src/theme/toolbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export default {
slots: {
root: 'flex justify-between items-center shrink-0 no-wrap relative w-full px-3 overflow-x-auto',
title: 'text-pretty pr-3 truncate font-semibold text-highlighted',
left: 'flex items-center',
right: 'flex items-center',
center: 'flex items-center'
},
variants: {
variant: {
solid: {
root: 'bg-inverted text-inverted',
title: 'text-inverted'
},
outline: {
root: 'bg-default border border-default'
},
soft: {
root: 'bg-elevated/50'
},
subtle: {
root: 'bg-elevated/50 border border-default'
}
},
size: {
sm: {
root: 'gap-1 min-h-[40px]',
title: 'text-sm',
left: 'gap-1',
center: 'gap-1',
right: 'gap-1'
},
md: {
root: 'gap-1.5 min-h-[49px]',
title: 'text-base',
left: 'gap-1.5',
center: 'gap-1.5',
right: 'gap-1.5'
},
lg: {
root: 'gap-2 min-h-14',
title: 'text-lg',
left: 'gap-2',
center: 'gap-2',
right: 'gap-2'
},
xl: {
root: 'gap-3 min-h-16',
title: 'text-xl',
left: 'gap-3',
center: 'gap-3',
right: 'gap-3'
}
}
},
defaultVariants: {
variant: 'outline',
size: 'md'
}
}
26 changes: 26 additions & 0 deletions test/components/Toolbar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, it, expect } from 'vitest'
import Toolbar, { type ToolbarProps, type ToolbarSlots } from '../../src/runtime/components/Toolbar.vue'

Check failure on line 2 in test/components/Toolbar.spec.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Prefer using a top-level type-only import instead of inline type specifiers

Check failure on line 2 in test/components/Toolbar.spec.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Prefer using a top-level type-only import instead of inline type specifiers
import ComponentRender from '../component-render'
import theme from '#build/ui/toolbar'

describe('Toolbar', () => {
const variants = Object.keys(theme.variants.variant) as any
const sizes = Object.keys(theme.variants.size) as any

it.each([
// Props
['with as', { props: { as: 'section' } }],
...variants.map((variant: string) => [`with variant ${variant}`, { props: { variant } }]),
...sizes.map((size: string) => [`with variant ${size}`, { props: { size } }]),
['with class', { props: { class: 'border-0 border-b' } }],
['with ui', { props: { ui: { root: 'border-x-0' } } }],
// Slots
['with left slot', { slots: { left: () => 'Left slot' } }],
['with title slot', { slots: { title: () => 'Title slot' } }],
['with right slot', { slots: { right: () => 'Right slot' } }],
['with center slot', { slots: { center: () => 'Center slot' } }]
])('renders %s correctly', async (nameOrHtml: string, options: { props?: ToolbarProps, slots?: Partial<ToolbarSlots> }) => {
const html = await ComponentRender(nameOrHtml, options, Toolbar)
expect(html).toMatchSnapshot()
})
})
Loading
Loading