frappe-ui 1.0.0-beta.13 → 1.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/CodeEditor/CodeEditor.cy.ts +5 -1
- package/src/components/Combobox/Combobox.vue +9 -11
- package/src/components/DatePicker/DatePicker.vue +3 -5
- package/src/components/DatePicker/DateRangePicker.vue +1 -1
- package/src/components/DatePicker/DateTimePicker.vue +3 -5
- package/src/components/HoverCard/HoverCard.api.md +101 -0
- package/src/components/HoverCard/HoverCard.cy.ts +103 -0
- package/src/components/HoverCard/HoverCard.md +52 -0
- package/src/components/HoverCard/HoverCard.vue +91 -0
- package/src/components/HoverCard/index.ts +2 -0
- package/src/components/HoverCard/stories/Arrow.vue +28 -0
- package/src/components/HoverCard/stories/Delays.vue +16 -0
- package/src/components/HoverCard/stories/Example.vue +28 -0
- package/src/components/HoverCard/types.ts +47 -0
- package/src/components/Popover/Popover.api.md +126 -41
- package/src/components/Popover/Popover.cy.ts +373 -41
- package/src/components/Popover/Popover.md +99 -4
- package/src/components/Popover/Popover.vue +271 -209
- package/src/components/Popover/index.ts +8 -1
- package/src/components/Popover/stories/Arrow.vue +16 -0
- package/src/components/Popover/stories/Bare.vue +20 -0
- package/src/components/Popover/stories/Click.vue +7 -4
- package/src/components/Popover/stories/Controlled.vue +22 -0
- package/src/components/Popover/stories/Dismissible.vue +19 -0
- package/src/components/Popover/stories/MatchTriggerWidth.vue +16 -0
- package/src/components/Popover/stories/SideAlign.vue +17 -0
- package/src/components/Popover/types.ts +98 -23
- package/src/components/Select/Select.vue +207 -192
- package/src/components/Tooltip/Tooltip.api.md +41 -3
- package/src/components/Tooltip/types.ts +1 -1
- package/src/components/shared/picker/PickerShell.vue +17 -14
- package/src/components/shared/popover/PopoverPanel.vue +67 -0
- package/src/components/shared/popover/popoverPanel.css +102 -0
- package/src/data-fetching/docStore.test.ts +73 -0
- package/src/data-fetching/docStore.ts +16 -3
- package/src/data-fetching/useDoc/useDoc.test.ts +59 -0
- package/src/data-fetching/useDoc/useDoc.ts +29 -13
- package/src/index.ts +1 -0
- package/src/molecules/editor/EditorFixedMenu.vue +2 -1
- package/src/molecules/editor/MenuItems.vue +26 -33
- package/tailwind/colorPalette.js +20 -2
- package/tailwind/colors.json +313 -313
- package/src/components/Popover/stories/Hover.vue +0 -14
package/package.json
CHANGED
|
@@ -75,8 +75,12 @@ describe('CodeEditor', () => {
|
|
|
75
75
|
cy.mount(CodeEditor, {
|
|
76
76
|
props: { modelValue: 'frozen', disabled: true },
|
|
77
77
|
}).then(({ wrapper }) => {
|
|
78
|
+
// `setProps` is synchronous JS, so it must be wrapped in `cy.then` to join
|
|
79
|
+
// the command queue *after* the first assertion — otherwise it flips the
|
|
80
|
+
// prop before that assertion runs and the initial `false` state is never
|
|
81
|
+
// observed (the flake this guards against).
|
|
78
82
|
cy.get('.cm-content').should('have.attr', 'contenteditable', 'false')
|
|
79
|
-
wrapper.setProps({ disabled: false })
|
|
83
|
+
cy.then(() => wrapper.setProps({ disabled: false }))
|
|
80
84
|
cy.get('.cm-content').should('have.attr', 'contenteditable', 'true')
|
|
81
85
|
})
|
|
82
86
|
})
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
FocusScope,
|
|
19
19
|
} from 'reka-ui'
|
|
20
20
|
import OptionIcon from '../shared/selection/OptionIcon.vue'
|
|
21
|
-
import '../shared/
|
|
21
|
+
import PopoverPanel from '../shared/popover/PopoverPanel.vue'
|
|
22
22
|
import ComboboxResults from './ComboboxResults.vue'
|
|
23
23
|
import { usePopoverMotion } from '../../composables/usePopoverMotion'
|
|
24
24
|
import { useInputLabeling } from '../../composables/useInputLabeling'
|
|
@@ -637,7 +637,6 @@ defineSlots<ComboboxSlots>()
|
|
|
637
637
|
<ComboboxPortal :to="portalTo">
|
|
638
638
|
<ComboboxContent
|
|
639
639
|
data-slot="content"
|
|
640
|
-
data-selection
|
|
641
640
|
:data-variant="variant"
|
|
642
641
|
:data-size="size"
|
|
643
642
|
:class="[
|
|
@@ -675,10 +674,9 @@ defineSlots<ComboboxSlots>()
|
|
|
675
674
|
@mount-auto-focus="handleFocusScopeMountAutoFocus"
|
|
676
675
|
@unmount-auto-focus.prevent
|
|
677
676
|
>
|
|
678
|
-
<
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
class="overflow-hidden rounded-lg bg-surface-elevation-2 shadow-2xl ring-1 ring-black ring-opacity-5"
|
|
677
|
+
<PopoverPanel
|
|
678
|
+
:motion="contentMotion"
|
|
679
|
+
class="origin-[var(--reka-combobox-content-transform-origin)]"
|
|
682
680
|
>
|
|
683
681
|
<div
|
|
684
682
|
v-if="isButtonMode"
|
|
@@ -718,7 +716,7 @@ defineSlots<ComboboxSlots>()
|
|
|
718
716
|
<div v-if="$slots.footer" data-slot="footer">
|
|
719
717
|
<slot name="footer" v-bind="controlSlotProps" />
|
|
720
718
|
</div>
|
|
721
|
-
</
|
|
719
|
+
</PopoverPanel>
|
|
722
720
|
</FocusScope>
|
|
723
721
|
</ComboboxContent>
|
|
724
722
|
</ComboboxPortal>
|
|
@@ -741,10 +739,10 @@ defineSlots<ComboboxSlots>()
|
|
|
741
739
|
outline: none !important;
|
|
742
740
|
}
|
|
743
741
|
|
|
744
|
-
/*
|
|
745
|
-
|
|
746
|
-
[data-slot='content
|
|
747
|
-
|
|
742
|
+
/* Block clicks while the panel plays its exit animation (the shell + open/close
|
|
743
|
+
motion now come from PopoverPanel; only this component-specific rule remains). */
|
|
744
|
+
[data-slot='content'][data-state='closed'] {
|
|
745
|
+
pointer-events: none;
|
|
748
746
|
}
|
|
749
747
|
|
|
750
748
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -120,11 +120,9 @@ const emit = defineEmits<DatePickerEmits>()
|
|
|
120
120
|
|
|
121
121
|
const slots = defineSlots<DatePickerSlots>()
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const contentClass = computed(() =>
|
|
126
|
-
slots.actions ? `w-fit ${POPOVER_CLASSES}` : `w-56 ${POPOVER_CLASSES}`,
|
|
127
|
-
)
|
|
123
|
+
// Layout only — the elevated shell (rounded/bg/shadow/ring) is owned by
|
|
124
|
+
// PopoverPanel inside PickerShell.
|
|
125
|
+
const contentClass = computed(() => (slots.actions ? 'w-fit' : 'w-56'))
|
|
128
126
|
|
|
129
127
|
// Cast strips @deprecated markers so internal back-compat reads don't trigger TS6385.
|
|
130
128
|
const dp = props as unknown as LegacyDatePickerProps
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
:readonly="inputReadonly"
|
|
22
22
|
:input-class="dp.inputClass"
|
|
23
23
|
:display-label="displayLabel"
|
|
24
|
-
content-class="w-fit
|
|
24
|
+
content-class="w-fit"
|
|
25
25
|
@blur="commitInput()"
|
|
26
26
|
@enter="commitInput(true)"
|
|
27
27
|
@open="onShellOpen"
|
|
@@ -137,11 +137,9 @@ const emit = defineEmits<DateTimePickerEmits>()
|
|
|
137
137
|
|
|
138
138
|
const slots = defineSlots<DateTimePickerSlots>()
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const contentClass = computed(() =>
|
|
143
|
-
slots.actions ? `w-fit ${POPOVER_CLASSES}` : `w-56 ${POPOVER_CLASSES}`,
|
|
144
|
-
)
|
|
140
|
+
// Layout only — the elevated shell (rounded/bg/shadow/ring) is owned by
|
|
141
|
+
// PopoverPanel inside PickerShell.
|
|
142
|
+
const contentClass = computed(() => (slots.actions ? 'w-fit' : 'w-56'))
|
|
145
143
|
|
|
146
144
|
const dp = props as unknown as LegacyDatePickerProps
|
|
147
145
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<!-- Auto Generated by scripts/propsgen.ts -->
|
|
2
|
+
<script setup>
|
|
3
|
+
import PropsTable from '@/components/Docs/PropsTable.vue'
|
|
4
|
+
import SlotsTable from '@/components/Docs/SlotsTable.vue'
|
|
5
|
+
import EmitsTable from '@/components/Docs/EmitsTable.vue'
|
|
6
|
+
|
|
7
|
+
const propsData = [
|
|
8
|
+
{
|
|
9
|
+
name: 'side',
|
|
10
|
+
description: 'Side of the trigger the card is placed on.',
|
|
11
|
+
required: false,
|
|
12
|
+
type: '"bottom" | "top" | "right" | "left"',
|
|
13
|
+
default: '"bottom"'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'align',
|
|
17
|
+
description: 'Alignment of the card relative to the trigger.',
|
|
18
|
+
required: false,
|
|
19
|
+
type: '"start" | "center" | "end"',
|
|
20
|
+
default: '"start"'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'offset',
|
|
24
|
+
description: 'Distance in pixels between the card and the trigger.',
|
|
25
|
+
required: false,
|
|
26
|
+
type: 'number',
|
|
27
|
+
default: '4'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'portalTo',
|
|
31
|
+
description: 'Where the card is teleported to in the DOM.',
|
|
32
|
+
required: false,
|
|
33
|
+
type: 'string | HTMLElement',
|
|
34
|
+
default: '"body"'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'collisionPadding',
|
|
38
|
+
description: 'Padding (in pixels) kept between the card and the viewport edges when\nrepositioning to avoid collisions.',
|
|
39
|
+
required: false,
|
|
40
|
+
type: 'number',
|
|
41
|
+
default: '10'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'hoverDelay',
|
|
45
|
+
description: 'Delay (in seconds) from when the pointer enters the trigger until the card\nopens. Matches the Tooltip convention of using seconds.',
|
|
46
|
+
required: false,
|
|
47
|
+
type: 'number',
|
|
48
|
+
default: '0.3'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'leaveDelay',
|
|
52
|
+
description: 'Delay (in seconds) from when the pointer leaves the trigger or card until\nthe card closes.',
|
|
53
|
+
required: false,
|
|
54
|
+
type: 'number',
|
|
55
|
+
default: '0.3'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'arrow',
|
|
59
|
+
description: 'Render a small arrow pointing at the trigger. Styled to match the panel\nsurface.',
|
|
60
|
+
required: false,
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
default: 'false'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'open',
|
|
66
|
+
description: '',
|
|
67
|
+
required: false,
|
|
68
|
+
type: 'boolean',
|
|
69
|
+
default: 'false'
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
const slotsData = [
|
|
74
|
+
{
|
|
75
|
+
name: 'trigger',
|
|
76
|
+
description: 'Trigger element. Rendered as-child so hover/focus a11y is auto-wired.',
|
|
77
|
+
type: '{ open: boolean; }'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'default',
|
|
81
|
+
description: 'Card contents, rendered inside the standard PopoverPanel shell.',
|
|
82
|
+
type: 'any'
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
const emitsData = [
|
|
87
|
+
{
|
|
88
|
+
name: 'update:open',
|
|
89
|
+
description: 'Fired when the open state changes.',
|
|
90
|
+
type: 'unknown[]'
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
</script>
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
<PropsTable name="HoverCard" :data="propsData"/>
|
|
97
|
+
|
|
98
|
+
<SlotsTable :data="slotsData"/>
|
|
99
|
+
|
|
100
|
+
<EmitsTable :data="emitsData"/>
|
|
101
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { defineComponent, h, ref } from 'vue'
|
|
2
|
+
import HoverCard from './HoverCard.vue'
|
|
3
|
+
import Button from '../Button/Button.vue'
|
|
4
|
+
|
|
5
|
+
const Slots = {
|
|
6
|
+
trigger: () => h(Button, { 'data-cy': 'trigger' }, () => 'Hover me'),
|
|
7
|
+
default: () => h('div', { 'data-cy': 'content' }, 'Card content'),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('HoverCard', () => {
|
|
11
|
+
it('opens on hover after the hoverDelay and renders inside the panel shell', () => {
|
|
12
|
+
cy.clock()
|
|
13
|
+
cy.mount(HoverCard, {
|
|
14
|
+
slots: Slots,
|
|
15
|
+
props: { hoverDelay: 0.5, leaveDelay: 0.3 },
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
cy.get('[data-slot="content"]').should('not.exist')
|
|
19
|
+
cy.get('[data-cy="trigger"]').trigger('pointerenter')
|
|
20
|
+
|
|
21
|
+
// Still closed before the delay elapses.
|
|
22
|
+
cy.get('[data-slot="content"]').should('not.exist')
|
|
23
|
+
cy.tick(500)
|
|
24
|
+
|
|
25
|
+
cy.get('[data-slot="content"]').should('exist')
|
|
26
|
+
cy.get('[data-slot="content"]')
|
|
27
|
+
.find('[data-slot="content-body"]')
|
|
28
|
+
.find('[data-cy="content"]')
|
|
29
|
+
.should('have.text', 'Card content')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('closes when the open model is set back to false', () => {
|
|
33
|
+
// reka owns the leave-delay timing internally; here we verify the wrapper's
|
|
34
|
+
// two-way open binding tears the card down when the parent closes it.
|
|
35
|
+
const Harness = defineComponent({
|
|
36
|
+
setup() {
|
|
37
|
+
const open = ref(false)
|
|
38
|
+
return () =>
|
|
39
|
+
h('div', [
|
|
40
|
+
h(
|
|
41
|
+
Button,
|
|
42
|
+
{
|
|
43
|
+
'data-cy': 'toggle',
|
|
44
|
+
onClick: () => (open.value = !open.value),
|
|
45
|
+
},
|
|
46
|
+
() => 'Toggle',
|
|
47
|
+
),
|
|
48
|
+
h(
|
|
49
|
+
HoverCard,
|
|
50
|
+
{
|
|
51
|
+
open: open.value,
|
|
52
|
+
'onUpdate:open': (value: boolean) => (open.value = value),
|
|
53
|
+
hoverDelay: 0,
|
|
54
|
+
},
|
|
55
|
+
Slots,
|
|
56
|
+
),
|
|
57
|
+
])
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
cy.mount(Harness)
|
|
62
|
+
cy.get('[data-cy="toggle"]').click()
|
|
63
|
+
cy.get('[data-slot="content"]').should('exist')
|
|
64
|
+
cy.get('[data-cy="toggle"]').click()
|
|
65
|
+
cy.get('[data-slot="content"]').should('not.exist')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('positions the card via side + align', () => {
|
|
69
|
+
cy.clock()
|
|
70
|
+
cy.mount(HoverCard, {
|
|
71
|
+
slots: Slots,
|
|
72
|
+
props: { hoverDelay: 0, side: 'right', align: 'end' },
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
cy.get('[data-cy="trigger"]').trigger('pointerenter')
|
|
76
|
+
cy.tick(0)
|
|
77
|
+
cy.get('[data-slot="content"]')
|
|
78
|
+
.should('have.attr', 'data-side', 'right')
|
|
79
|
+
.and('have.attr', 'data-align', 'end')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('renders an arrow when arrow is set', () => {
|
|
83
|
+
cy.clock()
|
|
84
|
+
cy.mount(HoverCard, { slots: Slots, props: { hoverDelay: 0, arrow: true } })
|
|
85
|
+
|
|
86
|
+
cy.get('[data-cy="trigger"]').trigger('pointerenter')
|
|
87
|
+
cy.tick(0)
|
|
88
|
+
cy.get('[data-slot="content"]').find('[data-slot="arrow"]').should('exist')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('always uses animated motion for hover opens', () => {
|
|
92
|
+
cy.clock()
|
|
93
|
+
cy.mount(HoverCard, { slots: Slots, props: { hoverDelay: 0 } })
|
|
94
|
+
|
|
95
|
+
cy.get('[data-cy="trigger"]').trigger('pointerenter')
|
|
96
|
+
cy.tick(0)
|
|
97
|
+
cy.get('[data-slot="content-body"]').should(
|
|
98
|
+
'have.attr',
|
|
99
|
+
'data-motion',
|
|
100
|
+
'animated',
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# HoverCard
|
|
2
|
+
|
|
3
|
+
Shows a floating panel when the pointer rests on (or the keyboard focuses) a
|
|
4
|
+
trigger — for profile previews, link previews, and other sighted-only context.
|
|
5
|
+
Built on [reka-ui](https://reka-ui.com/)'s `HoverCard` primitives.
|
|
6
|
+
|
|
7
|
+
Use `HoverCard` for hover-driven panels and [`Popover`](./popover) for
|
|
8
|
+
click-driven ones. For short text hints prefer [`Tooltip`](./tooltip) instead.
|
|
9
|
+
|
|
10
|
+
## Example
|
|
11
|
+
|
|
12
|
+
`#trigger` is rendered as-child, so hover and focus a11y are wired
|
|
13
|
+
automatically. `#default` renders inside the standard panel shell.
|
|
14
|
+
|
|
15
|
+
<ComponentPreview name="HoverCard-Example" />
|
|
16
|
+
|
|
17
|
+
## Delays
|
|
18
|
+
|
|
19
|
+
`hoverDelay` and `leaveDelay` are in **seconds** (consistent with `Tooltip`).
|
|
20
|
+
`hoverDelay` is how long the pointer must rest before the card opens;
|
|
21
|
+
`leaveDelay` is how long after the pointer leaves before it closes.
|
|
22
|
+
|
|
23
|
+
<ComponentPreview name="HoverCard-Delays" />
|
|
24
|
+
|
|
25
|
+
## Arrow
|
|
26
|
+
|
|
27
|
+
Set `arrow` to render a small arrow pointing at the trigger, styled to match the
|
|
28
|
+
panel surface.
|
|
29
|
+
|
|
30
|
+
<ComponentPreview name="HoverCard-Arrow" />
|
|
31
|
+
|
|
32
|
+
## Styling
|
|
33
|
+
|
|
34
|
+
Like `Popover`, the card owns its panel shell and exposes the same stable
|
|
35
|
+
`data-slot` hooks — `[data-slot="content"]` for the portaled content and
|
|
36
|
+
`[data-slot="content-body"]` for the shell that owns the visuals. There are no
|
|
37
|
+
class-injection props.
|
|
38
|
+
|
|
39
|
+
## Motion
|
|
40
|
+
|
|
41
|
+
A hover open always uses the animated scale-from-trigger entrance (180ms in /
|
|
42
|
+
140ms out), and `prefers-reduced-motion` is respected. No configuration is
|
|
43
|
+
required.
|
|
44
|
+
|
|
45
|
+
## Notes
|
|
46
|
+
|
|
47
|
+
- `HoverCard` is for sighted-only enhancement — don't put essential actions or
|
|
48
|
+
information that can only be reached by hovering.
|
|
49
|
+
- The card stays open while the pointer is over either the trigger or the card,
|
|
50
|
+
so users can move into it to interact.
|
|
51
|
+
|
|
52
|
+
<!-- @include: ./HoverCard.api.md -->
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
HoverCardArrow,
|
|
4
|
+
HoverCardContent,
|
|
5
|
+
HoverCardPortal,
|
|
6
|
+
HoverCardRoot,
|
|
7
|
+
HoverCardTrigger,
|
|
8
|
+
} from 'reka-ui'
|
|
9
|
+
import { computed } from 'vue'
|
|
10
|
+
import PopoverPanel from '../shared/popover/PopoverPanel.vue'
|
|
11
|
+
import type { HoverCardProps } from './types'
|
|
12
|
+
|
|
13
|
+
defineOptions({
|
|
14
|
+
inheritAttrs: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<HoverCardProps>(), {
|
|
18
|
+
side: 'bottom',
|
|
19
|
+
align: 'start',
|
|
20
|
+
offset: 4,
|
|
21
|
+
portalTo: 'body',
|
|
22
|
+
collisionPadding: 10,
|
|
23
|
+
hoverDelay: 0.3,
|
|
24
|
+
leaveDelay: 0.3,
|
|
25
|
+
arrow: false,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const open = defineModel<boolean>('open', { default: false })
|
|
29
|
+
|
|
30
|
+
// reka HoverCard delays are in milliseconds; the public API uses seconds to
|
|
31
|
+
// stay consistent with Tooltip.
|
|
32
|
+
const openDelay = computed(() => props.hoverDelay * 1000)
|
|
33
|
+
const closeDelay = computed(() => props.leaveDelay * 1000)
|
|
34
|
+
|
|
35
|
+
defineExpose({
|
|
36
|
+
open: () => {
|
|
37
|
+
open.value = true
|
|
38
|
+
},
|
|
39
|
+
close: () => {
|
|
40
|
+
open.value = false
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
defineSlots<{
|
|
45
|
+
/** Trigger element. Rendered as-child so hover/focus a11y is auto-wired. */
|
|
46
|
+
trigger?: (props: { open: boolean }) => any
|
|
47
|
+
/** Card contents, rendered inside the standard PopoverPanel shell. */
|
|
48
|
+
default?: () => any
|
|
49
|
+
}>()
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<HoverCardRoot
|
|
54
|
+
v-model:open="open"
|
|
55
|
+
:open-delay="openDelay"
|
|
56
|
+
:close-delay="closeDelay"
|
|
57
|
+
>
|
|
58
|
+
<HoverCardTrigger as-child>
|
|
59
|
+
<slot name="trigger" :open="open" />
|
|
60
|
+
</HoverCardTrigger>
|
|
61
|
+
<HoverCardPortal :to="portalTo">
|
|
62
|
+
<HoverCardContent
|
|
63
|
+
data-slot="content"
|
|
64
|
+
class="z-[100]"
|
|
65
|
+
:side="side"
|
|
66
|
+
:align="align"
|
|
67
|
+
:side-offset="offset"
|
|
68
|
+
:collision-padding="collisionPadding"
|
|
69
|
+
v-bind="$attrs"
|
|
70
|
+
>
|
|
71
|
+
<!--
|
|
72
|
+
Hover opens always want the scale-from-trigger entrance, so motion is
|
|
73
|
+
fixed to 'animated' rather than driven by usePopoverMotion (which keys
|
|
74
|
+
on pointerdown and would classify a hover open as 'instant'). The
|
|
75
|
+
panel relies on the ancestor HoverCardContent's data-state for motion.
|
|
76
|
+
-->
|
|
77
|
+
<PopoverPanel
|
|
78
|
+
motion="animated"
|
|
79
|
+
class="origin-[var(--reka-hover-card-content-transform-origin)]"
|
|
80
|
+
>
|
|
81
|
+
<slot />
|
|
82
|
+
</PopoverPanel>
|
|
83
|
+
<HoverCardArrow
|
|
84
|
+
v-if="arrow"
|
|
85
|
+
data-slot="arrow"
|
|
86
|
+
class="fill-surface-elevation-2"
|
|
87
|
+
/>
|
|
88
|
+
</HoverCardContent>
|
|
89
|
+
</HoverCardPortal>
|
|
90
|
+
</HoverCardRoot>
|
|
91
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Avatar, HoverCard } from 'frappe-ui'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<HoverCard side="top" arrow>
|
|
7
|
+
<template #trigger>
|
|
8
|
+
<a href="#" class="text-ink-gray-9 underline underline-offset-2">
|
|
9
|
+
@jane
|
|
10
|
+
</a>
|
|
11
|
+
</template>
|
|
12
|
+
<template #default>
|
|
13
|
+
<div class="flex w-64 gap-3 p-3">
|
|
14
|
+
<Avatar
|
|
15
|
+
label="Jane Doe"
|
|
16
|
+
image="https://avatars.githubusercontent.com/u/499550?v=4"
|
|
17
|
+
size="2xl"
|
|
18
|
+
/>
|
|
19
|
+
<div>
|
|
20
|
+
<p class="text-p-base font-medium text-ink-gray-9">Jane Doe</p>
|
|
21
|
+
<p class="mt-1 text-p-sm text-ink-gray-6">
|
|
22
|
+
Designer. Likes hover cards.
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
</HoverCard>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Button, HoverCard } from 'frappe-ui'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<HoverCard :hover-delay="0.1" :leave-delay="0.5">
|
|
7
|
+
<template #trigger>
|
|
8
|
+
<Button>Hover me</Button>
|
|
9
|
+
</template>
|
|
10
|
+
<template #default>
|
|
11
|
+
<div class="p-3 text-p-sm text-ink-gray-7">
|
|
12
|
+
Opens after 0.1s, closes 0.5s after the pointer leaves.
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
</HoverCard>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Avatar, HoverCard } from 'frappe-ui'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<HoverCard>
|
|
7
|
+
<template #trigger>
|
|
8
|
+
<a href="#" class="text-ink-gray-9 underline underline-offset-2">
|
|
9
|
+
@jane
|
|
10
|
+
</a>
|
|
11
|
+
</template>
|
|
12
|
+
<template #default>
|
|
13
|
+
<div class="flex w-64 gap-3 p-3">
|
|
14
|
+
<Avatar
|
|
15
|
+
label="Jane Doe"
|
|
16
|
+
image="https://avatars.githubusercontent.com/u/499550?v=4"
|
|
17
|
+
size="2xl"
|
|
18
|
+
/>
|
|
19
|
+
<div>
|
|
20
|
+
<p class="text-p-base font-medium text-ink-gray-9">Jane Doe</p>
|
|
21
|
+
<p class="mt-1 text-p-sm text-ink-gray-6">
|
|
22
|
+
Designer. Likes hover cards.
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
</HoverCard>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type HoverCardContentProps, type HoverCardPortalProps } from 'reka-ui'
|
|
2
|
+
|
|
3
|
+
export interface HoverCardProps {
|
|
4
|
+
/**
|
|
5
|
+
* Side of the trigger the card is placed on.
|
|
6
|
+
*/
|
|
7
|
+
side?: HoverCardContentProps['side']
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Alignment of the card relative to the trigger.
|
|
11
|
+
*/
|
|
12
|
+
align?: HoverCardContentProps['align']
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Distance in pixels between the card and the trigger.
|
|
16
|
+
*/
|
|
17
|
+
offset?: number
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Where the card is teleported to in the DOM.
|
|
21
|
+
*/
|
|
22
|
+
portalTo?: HoverCardPortalProps['to']
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Padding (in pixels) kept between the card and the viewport edges when
|
|
26
|
+
* repositioning to avoid collisions.
|
|
27
|
+
*/
|
|
28
|
+
collisionPadding?: number
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Delay (in seconds) from when the pointer enters the trigger until the card
|
|
32
|
+
* opens. Matches the Tooltip convention of using seconds.
|
|
33
|
+
*/
|
|
34
|
+
hoverDelay?: number
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Delay (in seconds) from when the pointer leaves the trigger or card until
|
|
38
|
+
* the card closes.
|
|
39
|
+
*/
|
|
40
|
+
leaveDelay?: number
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Render a small arrow pointing at the trigger. Styled to match the panel
|
|
44
|
+
* surface.
|
|
45
|
+
*/
|
|
46
|
+
arrow?: boolean
|
|
47
|
+
}
|