Skip to content

Commit 4364df7

Browse files
committed
feat(Toolbar): new component
1 parent 04f12ad commit 4364df7

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

playground-vue/src/app.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const components = [
6464
'textarea',
6565
'timeline',
6666
'toast',
67+
'toolbar',
6768
'tooltip',
6869
'tree'
6970
]

playground/app/app.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const components = [
6464
'textarea',
6565
'timeline',
6666
'toast',
67+
'toolbar',
6768
'tooltip',
6869
'tree'
6970
]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<script setup lang="ts">
2+
import theme from '#build/ui/toolbar'
3+
4+
const variants = Object.keys(theme.variants.variant)
5+
6+
const variant = ref(theme.defaultVariants.variant)
7+
const title = ref('Toolbar Title')
8+
</script>
9+
10+
<template>
11+
<div class=" space-y-4">
12+
<div class="space-x-2">
13+
<UButtonGroup>
14+
<UBadge color="neutral" variant="outline" size="lg" label="title" />
15+
16+
<UInput v-model="title" :highlight="false" />
17+
</UButtonGroup>
18+
19+
<UButtonGroup>
20+
<UBadge color="neutral" variant="outline" size="lg" label="variant" />
21+
22+
<USelect v-model="variant" :items="variants" />
23+
</UButtonGroup>
24+
</div>
25+
<UToolbar :variant="variant">
26+
<Placeholder class="w-full h-8" />
27+
</UToolbar>
28+
29+
<UToolbar :variant="variant">
30+
<template #left>
31+
<Placeholder class="w-40 h-8" />
32+
</template>
33+
<template #center>
34+
<Placeholder class="w-40 h-8" />
35+
</template>
36+
<template #right>
37+
<Placeholder class="w-40 h-8" />
38+
</template>
39+
</UToolbar>
40+
41+
<UToolbar :variant="variant">
42+
<template #left>
43+
<Placeholder class="w-40 h-8" />
44+
</template>
45+
</UToolbar>
46+
47+
<UToolbar :variant="variant">
48+
<template #center>
49+
<Placeholder class="w-40 h-8" />
50+
</template>
51+
</UToolbar>
52+
53+
<UToolbar :variant="variant">
54+
<template #right>
55+
<Placeholder class="w-40 h-8" />
56+
</template>
57+
</UToolbar>
58+
59+
<UToolbar :title="title" />
60+
<UToolbar :title="title" variant="outline" :ui="{ root: 'border-x-0' }" />
61+
<UToolbar :title="title" variant="outline" class="border-0 border-b" />
62+
<UToolbar :title="title" variant="soft" />
63+
<UToolbar :title="title" variant="subtle" />
64+
<UToolbar :title="title" variant="solid" />
65+
</div>
66+
</template>

src/runtime/components/Toolbar.vue

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script lang="ts">
2+
import type { AppConfig } from '@nuxt/schema'
3+
import theme from '#build/ui/toolbar'
4+
import type { ComponentConfig } from '../types/utils'
5+
6+
type Toolbar = ComponentConfig<typeof theme, AppConfig, 'toolbar'>
7+
8+
export interface ToolbarProps {
9+
/**
10+
* The element or component this component should render as.
11+
* @defaultValue 'div'
12+
*/
13+
as?: any
14+
/**
15+
* @defaultValue 'outline'
16+
*/
17+
title?: string
18+
variant?: Toolbar['variants']['variant']
19+
class?: any
20+
ui?: Toolbar['slots']
21+
}
22+
export interface ToolbarSlots {
23+
default(props?: {}): any
24+
title(props?: {}): any
25+
left(props?: {}): any
26+
right(props?: {}): any
27+
center(props?: {}): any
28+
}
29+
</script>
30+
31+
<script setup lang="ts">
32+
import { computed } from 'vue'
33+
import { Primitive } from 'reka-ui'
34+
import { useAppConfig } from '#imports'
35+
import { tv } from '../utils/tv'
36+
37+
const props = defineProps<ToolbarProps>()
38+
const slots = defineSlots<ToolbarSlots>()
39+
40+
const appConfig = useAppConfig() as Toolbar['AppConfig']
41+
42+
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.toolbar || {}) })({
43+
variant: props.variant
44+
}))
45+
</script>
46+
47+
<template>
48+
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
49+
<slot>
50+
<div :class="ui.left({ class: [props.ui?.left] })">
51+
<slot name="left">
52+
<div v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
53+
<slot name="title">
54+
{{ title }}
55+
</slot>
56+
</div>
57+
</slot>
58+
</div>
59+
60+
<div :class="ui.center({ class: [props.ui?.center] })">
61+
<slot name="center" />
62+
</div>
63+
64+
<div :class="ui.right({ class: [props.ui?.right] })">
65+
<slot name="right" />
66+
</div>
67+
</slot>
68+
</Primitive>
69+
</template>

src/runtime/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export * from '../components/Textarea.vue'
5050
export * from '../components/Timeline.vue'
5151
export * from '../components/Toast.vue'
5252
export * from '../components/Toaster.vue'
53+
export * from '../components/Toolbar.vue'
5354
export * from '../components/Tooltip.vue'
5455
export * from '../components/Tree.vue'
5556
export * from './form'

src/theme/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ export { default as textarea } from './textarea'
4848
export { default as timeline } from './timeline'
4949
export { default as toast } from './toast'
5050
export { default as toaster } from './toaster'
51+
export { default as toolbar } from './toolbar'
5152
export { default as tooltip } from './tooltip'
5253
export { default as tree } from './tree'

src/theme/toolbar.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export default {
2+
slots: {
3+
root: 'flex justify-between items-center shrink-0 no-wrap relative w-full px-3 gap-1.5 overflow-x-auto min-h-[49px]',
4+
title: 'text-base text-pretty truncate font-semibold text-highlighted px-3',
5+
left: 'flex items-center gap-1.5',
6+
right: 'flex items-center gap-1.5',
7+
center: 'flex items-center gap-1.5'
8+
},
9+
variants: {
10+
variant: {
11+
solid: {
12+
root: 'bg-inverted text-inverted',
13+
title: 'text-inverted'
14+
},
15+
outline: {
16+
root: 'bg-default border border-default'
17+
},
18+
soft: {
19+
root: 'bg-elevated/50'
20+
},
21+
subtle: {
22+
root: 'bg-elevated/50 border border-default'
23+
}
24+
}
25+
},
26+
defaultVariants: {
27+
variant: 'outline'
28+
}
29+
}

test/components/Toolbar.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, it, expect } from 'vitest'
2+
import Toolbar, { type ToolbarProps, type ToolbarSlots } from '../../src/runtime/components/Toolbar.vue'
3+
import ComponentRender from '../component-render'
4+
import theme from '#build/ui/toolbar'
5+
6+
describe('Toolbar', () => {
7+
const variants = Object.keys(theme.variants.variant) as any
8+
9+
it.each([
10+
// Props
11+
['with as', { props: { as: 'section' } }],
12+
...variants.map((variant: string) => [`with variant ${variant}`, { props: { variant } }]),
13+
['with class', { props: { class: 'border-0 border-b' } }],
14+
['with ui', { props: { ui: { root: 'border-x-0' } } }],
15+
// Slots
16+
['with left slot', { slots: { left: () => 'Left slot' } }],
17+
['with title slot', { slots: { title: () => 'Title slot' } }],
18+
['with right slot', { slots: { right: () => 'Right slot' } }],
19+
['with center slot', { slots: { center: () => 'Center slot' } }]
20+
])('renders %s correctly', async (nameOrHtml: string, options: { props?: ToolbarProps, slots?: Partial<ToolbarSlots> }) => {
21+
const html = await ComponentRender(nameOrHtml, options, Toolbar)
22+
expect(html).toMatchSnapshot()
23+
})
24+
})

0 commit comments

Comments
 (0)