Dropdown ​
Allow users to choose one option from a floating list.
<script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ key: '3', label: 'Third item' },
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Open
</HButton>
</HDropdown>
</template>Examples ​
Option Types ​
<script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ key: '3', label: 'Third item' },
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Default
</HButton>
</HDropdown>
</template><script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ type: 'divider' },
{ key: '3', label: 'Third item' },
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Divider
</HButton>
</HDropdown>
</template><script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ key: '3', label: 'Third item' },
{ type: 'group', key: 'group-1', label: 'With icons' },
{ key: '4', label: 'Fourth item', icons: ['data-model'] },
{ key: '5', label: 'Fifth item', icons: ['data-model', 'data-model'] },
{ key: '6', label: 'Sixth item', icons: ['data-model', 'data-model', 'data-model'] },
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Group
</HButton>
</HDropdown>
</template><script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ key: '3', label: 'Third item' },
{
key: '4',
label: 'Nested Item',
children: [
{ key: '8-1', label: 'Level 2 Item 1' },
{ key: '8-2', label: 'Level 2 Item 2', icons: 'data-model' },
{ type: 'divider' },
{
key: '8-3',
label: 'More Nested (hover me)',
icons: 'data-model',
children: [
{ key: '8-3-1', label: 'Level 3 Item 1' },
{
key: '8-3-2',
label: 'No more!',
icons: 'lock',
disabled: true,
children: [
{ key: '7-3-2-1', label: 'Look me in the eye' },
{ key: '7-3-2-2', label: 'Tell me what you see' },
],
},
],
},
],
},
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Nested
</HButton>
</HDropdown>
</template><script setup lang="ts">
import { h } from 'vue'
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
import RenderComponent from './_RenderComponent.vue'
const options: DropdownOption[] = [
// @ts-ignore Mismatch Vue version of `h()` function
{ type: 'render', render: (renderProps) => h(RenderComponent, { renderProps }) },
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ key: '3', label: 'Third item' },
]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Render
</HButton>
</HDropdown>
</template><script setup lang="ts">
import { type DropdownOptionSlotProps, HIcon } from '@holistics/design-system'
const props = defineProps<{
renderProps: DropdownOptionSlotProps
}>()
const { keyField, labelField } = props.renderProps
</script>
<template>
<div>
<div class="flex items-center p-4">
<div class="flex size-10 items-center justify-center overflow-hidden rounded-full border border-gray-500">
<HIcon
size="lg"
name="data-model"
/>
</div>
<div class="ml-2">
<div class="text-base font-bold">
Wonderful Data Model
</div>
<div class="mt-0.5 text-xs italic text-gray-600">
Last edited: {{ new Date().toLocaleString() }}
</div>
</div>
</div>
<hr class="mx-auto mt-1 w-3/4">
<div class="p-1 text-center text-2xs italic text-gray-500">
(Key field: <code>"{{ keyField }}"</code>, Label field: <code>"{{ labelField }}"</code>)
</div>
</div>
</template><script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{ key: '1', label: 'First item' },
{ key: '2', label: 'Second item' },
{ key: '3', label: 'Third item' },
{ key: 'option-slot', slot: 'option-slot' },
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Slot
</HButton>
<template #option-slot="{ keyField, labelField }">
<div class="flex items-center justify-between p-2">
<span class="font-bold text-red-500">Key: {{ keyField }}</span>
<span class="ml-2 italic text-green-600">Label: {{ labelField }}</span>
</div>
</template>
</HDropdown>
</template>Custom CSS & Styles for Options ​
<script setup lang="ts">
import { type DropdownOption, HDropdown, HButton } from '@holistics/design-system'
const options = [
{
key: 'custom-css-default',
label: 'I look like a Default Option, but my text is blue, bold, and italic!',
class: 'text-blue-600 font-bold italic',
icons: 'data-model',
},
{ type: 'divider' },
{
type: 'group',
key: 'custom-css-group',
label: '(Truely) Disabled Group',
class: 'text-red-500',
icons: 'cancel',
},
{
key: 'custom-css-disabled-item-guide',
label: "Can you see the thickness of the divider right below? It's 20px!",
disabled: true,
class: 'italic',
icons: 'arrow-down',
},
{
type: 'divider',
style: { 'border-top-width': '20px' },
},
{
key: 'option',
label: 'Normal Option',
},
] as const satisfies DropdownOption[]
</script>
<template>
<HDropdown :options="options">
<HButton type="primary-highlight">
Open
</HButton>
</HDropdown>
</template>API ​
Pass-through: <HPopper> ​
What does this mean?
All props, events, and attrs that are not specified in the tables below will be passed to the element/component described above.
Props ​
| Name | Type | Description |
|---|---|---|
options * | Readonly<DropdownOption<any, "key", "label">>[] | |
disabled | boolean | When |
anchor | PopperAnchorRect | PopperAnchorElement | Anchor element. Can be an object with coordinates or a virtual element. {@link https://floating-ui.com/docs/virtual-elements} |
safeAreaDuration | number | Duration (ms) of the safe area to exist before closing. |
disableOutsidePointerEvents | boolean | When To interact with any element within lower layers, user will need to click outside of all layers with
This can be used as a way to force all lower layers to not emit |
strategy | Strategy | The type of CSS {@link https://floating-ui.com/docs/computeposition#strategy} |
disableTransform | boolean | By default, the Floating element is positioned using {@link https://floating-ui.com/docs/vue#disabling-transform} |
placement | Placement= "bottom-start" | The placement of the Floating element relative to the Anchor element. {@link https://floating-ui.com/docs/computeposition#placement} |
distance | number= 2 | The distance between the Floating element and the Anchor element (applied on the axis that runs along the side of the Floating element). {@link https://floating-ui.com/docs/offset#mainaxis} |
skidding | number | The skidding between the Floating element and the Anchor element (applied on the axis that runs along the alignment of the Floating element). {@link https://floating-ui.com/docs/offset#crossaxis} |
allowCollisions | boolean | Whether to allow the Floating element to collide with the boundary. |
collisionBoundary | Boundary | The clipping element(s) or area that collision will be checked relative to. {@link https://floating-ui.com/docs/detectOverflow#boundary} |
collisionPadding | Padding | The virtual padding around the boundary to check for collision. {@link https://floating-ui.com/docs/detectOverflow#padding} |
noShift | boolean | When {@link https://floating-ui.com/docs/shift} |
noFlip | boolean | When {@link https://floating-ui.com/docs/flip} |
prioritizePlacement | boolean | Whether to prioritize shifting (i.e. keeping original {@link https://floating-ui.com/docs/flip#combining-with-shift} |
arrow | boolean | Whether to render arrow elements. The arrow has 2 parts: outer and inner. The inner will always overlap the outer to be able to create a continuous border connecting the arrow with the Floating element. |
arrowPadding | Padding | The padding between the arrow and the edges of the Floating element. {@link https://floating-ui.com/docs/arrow#padding} |
autoAvailableSize | boolean | Whether to automatically set |
matchAnchorSize | boolean | "min" | "max" | Config to match the Anchor "size". The "size" here is detected by the final side of the Floating element:
|
transformOrigin | boolean | Whether to add CSS |
hideWhenDetached | boolean | Whether to hide the Floating element when the Anchor element becomes fully occluded. |
updatePositionStrategy | "optimized" | "always" | "none" | Strategy to update the position of the Floating element when necessary ( {@link https://floating-ui.com/docs/autoupdate} |
trapFocus | boolean | Whether to trap focus so it cannot escape the Floating element via keyboard, pointer, or a programmatic focus. |
autoFocus | boolean | Whether to automatically focus first focusable element inside the Floating element on mount. |
autoFocusElement | string | true | FocusableElement | null | Custom element used to automatically focus on mount or when calling the exposed
|
floatingProps | HTMLAttributes | Additional props bound to the Floating element. |
contentClass | HTMLAttributeClass | HTML |
contentStyle | StyleValue | HTML |
contentProps | HTMLAttributes | Additional props bound to the Content element. |
trigger | PopperTrigger= "click" | The type of event to automatically control opening state of the Floating element. |
open | boolean | The opening state of the Floating element. |
delay | number | null | The delay duration (in milliseconds) from when hovering the Anchor element until the NOTE:
|
disableHoverableContent | boolean | When |
forceHoverProviderSafeAreas | boolean | By default, |
floatingTeleportTo | string | RendererElement | null | Specify target container. Can either be a selector or an actual element. {@link https://vuejs.org/api/built-in-components.html#teleport} |
floatingTeleportDisabled | boolean | When {@link https://vuejs.org/api/built-in-components.html#teleport} |
floatingTeleportDefer | boolean | When {@link https://vuejs.org/api/built-in-components.html#teleport} |
floatingClass | HTMLAttributeClass | HTML |
floatingStyle | StyleValue | HTML |
keyField | string= "key" | |
labelField | string= "label" | |
maxHeight | string= "none" |
Events ​
| Name | Parameters | Description |
|---|---|---|
@select | [option: DropdownOption<any, string, string>] | |
@update:open | [opened: boolean] | |
@postOpen | [event: CustomEvent<any>] | |
@postClose | [event: CustomEvent<any>] | |
@pointerDownOutside | [event: PointerDownOutsideEvent] | |
@focusOutside | [event: FocusOutsideEvent] | |
@interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent] | |
@keydownEscape | [event: KeyboardEvent] | |
@autoFocusOnMount | [event: CustomEvent<any>] | |
@autoFocusOnUnmount | [event: CustomEvent<any>] | |
@open | () => void | |
@close | (noForce?: boolean, immediate?: boolean) => void |
Slots ​
| Name | Scoped | Description |
|---|---|---|
#default | { open: boolean; onOpen: () => void; onClose: (force?: boolean | undefined, propagated?: boolean | undefined) => void; updatePosition: EventHookTrigger<void>; } |
Exposed ​
| Name | Type | Description |
|---|---|---|
popperRefByKey | Partial<Record<PopperRefKey, { $: ComponentInternalInstance; $data: {}; $props: { readonly open?: boolean | undefined; readonly trigger: PopperTrigger; ... 46 more ...; readonly onAutoFocusOnUnmount?: ((event: CustomEvent<...>) => any) | undefined; } & VNodeProps & AllowedComponentProps & ComponentCustomProps; ... 1... | |
openedPopperRefKeys | Set<PopperRefKey> | |
updatePosition | () => Promise<void> |