@soubiran/ui 0.4.0 → 0.6.0

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.
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ import { tv } from 'tailwind-variants'
3
+ import { computed } from 'vue'
4
+ import useTableOfContents from '../composables/useTableOfContents'
5
+ import useUmami from '../composables/useUmami'
6
+
7
+ const tableOfContents = tv({
8
+ slots: {
9
+ base: 'space-y-1 text-sm text-dimmed font-medium',
10
+ title: '',
11
+ list: 'font-sofia',
12
+ link: 'flex gap-1 px-0 py-1 text-dimmed hover:text-default active:text-default transition-colors focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted data-active:text-default rounded-md',
13
+ },
14
+ })
15
+
16
+ export interface TableOfContentsItem {
17
+ level: number
18
+ anchor: string | null
19
+ text: string
20
+ children?: TableOfContentsItem[]
21
+ }
22
+
23
+ export interface TableOfContentsProps {
24
+ toc: TableOfContentsItem[]
25
+ class?: any
26
+ ui?: Partial<typeof tableOfContents.slots>
27
+ }
28
+ export interface TableOfContentsEmits {}
29
+ export interface TableOfContentsSlots {}
30
+ </script>
31
+
32
+ <script lang="ts" setup>
33
+ const props = defineProps<TableOfContentsProps>()
34
+ defineEmits<TableOfContentsEmits>()
35
+ defineSlots<TableOfContentsSlots>()
36
+
37
+ const { track } = useUmami()
38
+ function trackClick(text: string, hash: string) {
39
+ track('table_of_contents_click', {
40
+ toc_text: text,
41
+ toc_hash: hash,
42
+ })
43
+ }
44
+
45
+ const { activeHeadings } = useTableOfContents()
46
+
47
+ const ui = computed(() => tableOfContents())
48
+ </script>
49
+
50
+ <template>
51
+ <div :class="ui.base({ class: [props.ui?.base, props.class] })">
52
+ <div :class="ui.title({ class: props.ui?.title })">
53
+ Table of Contents
54
+ </div>
55
+ <ul :class="ui.list({ class: props.ui?.list })">
56
+ <li v-for="(item, index) in props.toc" :key="item.anchor || index">
57
+ <!-- Cannot use RouterLink as the anchor will be prefixed with the route path. -->
58
+ <a
59
+ v-if="item.anchor"
60
+ custom
61
+ variant="link"
62
+ color="neutral"
63
+ :href="`#${item.anchor}`"
64
+ :data-active="activeHeadings.includes(item.anchor || '') ? true : undefined"
65
+ :class="ui.link({ class: props.ui?.link })"
66
+ @click="trackClick(item.text, item.anchor)"
67
+ >
68
+ <span>{{ index + 1 }}.</span>
69
+ {{ item.text }}
70
+ </a>
71
+ </li>
72
+ </ul>
73
+ </div>
74
+ </template>
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import PartySocket from 'partysocket'
3
+ import { tv } from 'tailwind-variants'
4
+ import { computed, onMounted, ref } from 'vue'
5
+
6
+ const viewersCounter = tv({
7
+ slots: {
8
+ base: 'flex items-center justify-center gap-[0.375rem] border border-green-400 rounded-full px-[0.375rem] py-[0.125rem] text-xs text-muted font-light leading-3 dark:border-green-700',
9
+ dot: 'inline-block h-[0.375rem] w-[0.375rem] animate-[pulse_4s_cubic-bezier(0.4,_0,_0.6,_1)_infinite] rounded-full bg-green-400 ring ring-2 ring-green-200 dark:bg-green-700 dark:ring-green-500',
10
+ },
11
+ })
12
+
13
+ interface Data {
14
+ count: number
15
+ connections: { [key: string]: number }
16
+ }
17
+
18
+ export interface ViewersCounterProps {
19
+ class?: any
20
+ ui?: Partial<typeof viewersCounter.slots>
21
+ }
22
+ export interface ViewersCounterEmits {}
23
+ export interface ViewersCounterSlots {}
24
+ </script>
25
+
26
+ <script lang="ts" setup>
27
+ const props = defineProps<ViewersCounterProps>()
28
+ defineEmits<ViewersCounterEmits>()
29
+ defineSlots<ViewersCounterSlots>()
30
+
31
+ const count = ref<number>(0)
32
+ const connections = ref<{ [key: string]: number }>({})
33
+
34
+ function connect() {
35
+ return new PartySocket({
36
+ host: import.meta.env.VITE_PARTYKIT_URL,
37
+ room: 'soubiran-dev',
38
+ })
39
+ }
40
+
41
+ onMounted(() => {
42
+ const ws = connect()
43
+
44
+ ws.addEventListener('message', (event) => {
45
+ const data = JSON.parse(event.data) as Data
46
+
47
+ count.value = data.count
48
+ connections.value = data.connections
49
+ })
50
+ })
51
+
52
+ const title = computed(() => {
53
+ return `${count.value} viewer${count.value > 1 ? 's' : ''} currently online (${Object.keys(connections.value)
54
+ .map(host => `${connections.value[host]} from ${host}`)
55
+ .join(', ')})`
56
+ })
57
+
58
+ const ui = computed(() => viewersCounter())
59
+ </script>
60
+
61
+ <template>
62
+ <Transition name="fade">
63
+ <div
64
+ v-if="count"
65
+ :title
66
+ :class="ui.base({ class: [props.class, props.ui?.base] })"
67
+ >
68
+ <span>{{ count }}</span>
69
+ <span
70
+ :class="ui.dot({ class: props.ui?.dot })"
71
+ aria-hidden="true"
72
+ />
73
+ </div>
74
+ </Transition>
75
+ </template>
@@ -0,0 +1,11 @@
1
+ import * as vue from "vue";
2
+
3
+ //#region src/composables/useTableOfContents.d.ts
4
+ declare function _useTableOfContents(): {
5
+ setActive: (id: string) => void;
6
+ unsetActive: (id: string) => void;
7
+ activeHeadings: Readonly<vue.Ref<readonly string[], readonly string[]>>;
8
+ };
9
+ declare const _default: typeof _useTableOfContents;
10
+ //#endregion
11
+ export { _default };
@@ -0,0 +1,20 @@
1
+ import { createSharedComposable } from "@vueuse/core";
2
+ import { readonly, ref } from "vue";
3
+ //#region src/composables/useTableOfContents.ts
4
+ function _useTableOfContents() {
5
+ const activeHeadings = ref([]);
6
+ function setActive(id) {
7
+ activeHeadings.value.push(id);
8
+ }
9
+ function unsetActive(id) {
10
+ activeHeadings.value = activeHeadings.value.filter((h) => h !== id);
11
+ }
12
+ return {
13
+ setActive,
14
+ unsetActive,
15
+ activeHeadings: readonly(activeHeadings)
16
+ };
17
+ }
18
+ var useTableOfContents_default = createSharedComposable(_useTableOfContents);
19
+ //#endregion
20
+ export { useTableOfContents_default as default };
@@ -0,0 +1,6 @@
1
+ //#region src/composables/useUmami.d.ts
2
+ declare function useUmami(): {
3
+ track: (event: string, data?: Record<string, unknown>) => void;
4
+ };
5
+ //#endregion
6
+ export { useUmami };
@@ -0,0 +1,14 @@
1
+ import { useRoute } from "vue-router";
2
+ //#region src/composables/useUmami.ts
3
+ function useUmami() {
4
+ const route = useRoute();
5
+ function track(event, data) {
6
+ window.umami?.track(event, {
7
+ ...data,
8
+ page_path: route.path
9
+ });
10
+ }
11
+ return { track };
12
+ }
13
+ //#endregion
14
+ export { useUmami as default };