Skip to content

Commit 70b180e

Browse files
committed
feat: Add Vertical Dock
1 parent 472aa97 commit 70b180e

File tree

8 files changed

+138
-43
lines changed

8 files changed

+138
-43
lines changed

components/content/inspira/examples/DockDemo.vue

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<DockIcon>
44
<GitHubIcon />
55
</DockIcon>
6+
<DockSeparator />
67
<DockIcon>
78
<GoogleDriveIcon />
89
</DockIcon>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<Dock orientation="vertical">
3+
<DockIcon>
4+
<GitHubIcon />
5+
</DockIcon>
6+
<DockSeparator />
7+
<DockIcon>
8+
<GoogleDriveIcon />
9+
</DockIcon>
10+
<DockIcon>
11+
<NotionIcon />
12+
</DockIcon>
13+
<DockIcon>
14+
<WhatsAppIcon />
15+
</DockIcon>
16+
</Dock>
17+
</template>

components/content/inspira/ui/dock/Dock.vue

+37-24
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
:class="
55
cn(
66
'supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 mx-auto mt-8 flex h-[58px] w-max rounded-2xl border p-2 backdrop-blur-md transition-all gap-4',
7-
$props.class,
7+
orientation === 'vertical' && 'flex-col w-[58px] h-max',
8+
props.class,
89
dockClass,
910
)
1011
"
@@ -16,30 +17,37 @@
1617
</template>
1718

1819
<script setup lang="ts">
19-
import { ref, computed } from "vue";
20+
import { ref, computed, type HTMLAttributes } from "vue";
2021
import { cn } from "~/lib/utils";
22+
import type { DataOrientation, Direction } from "./types";
23+
import {
24+
MOUSE_X_INJECTION_KEY,
25+
MOUSE_Y_INJECTION_KEY,
26+
MAGNIFICATION_INJECTION_KEY,
27+
DISTANCE_INJECTION_KEY,
28+
ORIENTATION_INJECTION_KEY,
29+
} from "./injectionKeys";
2130
22-
const props = defineProps({
23-
class: {
24-
type: String,
25-
default: "",
26-
},
27-
magnification: {
28-
type: Number,
29-
default: 60,
30-
},
31-
distance: {
32-
type: Number,
33-
default: 140,
34-
},
35-
direction: {
36-
type: String,
37-
default: "middle",
38-
},
31+
interface DockProps {
32+
class?: HTMLAttributes["class"];
33+
magnification?: number;
34+
distance?: number;
35+
direction?: Direction;
36+
orientation?: DataOrientation;
37+
}
38+
39+
const props = withDefaults(defineProps<DockProps>(), {
40+
magnification: 60,
41+
distance: 140,
42+
direction: "middle",
43+
orientation: "horizontal",
3944
});
4045
4146
const dockRef = ref<HTMLElement | null>(null);
4247
const mouseX = ref(Infinity);
48+
const mouseY = ref(Infinity);
49+
const magnification = computed(() => props.magnification);
50+
const distance = computed(() => props.distance);
4351
4452
const dockClass = computed(() => ({
4553
"items-start": props.direction === "top",
@@ -50,14 +58,19 @@ const dockClass = computed(() => ({
5058
function onMouseMove(e: MouseEvent) {
5159
requestAnimationFrame(() => {
5260
mouseX.value = e.pageX;
61+
mouseY.value = e.pageY;
5362
});
5463
}
5564
5665
function onMouseLeave() {
57-
mouseX.value = Infinity;
66+
requestAnimationFrame(() => {
67+
mouseX.value = Infinity;
68+
mouseY.value = Infinity;
69+
});
5870
}
59-
60-
provide("mouseX", mouseX);
61-
provide("magnification", props.magnification);
62-
provide("distance", props.distance);
71+
provide(MOUSE_X_INJECTION_KEY, mouseX);
72+
provide(MOUSE_Y_INJECTION_KEY, mouseY);
73+
provide(ORIENTATION_INJECTION_KEY, props.orientation);
74+
provide(MAGNIFICATION_INJECTION_KEY, magnification);
75+
provide(DISTANCE_INJECTION_KEY, distance);
6376
</script>

components/content/inspira/ui/dock/DockIcon.vue

+26-7
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,44 @@
1717

1818
<script setup lang="ts">
1919
import { ref, inject, computed } from "vue";
20+
import {
21+
MOUSE_X_INJECTION_KEY,
22+
MOUSE_Y_INJECTION_KEY,
23+
MAGNIFICATION_INJECTION_KEY,
24+
DISTANCE_INJECTION_KEY,
25+
ORIENTATION_INJECTION_KEY,
26+
} from "./injectionKeys";
2027
2128
const iconRef = ref<HTMLDivElement | null>(null);
2229
23-
const mouseX = inject("mouseX", ref(Infinity));
24-
const magnification = inject("magnification", 60);
25-
const distance = inject("distance", 140);
30+
const mouseX = inject(MOUSE_X_INJECTION_KEY, ref(Infinity));
31+
const mouseY = inject(MOUSE_Y_INJECTION_KEY, ref(Infinity));
32+
const distance = inject(DISTANCE_INJECTION_KEY);
33+
const orientation = inject(ORIENTATION_INJECTION_KEY, "vertical");
34+
const magnification = inject(MAGNIFICATION_INJECTION_KEY);
35+
const isVertical = computed(() => orientation === "vertical");
2636
2737
const margin = ref(0);
2838
2939
function calculateDistance(val: number) {
40+
if (isVertical.value) {
41+
const bounds = iconRef.value?.getBoundingClientRect() || {
42+
y: 0,
43+
height: 0,
44+
};
45+
return val - bounds.y - bounds.height / 2;
46+
}
3047
const bounds = iconRef.value?.getBoundingClientRect() || { x: 0, width: 0 };
3148
return val - bounds.x - bounds.width / 2;
3249
}
3350
3451
const iconWidth = computed(() => {
35-
const distanceCalc = calculateDistance(mouseX.value);
36-
37-
if (Math.abs(distanceCalc) < distance) {
38-
return (1 - Math.abs(distanceCalc) / distance) * magnification + 40;
52+
const distanceCalc = isVertical.value
53+
? calculateDistance(mouseY.value)
54+
: calculateDistance(mouseX.value);
55+
if (!distance?.value || !magnification?.value) return 40;
56+
if (Math.abs(distanceCalc) < distance?.value) {
57+
return (1 - Math.abs(distanceCalc) / distance?.value) * magnification?.value + 40;
3958
}
4059
4160
return 40;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<template>
2+
<div
3+
:class="
4+
cn('relative block bg-secondary', orientation === 'vertical' ? 'w-4/5 h-0.5' : 'h-4/5 w-0.5')
5+
"
6+
></div>
7+
</template>
8+
9+
<script setup lang="ts">
10+
import { ORIENTATION_INJECTION_KEY } from "./injectionKeys";
11+
import { cn } from "~/lib/utils";
12+
13+
const orientation = inject(ORIENTATION_INJECTION_KEY, "vertical");
14+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Ref, InjectionKey, ComputedRef } from "vue";
2+
import type { DataOrientation } from "./types";
3+
4+
export const MOUSE_X_INJECTION_KEY = Symbol() as InjectionKey<Ref<number>>;
5+
export const MOUSE_Y_INJECTION_KEY = Symbol() as InjectionKey<Ref<number>>;
6+
7+
export const MAGNIFICATION_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<number>>;
8+
9+
export const DISTANCE_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<number>>;
10+
11+
export const ORIENTATION_INJECTION_KEY = Symbol() as InjectionKey<DataOrientation>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type DataOrientation = "vertical" | "horizontal";
2+
export type Direction = "top" | "middle" | "bottom";

content/2.components/miscellaneous/Dock.md

+30-12
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,30 @@ description: A macOS-style dock with magnifying icons as you hover over them.
66
::ComponentLoader{label="Preview" componentName="DockDemo" type="examples"}
77
::
88

9+
## Examples
10+
11+
Vertical Dock
12+
13+
::ComponentLoader{label="Preview" componentName="DockDemo2" type="examples"}
14+
::
15+
916
## API
1017

1118
::steps
1219

1320
### `Dock`
1421

15-
| Prop Name | Type | Description |
16-
| --------------- | -------- | ----------------------------------------------------------------- |
17-
| `class` | `string` | Additional classes to apply to the dock container. |
18-
| `magnification` | `number` | Magnification factor for the dock icons on hover (default: 60). |
19-
| `distance` | `number` | Distance from the icon center where magnification applies. |
20-
| `direction` | `string` | Alignment of icons (`top`, `middle`, `bottom`) (default: middle). |
22+
| Prop Name | Type | Description |
23+
| --------------- | -------- | ---------------------------------------------------------------------- |
24+
| `class` | `string` | Additional classes to apply to the dock container. |
25+
| `magnification` | `number` | Magnification factor for the dock icons on hover (default: 60). |
26+
| `distance` | `number` | Distance from the icon center where magnification applies. |
27+
| `direction` | `string` | Alignment of icons (`top`, `middle`, `bottom`) (default: middle). |
28+
| `orientation` | `string` | Orientation of Dock (`'vertical`, `horizontal`) (default: horizontal). |
2129

22-
| Slot Name | Description |
23-
| --------- | ----------------------------------------------------- |
24-
| `default` | Dock icons or other child components to be displayed. |
30+
| Slot Name | Description |
31+
| --------- | ---------------------------------------------------- |
32+
| `default` | Dock Dock or other child components to be displayed. |
2533

2634
### `DockIcon`
2735

@@ -36,10 +44,20 @@ description: A macOS-style dock with magnifying icons as you hover over them.
3644
You can copy and paste the following code to create these components:
3745

3846
::CodeGroup
39-
::CodeViewerTab{filename="Dock.vue" language="vue" componentName="Dock" type="ui" id="dock"}
40-
::
47+
::CodeViewerTab{filename="Dock.vue" language="vue" componentName="Dock" type="ui" id="dock"}
48+
::
49+
4150
::CodeViewerTab{filename="DockIcon.vue" language="vue" componentName="DockIcon" type="ui" id="dock"}
42-
::
51+
::
52+
53+
::CodeViewerTab{filename="DockSeparator.vue" language="vue" componentName="DockSeparator" type="ui" id="dock"}
54+
::
55+
56+
::CodeViewerTab{filename="types.ts" language="ts" componentName="types" extension="ts" type="ui" id="dock"}
57+
::
58+
59+
::CodeViewerTab{filename="injectionKeys.ts" language="ts" componentName="injectionKeys" extension="ts" type="ui" id="dock"}
60+
::
4361
::
4462

4563
## Credits

0 commit comments

Comments
 (0)