@wordup-md/nuxt-layer-shadcn-unocss 0.2.13 → 0.2.14

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.
@@ -67,7 +67,7 @@ You can put iconify icons, emojis and urls in `icon`. Smart icon will render the
67
67
  <div class="space-x-2">
68
68
 
69
69
  :icon{name="/logo.svg"}
70
- :icon{name="https://vueuse.org/favicon.svg" size=40}
70
+ :icon{name="https://img.icons8.com/color/48/vue-js.png" size=40}
71
71
 
72
72
  </div>
73
73
  ::
@@ -85,7 +85,7 @@ You can put iconify icons, emojis and urls in `icon`. Smart icon will render the
85
85
 
86
86
  **URL**
87
87
  :icon{name="/logo.svg"}
88
- :icon{name="https://vueuse.org/favicon.svg" size=40}
88
+ :icon{name="https://img.icons8.com/color/48/vue-js.png" size=40} # Keep care of CORS origin restrictions.
89
89
  ```
90
90
  ::
91
91
 
@@ -0,0 +1,6 @@
1
+ # [](/getting-started)
2
+
3
+ # [](/content-elements)
4
+
5
+ # [](/components/)
6
+
@@ -2,6 +2,7 @@ import { mergeConfigs } from '@unocss/core'
2
2
  import config from './.nuxt/uno.config.mjs'
3
3
 
4
4
  export default mergeConfigs([config, {
5
+ safelist: ['rotate-x-180', 'translate-y-1'],
5
6
  // your overrides
6
7
  // presets: [
7
8
  // presetShadcn({
@@ -9,8 +9,8 @@
9
9
  }
10
10
 
11
11
  /* & > p:not(:has(span)):not(:first-child) { */
12
- & > p:not(:first-child) {
13
- margin-top: 1.5rem;
12
+ & p:not(:first-child) {
13
+ --at-apply: "leading-7 [&:not(:first-child)]:mt-6";
14
14
  }
15
15
 
16
16
  & h2.prose-h2:first-child {
@@ -34,16 +34,22 @@
34
34
  }
35
35
 
36
36
  & code {
37
- font-size: 0.875rem;
38
- line-height: 1.25rem;
39
- position: relative;
40
- border-radius: 0.25rem;
41
- padding: 0.2rem 0.3rem;
37
+ --at-apply: "px-1.5 text-sm font-mono font-medium rounded-md inline-block border bg-muted/50";
38
+ }
39
+
40
+ & pre {
41
+ --at-apply: "p-3 rounded-lg border bg-muted/30! text-card-foreground shadow-sm overflow-x-auto";
42
42
 
43
- font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
43
+ & code {
44
+ --at-apply: "bg-transparent p-0 border-none";
45
+ }
46
+ }
44
47
 
45
- --un-bg-opacity: 1;
46
- background-color: hsl(var(--muted) / var(--un-bg-opacity)) /* hsl(var(--muted)) */;
48
+ [in-stack="true"] pre {
49
+ --at-apply: "rounded-none border-b-none border-l-none border-r-none shadow-none";
50
+ }
51
+ .FRONTMATTER pre {
52
+ --at-apply: "rounded-none border-none shadow-none bg-transparent";
47
53
  }
48
54
 
49
55
  & > div {
@@ -28,6 +28,7 @@
28
28
  v-else
29
29
  :src="name"
30
30
  :style="`width: ${size}px; height: ${size}px;`"
31
+ class="inline-block"
31
32
  :class="$attrs.class"
32
33
  />
33
34
  </template>
@@ -1,27 +1,61 @@
1
1
  <script lang="ts" setup>
2
2
  import type { NavItem } from '@nuxt/content'
3
3
 
4
- const { items, title } = defineProps<{
4
+ const { items, title, removeCurrentPage } = defineProps<{
5
5
  title?: string
6
- items: NavItem[]
6
+ items: NavItem[] | string[]
7
+ removeCurrentPage?: boolean
7
8
  }>()
8
9
 
9
10
  const { navigation } = useContent()
11
+ const { navDirFromPath, navPageFromPath } = useContentHelpers()
12
+ const route = useRoute()
10
13
 
11
- const _links = computed(() => {
12
- return items?.map((link) => {
13
- if (typeof link === 'string') {
14
- return {
15
- ...navigation.value?.find(n => n._path === link),
16
- _path: link,
14
+ const links = computed(() => {
15
+ const _links = []
16
+ for (const item of items) {
17
+ if (typeof item === 'string') {
18
+ if (item.endsWith('/')) {
19
+ const path = item.replace(/\/$/, '')
20
+ const dir = navDirFromPath(path, navigation.value || [])
21
+ const _dir = removeCurrentPage ? dir?.filter(d => d._path !== route.path) : dir
22
+ if (_dir) {
23
+ _links.push(..._dir)
24
+ }
17
25
  }
26
+ else {
27
+ _links.push(navPageFromPath(item as unknown as string, navigation.value || []))
28
+ }
29
+ // _links.push({
30
+ // ...navigation.value?.find(n => item === n._path),
31
+ // _path: item,
32
+ // })
18
33
  }
19
-
20
- return {
21
- ...link,
22
- _path: link.to || link._path,
34
+ else {
35
+ _links.push({
36
+ ...item,
37
+ _path: item.to || item._path,
38
+ })
23
39
  }
24
- })
40
+ }
41
+ return _links
42
+ // return items?.map((item) => {
43
+ // console.log(
44
+ // item, navigation.value, navigation.value?.find(n => item === n._path),
45
+ // navDirFromPath(item as unknown as string, navigation.value || []),
46
+ // )
47
+ // if (typeof item === 'string') {
48
+ // return {
49
+ // ...navigation.value?.find(n => item === n._path),
50
+ // _path: item,
51
+ // }
52
+ // }
53
+
54
+ // return {
55
+ // ...item,
56
+ // _path: item.to || item._path,
57
+ // }
58
+ // })
25
59
  })
26
60
  </script>
27
61
 
@@ -35,7 +69,7 @@ const _links = computed(() => {
35
69
  </p>
36
70
 
37
71
  <MenuItem
38
- v-for="link in _links"
72
+ v-for="link in links"
39
73
  :key="link._path"
40
74
  :link="link"
41
75
  />
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <code class="inline-code relative rounded bg-muted px-[0.3rem] pt-[0.2rem] font-mono">
2
+ <code class="inline-code px-1.5 text-sm font-mono font-medium rounded-md inline-block border bg-muted/50">
3
3
  <slot />
4
4
  </code>
5
5
  </template>
@@ -14,9 +14,9 @@
14
14
  line-height: 1.25rem;
15
15
  }
16
16
 
17
- p .inline-code {
17
+ /* p .inline-code {
18
18
  vertical-align: -0.05rem;
19
- }
19
+ } */
20
20
 
21
21
  strong .inline-code {
22
22
  font-weight: inherit;
@@ -1,15 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  import { cn } from '@/lib/utils'
3
+ import Slugger from 'github-slugger'
4
+
5
+ const slugger = new Slugger()
3
6
 
4
7
  // Don't inherit attrs, we want to use it with cn
5
8
  defineOptions({
6
9
  inheritAttrs: false,
7
10
  })
8
11
 
9
- defineProps<{
12
+ const props = defineProps<{
10
13
  title?: string
11
14
  }>()
12
15
 
16
+ const id = computed(() => slugger.slug(props.title ?? ''))
17
+
13
18
  defineSlots()
14
19
  </script>
15
20
 
@@ -20,6 +25,7 @@ defineSlots()
20
25
  'relative p-6 mt-6',
21
26
  classToString($attrs.class),
22
27
  )"
28
+ :aria-labelledby="id"
23
29
  >
24
30
  <div>
25
31
  <div
@@ -32,7 +38,10 @@ defineSlots()
32
38
  />
33
39
  </div>
34
40
 
35
- <ProseH2 v-if="title">
41
+ <ProseH2
42
+ v-if="title"
43
+ :id
44
+ >
36
45
  {{ title }}
37
46
  </ProseH2>
38
47
 
@@ -4,7 +4,9 @@
4
4
  :target
5
5
  :icon
6
6
  >
7
- {{ $t(prefix) }} <span class="font-semibold">{{ computedTitle }}</span>
7
+ <slot>
8
+ {{ $t(prefix) }} <span class="font-semibold">{{ computedTitle }}</span>
9
+ </slot>
8
10
  </Alert>
9
11
  </template>
10
12
 
@@ -13,11 +15,11 @@ const {
13
15
  prefix = 'Read more at',
14
16
  title,
15
17
  to,
16
- icon = 'lucide:bookmark',
18
+ icon = 'lucide:info',
17
19
  } = defineProps<{
18
20
  prefix?: string
19
21
  title?: string
20
- to: string
22
+ to?: string
21
23
  target?: Target
22
24
  icon?: string
23
25
  }>()
@@ -27,11 +29,14 @@ const computedTitle = computed<string>(
27
29
  if (title)
28
30
  return title
29
31
 
32
+ if (!to)
33
+ return ''
34
+
30
35
  try {
31
36
  if (to?.startsWith('http'))
32
37
  return to.replace('http://', '').replace('https://', '').replace(/\/$/, '')
33
38
 
34
- return useBreadcrumb(to).filter(x => x.title !== '').map(x => x.title).join(' > ')
39
+ return useBreadcrumb(to).filter(x => x.title !== '').map(x => x.title.replace(/#.*$/, '')).join(' > ')
35
40
  }
36
41
  catch {
37
42
  return to
@@ -4,14 +4,17 @@
4
4
  v-model="mdcAttrs.type"
5
5
  chevron
6
6
  tooltip="Type"
7
- placement="bottom-end"
7
+ placement="bottom-start"
8
8
  :items="types"
9
- class="absolute right-1 top-1 z-10 opacity-25 transition-opacity group-hover:opacity-100"
9
+ class="absolute left-3 top--2 z-10 opacity-0 border rounded pl-2 pr-1 transition-opacity group-hover:opacity-100 bg-background"
10
10
  >
11
11
  <span class="text-xs font-mono capitalize">{{ model.type }}</span>
12
12
  </UiEditorDropdown>
13
13
 
14
- <Alert v-bind="model">
14
+ <Alert
15
+ v-bind="model"
16
+ no-click
17
+ >
15
18
  <p
16
19
  :ref="props.contentRef"
17
20
  class="[&>div]:min-h-5.5"
@@ -26,7 +29,7 @@ import type { VueNodeViewProps } from 'prosekit/vue'
26
29
  const props = defineProps<VueNodeViewProps>()
27
30
  const { getComponentProps } = useMdcEditor()
28
31
 
29
- const types = ['default', 'info', 'success', 'warning', 'danger', 'primary']
32
+ const types = ['default', 'info', 'success', 'warning', 'danger', 'example', 'secondary']
30
33
 
31
34
  const { model, mdcAttrs } = getComponentProps(props, {
32
35
  id: {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <FieldGroup class="[&>div>div]:divide-y">
2
+ <FieldGroup class="[&>div>div>span]:block [&>div>div]:divide-y [&>div>div>span:not(:first-child)]:mt-4 [&>div>div>span:not(:first-child)]:pt-4">
3
3
  <slot />
4
4
  </FieldGroup>
5
5
  </template>
@@ -3,28 +3,32 @@
3
3
  class="group relative"
4
4
  contenteditable="false"
5
5
  >
6
- <Alert
6
+ <ReadMore
7
7
  v-bind="{ ...model, title: undefined, noClick: true }"
8
8
  class="[&_span.w-full]:flex [&_span.w-full]:items-center"
9
9
  >
10
- <!-- <input
11
- v-model="model.prefix"
12
- class="inline-block w-min outline-none focus-within:bg-muted"
13
- > -->
14
- <div
10
+ <input
11
+ ref="prefixInput"
12
+ v-model="mdcAttrs.prefix"
13
+ name="prefix"
14
+ class="inline-block outline-none focus-within:bg-muted py-1 my--1"
15
+ :style="{ width: `${inputWidth}px` }"
16
+ @input="updateInputWidth"
17
+ >
18
+ <!-- <div
15
19
  contenteditable="true"
16
20
  class="inline-block w-min outline-none hover:bg-muted/50 focus-within:bg-muted whitespace-nowrap mr-2"
17
21
  @input="updatePrefix"
18
22
  v-text="model.prefix"
19
- />
23
+ /> -->
20
24
 
21
25
  <UiEditorLinkInput
22
- class="inline-flex flex-1 my--2 [&>div]:w-full"
26
+ class="inline-flex flex-1 my--2 [&>div]:w-full ml--1"
23
27
  dropdown-classes="DROPDOWN_MENU absolute mt-8 !w-[500px]"
24
28
  :model-value="model.to"
25
29
  @update:model-value="updateLink"
26
30
  />
27
- </Alert>
31
+ </ReadMore>
28
32
  </div>
29
33
  </template>
30
34
 
@@ -43,7 +47,7 @@ const { mdcAttrs, model } = getComponentProps(props, {
43
47
  },
44
48
  icon: {
45
49
  type: String,
46
- default: 'lucide:bookmark',
50
+ default: 'lucide:info',
47
51
  },
48
52
  prefix: {
49
53
  type: String,
@@ -60,11 +64,38 @@ const { mdcAttrs, model } = getComponentProps(props, {
60
64
  },
61
65
  })
62
66
 
63
- function updatePrefix(event: Event) {
64
- const target = event.target as HTMLDivElement
65
- mdcAttrs.prefix = target.innerHTML
67
+ const prefixInput = useTemplateRef<HTMLInputElement>('prefixInput')
68
+ const inputWidth = ref(100)
69
+
70
+ function updateInputWidth() {
71
+ if (!prefixInput.value) return
72
+
73
+ // Create a temporary span to measure text width
74
+ const measure = document.createElement('span')
75
+ measure.style.visibility = 'hidden'
76
+ measure.style.position = 'absolute'
77
+ measure.style.whiteSpace = 'pre'
78
+ measure.style.font = window.getComputedStyle(prefixInput.value).font
79
+ measure.textContent = prefixInput.value.value || prefixInput.value.placeholder || ''
80
+
81
+ document.body.appendChild(measure)
82
+ inputWidth.value = Math.max(measure.offsetWidth, 20) // Add padding, min 20px
83
+ document.body.removeChild(measure)
66
84
  }
85
+
67
86
  function updateLink(to: string) {
68
87
  mdcAttrs.to = to
69
88
  }
89
+
90
+ watch(() => mdcAttrs.prefix, () => {
91
+ nextTick(() => {
92
+ updateInputWidth()
93
+ })
94
+ })
95
+
96
+ onMounted(() => {
97
+ nextTick(() => {
98
+ updateInputWidth()
99
+ })
100
+ })
70
101
  </script>
@@ -1,18 +1,29 @@
1
1
  <template>
2
- {{ model }}
3
- <UiTabs v-bind="model">
4
- <div :ref="props.contentRef" />
5
- </UiTabs>
2
+ <render />
3
+ <!-- <Tabs v-bind="model">
4
+ <div
5
+ v-for="(slot, index) in props.node.value.children ?? []"
6
+ :key="index"
7
+ :label="slot.attrs?.attributes?.label"
8
+ :icon="slot.attrs?.attributes?.icon"
9
+ >
10
+ {{ slot.attrs }}
11
+ <component
12
+ :is="slot.type"
13
+ :label="slot.attrs.label"
14
+ />
15
+ </div>
16
+ </Tabs> -->
6
17
  </template>
7
18
 
8
19
  <script lang="ts" setup>
9
20
  import type { VueNodeViewProps } from 'prosekit/vue'
21
+ import Tabs from '../Tabs.vue'
10
22
 
11
23
  // import { useMdcEditor } from '#imports'
12
24
 
13
25
  const props = defineProps<VueNodeViewProps>()
14
-
15
- const variants = ['separate', 'card']
26
+ const variants = ['separate', 'card', 'line', 'combobox']
16
27
 
17
28
  const { getComponentProps } = useMdcEditor()
18
29
  const { model } = getComponentProps(props, {
@@ -34,5 +45,44 @@ const { model } = getComponentProps(props, {
34
45
  type: Boolean,
35
46
  default: true,
36
47
  },
48
+ sync: {
49
+ type: String,
50
+ },
51
+ disableSearch: {
52
+ type: Boolean,
53
+ default: false,
54
+ },
55
+ searchPlaceholder: {
56
+ type: String,
57
+ default: 'Search Tab...',
58
+ },
59
+ searchEmpty: {
60
+ type: String,
61
+ default: 'No tab found.',
62
+ },
37
63
  })
64
+
65
+ function render() {
66
+ // const slots = _slots?.default?.() || []
67
+ // console.log('slots', slots)
68
+ // const slotsData = slots.map((slot, index) => ({
69
+ // label: slot.props?.label || slot.props?.filename || '',
70
+ // index,
71
+ // }))
72
+
73
+ // <div
74
+ // v-for="(slot, index) in props.node.value.children ?? []"
75
+ // :key="index"
76
+ // :label="slot.attrs?.attributes?.label"
77
+ // :icon="slot.attrs?.attributes?.icon"
78
+ // >
79
+
80
+ return h(
81
+ Tabs,
82
+ model,
83
+ () => props.node.value.children.map(child => h('div', { label: child.attrs?.attributes?.label, icon: child.attrs?.attributes?.icon })),
84
+
85
+ // () => [h('div', { label: 'Tab 1' }), h('div', { label: 'Tab 2' })],
86
+ )
87
+ }
38
88
  </script>
@@ -4,57 +4,50 @@
4
4
  class="app__aside relative overflow-hidden text-sm mt-6 pr-6 md:pr-4 h-[calc(100vh-var(--header-height)-var(--banner-height)-7rem)]"
5
5
  type="hover"
6
6
  >
7
- <LayoutHeaderNavMobile
8
- v-if="isMobile"
9
- class="mb-5 border-b pb-2"
10
- />
11
- <LayoutSearchButton v-if="config.search.inAside" />
12
- <ul
13
- v-if="config.aside.useLevel"
14
- class="flex flex-col gap-1 border-b pb-4"
15
- >
16
- <li
17
- v-for="link in navigation || nav"
18
- :key="link.id"
7
+ <template v-if="!tree?.children.length">
8
+ <LayoutHeaderNavMobile
9
+ v-if="isMobile"
10
+ class="mb-5 border-b pb-2"
11
+ />
12
+
13
+ <LayoutSearchButton v-if="config.search.inAside" />
14
+
15
+ <ul
16
+ v-if="config.aside.useLevel"
17
+ class="flex flex-col gap-1 border-b pb-4"
19
18
  >
20
- <NuxtLink
21
- :to="link.redirect ?? link._path"
22
- class="flex h-8 items-center gap-2 rounded-md p-2 text-sm text-foreground/80 hover:bg-muted hover:text-primary"
23
- :class="[path.startsWith(link._path) && 'bg-muted !text-primary']"
19
+ <li
20
+ v-for="link in navigation || nav"
21
+ :key="link.id"
24
22
  >
25
- <Icon
26
- v-if="link.icon"
27
- :name="link.icon"
28
- class="self-center"
29
- :size="16"
30
- />
31
- {{ link.title }}
23
+ <LayoutAsideMenuItem v-bind="{ ...link, children: [] }" />
24
+ </li>
25
+ </ul>
32
26
 
33
- <span
34
- v-for="(badge, i) in link.navBadges"
35
- :key="i"
36
- >
37
- <Badge
38
- :variant="badge.variant"
39
- :type="badge.type"
40
- :size="badge.size ?? 'sm'"
41
- >
42
- {{ badge.value }}
43
- </Badge>
44
- </span>
45
- </NuxtLink>
46
- </li>
47
- </ul>
48
- <LayoutAsideTree
49
- :links
50
- :level="0"
51
- :class="[config.aside.useLevel ? 'pt-4' : 'pt-1']"
27
+ <LayoutAsideTree
28
+ :links
29
+ :level="0"
30
+ :class="[config.aside.useLevel ? 'pt-4' : 'pt-1']"
31
+ />
32
+ </template>
33
+
34
+ <MDCRenderer
35
+ v-else
36
+ :body="tree"
37
+ class="min-h-10"
38
+ :class="{ 'flex flex-col gap-1': !!tree }"
39
+ :components="{
40
+ 'aside-menu-item': AsideMenuItem,
41
+ 'aside-tree-2': AsideTree2,
42
+ }"
52
43
  />
53
44
  </UiScrollArea>
54
45
  </template>
55
46
 
56
47
  <script setup lang="ts">
57
48
  import type { NavItem } from '@nuxt/content'
49
+ import AsideMenuItem from './AsideMenuItem.vue'
50
+ import AsideTree2 from './AsideTree2.vue'
58
51
 
59
52
  const { isMobile, navigation, level = 0 } = defineProps<{
60
53
  isMobile: boolean
@@ -79,5 +72,20 @@ const links = computed(() => {
79
72
  return navigation || nav.value
80
73
  })
81
74
 
82
- const path = computed(() => useRoute().path)
75
+ // const path = computed(() => useRoute().path)
76
+
77
+ const menu = '/_menu-aside'
78
+ const { data: count } = await useAsyncData(menu + '-count', () => queryContent(menu).count())
79
+
80
+ let tree = undefined
81
+ if (count.value) {
82
+ const { data } = await useAsyncData(menu, () =>
83
+ queryContent(menu).findOne(),
84
+ )
85
+ tree = await useMdcMenu(data, {
86
+ menuComponentName: 'aside-menu-item',
87
+ submenuComponentName: 'aside-tree-2',
88
+ })
89
+ console.log('aime', tree)
90
+ }
83
91
  </script>
@@ -0,0 +1,103 @@
1
+ <script lang="ts" setup>
2
+ import type { BadgeVariants } from '@/components/ui/badge'
3
+ import type { NavItem } from '@nuxt/content'
4
+
5
+ defineProps<{
6
+ to?: string
7
+ _path?: string
8
+ target?: string
9
+ icon?: string
10
+ title?: string
11
+ navBadges?: {
12
+ variant: BadgeVariants['variant']
13
+ type: BadgeVariants['type']
14
+ size: BadgeVariants['size']
15
+ value: string
16
+ }[]
17
+ redirect?: string
18
+ children?: NavItem[]
19
+
20
+ }>()
21
+
22
+ const { path } = useRoute()
23
+ const { folderStyle: defaultFolderStyle } = useConfig().value.aside
24
+ // const folderStyle = computed(() => link.sidebar?.style ?? defaultFolderStyle)
25
+ const folderStyle = computed(() => defaultFolderStyle)
26
+ const isOpen = ref(false)
27
+ </script>
28
+
29
+ <template>
30
+ <div v-if="$slots.content">
31
+ <template v-if="folderStyle === 'group'">
32
+ <div
33
+ class="mt-4 flex items-center gap-2 rounded-md px-2 text-base font-semibold outline-none"
34
+ :class="['h-8']"
35
+ >
36
+ <LayoutAsideTreeItemButton :link="{ title: title ?? '', _path: _path ?? '', icon, navBadges }" />
37
+ </div>
38
+
39
+ <div class="flex flex-col gap-1 py-1">
40
+ <slot name="content" />
41
+ </div>
42
+ </template>
43
+
44
+ <template v-else>
45
+ <button
46
+ class="flex w-full cursor-pointer items-center gap-2 rounded-md p-2 text-left text-sm font-medium text-foreground/80 hover:bg-muted hover:text-primary"
47
+ :class="['h-8']"
48
+ @click="isOpen = !isOpen"
49
+ >
50
+ <NuxtIcon
51
+ v-if="folderStyle === 'tree'"
52
+ name="lucide:chevron-down"
53
+ class="transition-transform"
54
+ :class="[!isOpen && '-rotate-90']"
55
+ />
56
+ <LayoutAsideTreeItemButton :link="{ title: title ?? '', _path: _path ?? '', icon, navBadges }" />
57
+ <NuxtIcon
58
+ v-if="folderStyle === 'default'"
59
+ name="lucide:chevron-down"
60
+ class="ml-auto transition-transform"
61
+ :class="[!isOpen && '-rotate-90']"
62
+ />
63
+ </button>
64
+ <div v-show="isOpen">
65
+ <ul
66
+ class="flex flex-col gap-1 py-1"
67
+ :class="['mx-3.5 border-l px-2.5']"
68
+ >
69
+ <slot name="content" />
70
+ </ul>
71
+ </div>
72
+ </template>
73
+ </div>
74
+
75
+ <NuxtLink
76
+ v-else
77
+ :to="redirect ?? _path ?? to"
78
+ class="flex h-8 items-center gap-2 rounded-md p-2 text-sm text-foreground/80 hover:bg-muted hover:text-primary"
79
+ :class="[(_path && path.startsWith(_path) || to && path.startsWith(to)) && 'bg-muted !text-primary']"
80
+ >
81
+ <Icon
82
+ v-if="icon"
83
+ :name="icon"
84
+ class="self-center"
85
+ :size="16"
86
+ />
87
+
88
+ {{ title }}
89
+
90
+ <span
91
+ v-for="(badge, i) in navBadges"
92
+ :key="i"
93
+ >
94
+ <Badge
95
+ :variant="badge.variant"
96
+ :type="badge.type"
97
+ :size="badge.size ?? 'sm'"
98
+ >
99
+ {{ badge.value }}
100
+ </Badge>
101
+ </span>
102
+ </NuxtLink>
103
+ </template>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <NuxtLink
3
+ :to
4
+ class="flex items-center gap-2 rounded-md p-2 text-sm text-foreground/80 hover:bg-muted hover:text-primary"
5
+ >
6
+ <Icon
7
+ v-if="icon"
8
+ :name="icon"
9
+ class="self-center"
10
+ :size="16"
11
+ />
12
+ {{ title }}
13
+ </NuxtLink>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import type { NavItem } from '@nuxt/content'
18
+
19
+ defineProps<{
20
+ to?: string
21
+ target?: string
22
+ showLinkIcon?: boolean
23
+ icon?: string
24
+ title?: string
25
+ description?: string
26
+ tag?: string
27
+ }>()
28
+ </script>
@@ -4,7 +4,7 @@
4
4
  <div v-if="link.children">
5
5
  <template v-if="folderStyle === 'group'">
6
6
  <div
7
- class="mt-2 flex items-center gap-2 rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none"
7
+ class="mt-4 flex items-center gap-2 rounded-md px-2 text-base font-semibold outline-none"
8
8
  :class="[link.navTruncate !== false && 'h-8']"
9
9
  >
10
10
  <LayoutAsideTreeItemButton :link />
@@ -2,7 +2,7 @@
2
2
  <NuxtLink
3
3
  v-if="prevNext && prevNext._path"
4
4
  :to="prevNext._path"
5
- class="mb-6 space-y-2 rounded-lg border p-4 transition-all hover:bg-muted/50"
5
+ class="inline-block w-full mb-6 space-y-2 rounded-lg border p-4 transition-all hover:bg-muted/50"
6
6
  >
7
7
  <div class="flex flex-row gap-3">
8
8
  <div
@@ -3,7 +3,7 @@
3
3
  <UiButton
4
4
  v-if="style === 'input'"
5
5
  variant="outline"
6
- class="h-8 w-full self-center rounded-md px-1.5 font-normal bg-white/50 text-muted-foreground hover:text-accent-foreground"
6
+ class="h-8 w-full self-center rounded-md px-1.5 font-normal bg-muted/50 text-muted-foreground hover:text-accent-foreground"
7
7
  :class="[inAside ? 'mb-4' : 'md:w-32 lg:w-40']"
8
8
  @click="isOpen = true"
9
9
  >
@@ -16,15 +16,33 @@
16
16
 
17
17
  <div
18
18
  v-if="badges"
19
- class="flex gap-2 pt-4"
19
+ class="flex items-center gap-2 pt-4"
20
20
  >
21
- <NuxtLink
21
+ <template
22
22
  v-for="(badge, i) in badges"
23
23
  :key="i"
24
- :to="badge.to"
25
- :target="badge.target"
26
24
  >
25
+ <template v-if="badge.to">
26
+ <NuxtLink
27
+ :to="badge.to"
28
+ :target="badge.target"
29
+ >
30
+ <UiBadge
31
+ :variant="badge.variant || 'secondary'"
32
+ :type="badge.type"
33
+ class="gap-1 rounded-md"
34
+ >
35
+ <Icon
36
+ :size="12"
37
+ :name="badge.icon || 'lucide:external-link'"
38
+ />
39
+ {{ badge.value }}
40
+ </UiBadge>
41
+ </NuxtLink>
42
+ </template>
43
+
27
44
  <UiBadge
45
+ v-else
28
46
  :variant="badge.variant || 'secondary'"
29
47
  :type="badge.type"
30
48
  class="gap-1 rounded-md"
@@ -36,7 +54,7 @@
36
54
  />
37
55
  {{ badge.value }}
38
56
  </UiBadge>
39
- </NuxtLink>
57
+ </template>
40
58
  </div>
41
59
 
42
60
  <div
@@ -21,10 +21,10 @@
21
21
  v-if="links"
22
22
  class="pt-5 text-muted-foreground"
23
23
  >
24
- <NuxtLink
24
+ <NuxtLinkLocale
25
25
  v-for="(link, i) in links"
26
26
  :key="i"
27
- :to="link.to"
27
+ :to="localePath(link.to)"
28
28
  :target="link.target"
29
29
  class="flex w-full gap-1 underline-offset-4 hover:underline [&:not(:first-child)]:pt-3"
30
30
  >
@@ -40,7 +40,7 @@
40
40
  class="ml-auto self-center text-muted-foreground"
41
41
  size="13"
42
42
  />
43
- </NuxtLink>
43
+ </NuxtLinkLocale>
44
44
  </div>
45
45
  </div>
46
46
  <!-- <div class="flex-grow" /> -->
@@ -76,6 +76,7 @@
76
76
  defineProps<{ isSmall?: boolean }>()
77
77
 
78
78
  const { toc } = useContent()
79
+ const { localePath } = useI18nDocs()
79
80
  const { title, links: configLinks } = useConfig().value.toc
80
81
  const { border } = useConfig().value.header
81
82
  const isOpen = ref(false)
@@ -3,15 +3,25 @@
3
3
  <li
4
4
  v-for="link in links"
5
5
  :key="link.id"
6
- class="pt-2"
6
+ class="relative pt-2"
7
7
  >
8
- <NuxtLink
9
- :to="`#${link.id}`"
10
- class="text-muted-foreground transition-all hover:text-primary"
11
- :class="[activeHeadings.includes(link.id) && 'text-primary']"
12
- >
13
- {{ link.text }}
14
- </NuxtLink>
8
+ <div class="flex items-center">
9
+ <div
10
+ v-if="level === 0 && progressBar"
11
+ class="absolute md:ml-1.5 left-0 top-0 bottom-0 w-px transition-colors duration-300 ease-in-out"
12
+ :class="isActive(link) ? 'bg-primary' : 'bg-border'"
13
+ />
14
+ <NuxtLink
15
+ :to="`#${link.id}`"
16
+ class="text-muted-foreground transition-all hover:text-primary"
17
+ :class="{
18
+ 'pl-4.5 md:pl-5.5': progressBar,
19
+ 'text-primary': isActive(link),
20
+ }"
21
+ >
22
+ {{ link.text }}
23
+ </NuxtLink>
24
+ </div>
15
25
  <TocTree
16
26
  v-if="link.children"
17
27
  :links="(link.children || []).filter((x: any) => x.id !== 'hide-toc')"
@@ -29,8 +39,16 @@ defineProps<{
29
39
  level: number
30
40
  }>()
31
41
 
42
+ const config = useConfig()
43
+ const progressBar = config.value.toc.progressBar
44
+
32
45
  const { activeHeadings, updateHeadings } = useScrollspy()
33
46
 
47
+ function isActive(link: TocLink) {
48
+ return activeHeadings.value.includes(link.id)
49
+ || link.children?.some(child => activeHeadings.value.includes(child.id))
50
+ }
51
+
34
52
  onMounted(() =>
35
53
  updateHeadings([
36
54
  ...document.querySelectorAll('.page-content h1'),
@@ -152,6 +152,7 @@ const defaultConfig: DefaultConfig = {
152
152
  toc: {
153
153
  enable: true,
154
154
  enableInMobile: false,
155
+ progressBar: true,
155
156
  title: 'On This Page',
156
157
  links: [],
157
158
  carbonAds: {
@@ -177,10 +178,10 @@ export function useConfig() {
177
178
  const appConfig = computed(() => useAppConfig()?.shadcnDocs || {})
178
179
  const { navKeyFromPath } = useContentHelpers()
179
180
  const { navigation, page } = useContent()
180
- const route = useRoute()
181
181
 
182
182
  return computed(
183
183
  () => {
184
+ const route = useRoute()
184
185
  const processedConfig = customDefu(appConfig.value, defaultConfig)
185
186
  const header = processedConfig.header
186
187
  const banner = processedConfig.banner
package/index.d.ts CHANGED
@@ -491,6 +491,12 @@ interface DefaultConfig {
491
491
  * @default false
492
492
  */
493
493
  enableInMobile: boolean
494
+ /**
495
+ * Show progress bar in table of contents
496
+ *
497
+ * @default true
498
+ */
499
+ progressBar: boolean
494
500
  /**
495
501
  * Table of contents title
496
502
  *
@@ -595,19 +601,19 @@ interface DefaultConfig {
595
601
  *
596
602
  * @values 'zinc', 'slate', 'stone', 'gray', 'neutral', 'red', 'rose', 'orange', 'green', 'blue', 'yellow', 'violet'
597
603
  */
598
- type Color =
599
- | 'zinc'
600
- | 'slate'
601
- | 'stone'
602
- | 'gray'
603
- | 'neutral'
604
- | 'red'
605
- | 'rose'
606
- | 'orange'
607
- | 'green'
608
- | 'blue'
609
- | 'yellow'
610
- | 'violet'
604
+ type Color
605
+ = | 'zinc'
606
+ | 'slate'
607
+ | 'stone'
608
+ | 'gray'
609
+ | 'neutral'
610
+ | 'red'
611
+ | 'rose'
612
+ | 'orange'
613
+ | 'green'
614
+ | 'blue'
615
+ | 'yellow'
616
+ | 'violet'
611
617
 
612
618
  /**
613
619
  * HTML anchor target attribute values
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/nuxt.config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { dirname, join } from 'node:path'
1
+ import { dirname, join, resolve } from 'node:path'
2
2
  import { fileURLToPath } from 'node:url'
3
3
 
4
4
  const currentDir = dirname(fileURLToPath(import.meta.url))
@@ -14,11 +14,37 @@ export default defineNuxtConfig({
14
14
  '@nuxt/icon',
15
15
  '@nuxtjs/color-mode',
16
16
  '@nuxtjs/i18n',
17
- '@unpress/nuxt-module',
18
- // '../../../unpress/packages/nuxt-module/src/module',
19
17
  // 'nuxt-og-image',
20
18
  ],
21
19
 
20
+ $development: {
21
+ modules: [
22
+ '../../../unpress/packages/nuxt-module/src/module',
23
+ ],
24
+ alias: {
25
+ '@unpress/mdc-editor': r('../../unpress/packages/mdc-editor/src/module'),
26
+ '@unpress/nuxt-module': r('../../unpress/packages/nuxt-module/src/module'),
27
+ },
28
+ vite: {
29
+ server: {
30
+ fs: {
31
+ allow: [
32
+ // Allow serving files from the project root
33
+ currentDir,
34
+ // Allow serving files from the monorepo root (parent directories)
35
+ resolve(currentDir, '../../..'),
36
+ ],
37
+ },
38
+ },
39
+ },
40
+ },
41
+
42
+ $production: {
43
+ modules: [
44
+ '@unpress/nuxt-module',
45
+ ],
46
+ },
47
+
22
48
  components: {
23
49
  dirs: [
24
50
  {
@@ -45,8 +71,8 @@ export default defineNuxtConfig({
45
71
  documentDriven: true,
46
72
  highlight: {
47
73
  theme: {
48
- default: 'github-light',
49
- dark: 'github-dark',
74
+ default: 'one-dark-pro',
75
+ dark: 'one-dark-pro',
50
76
  },
51
77
  preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'yaml', 'bash', 'ini'],
52
78
  },
@@ -123,3 +149,7 @@ export default defineNuxtConfig({
123
149
  componentDir: join(currentDir, './components/ui'),
124
150
  },
125
151
  })
152
+
153
+ function r(p: string) {
154
+ return resolve(__dirname, p)
155
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordup-md/nuxt-layer-shadcn-unocss",
3
3
  "type": "module",
4
- "version": "0.2.13",
4
+ "version": "0.2.14",
5
5
  "author": "Emmanuel Salomon <emmanuel.salomon@gmail.com>",
6
6
  "main": "./nuxt.config.ts",
7
7
  "repository": {
@@ -16,19 +16,6 @@
16
16
  "wordup-md"
17
17
  ],
18
18
  "license": "MIT",
19
- "scripts": {
20
- "dev": "nuxi dev .playground",
21
- "dev:root": "nuxi dev",
22
- "prepare": "nuxt prepare .playground",
23
- "build": "nuxt build .playground",
24
- "generate": "nuxt generate .playground",
25
- "preview": "nuxt preview .playground",
26
- "lint": "eslint .",
27
- "build:json-doc": "tsx scripts/buildContentDoc-cli.ts",
28
- "bump": "pnpm build:json-doc && bumpp --skip-install --commit --push --tag",
29
- "release": "pnpm bump && npm publish",
30
- "clean": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + && find . -name '.nuxt' -type d -prune -exec rm -rf '{}' + && find . -name '.output' -type d -prune -exec rm -rf '{}' + && find . -name 'dist' -prune -exec rm -rf '{}' +"
31
- },
32
19
  "devDependencies": {
33
20
  "@nuxt/eslint": "latest",
34
21
  "@nuxt/kit": "^3.17.2",
@@ -58,7 +45,7 @@
58
45
  "@nuxtjs/color-mode": "^3.5.2",
59
46
  "@nuxtjs/i18n": "^9.5.4",
60
47
  "@tanstack/vue-table": "^8.21.3",
61
- "@unpress/nuxt-module": "0.1.13-beta.1",
48
+ "@unpress/nuxt-module": "0.1.13-beta.6",
62
49
  "class-variance-authority": "^0.7.1",
63
50
  "clsx": "^2.1.1",
64
51
  "nuxt": "3.17.2",
@@ -67,9 +54,16 @@
67
54
  "tailwind-merge": "^3.2.0"
68
55
  },
69
56
  "packageManager": "pnpm@10.4.1",
70
- "pnpm": {
71
- "onlyBuiltDependencies": [
72
- "sharp"
73
- ]
57
+ "scripts": {
58
+ "dev": "nuxi dev .playground",
59
+ "dev:root": "nuxi dev",
60
+ "build": "nuxt build .playground",
61
+ "generate": "nuxt generate .playground",
62
+ "preview": "nuxt preview .playground",
63
+ "lint": "eslint .",
64
+ "build:json-doc": "tsx scripts/buildContentDoc-cli.ts",
65
+ "bump": "pnpm build:json-doc && bumpp --skip-install --commit --push --tag",
66
+ "release": "pnpm bump && npm publish",
67
+ "clean": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + && find . -name '.nuxt' -type d -prune -exec rm -rf '{}' + && find . -name '.output' -type d -prune -exec rm -rf '{}' + && find . -name 'dist' -prune -exec rm -rf '{}' +"
74
68
  }
75
- }
69
+ }
@@ -84,7 +84,6 @@
84
84
  <!-- <LayoutSubmenuTree
85
85
  :level="1"
86
86
  /> -->
87
-
88
87
  <div
89
88
  v-for="component in sidebarRightComponents"
90
89
  :key="component.name"
package/plugins/wordup.ts CHANGED
@@ -37,9 +37,84 @@ export default defineNuxtPlugin((nuxtApp) => {
37
37
  transform() {
38
38
  return { type: 'containerComponent', attrs: { componentName: 'read-more' } }
39
39
  },
40
- onSelect: () => editor.commands.insertContainerComponent({
41
- name: 'read-more',
42
- }),
40
+ onSelect: () => {
41
+ editor.commands.insertContainerComponent({
42
+ name: 'read-more',
43
+ })
44
+
45
+ // Set cursor on the input of the component after insertion
46
+ // Use setTimeout to wait for the DOM update
47
+ nextTick(() => {
48
+ const { view, state } = editor
49
+ if (!view || !state) return
50
+
51
+ // Get the current selection position
52
+ const { $anchor } = state.selection
53
+ const pos = $anchor.pos
54
+ // Find the inserted containerComponent node
55
+ // Walk backwards to find the node we just inserted
56
+ let node = null
57
+ let nodePos = -1
58
+
59
+ // Check if we're inside a containerComponent
60
+ for (let depth = $anchor.depth; depth > 0; depth--) {
61
+ const nodeAtDepth = $anchor.node(depth)
62
+ if (nodeAtDepth.type.name === 'containerComponent'
63
+ && nodeAtDepth.attrs.componentName === 'read-more') {
64
+ node = nodeAtDepth
65
+ nodePos = $anchor.before(depth)
66
+ break
67
+ }
68
+ }
69
+
70
+ // If not found, search forward from current position
71
+ if (!node) {
72
+ const doc = state.doc
73
+ for (let i = Math.max(0, pos - 50); i < Math.min(doc.content.size, pos + 50); i++) {
74
+ const $pos = doc.resolve(i)
75
+ for (let depth = $pos.depth; depth > 0; depth--) {
76
+ const nodeAtDepth = $pos.node(depth)
77
+ if (nodeAtDepth.type.name === 'containerComponent'
78
+ && nodeAtDepth.attrs.componentName === 'read-more') {
79
+ node = nodeAtDepth
80
+ nodePos = $pos.before(depth)
81
+ break
82
+ }
83
+ }
84
+ if (node) break
85
+ }
86
+ }
87
+
88
+ const prevNode = view.domAtPos(nodePos - 1)
89
+ // get next html element with js
90
+ setTimeout(() => {
91
+ prevNode.node.nextElementSibling?.querySelector('input.INLINE_MENU_LINK_INPUT')?.focus()
92
+ }, 100)
93
+ // const node = editor.state.doc.nodeAt(nodePos)
94
+ // if (node && node.type.name === 'read-more') {
95
+ // const input = node.firstChild?.firstChild?.firstChild as HTMLInputElement
96
+ // if (input) {
97
+ // input.focus()
98
+ // }
99
+ // }
100
+ // if (node && nodePos >= 0) {
101
+ // // Set cursor inside the component (at the start of its content)
102
+ // // Position is after the opening tag, typically nodePos + 1
103
+ // const insidePos = nodePos + 1
104
+
105
+ // // Import TextSelection dynamically and set selection
106
+ // import('prosemirror-state').then(({ TextSelection }) => {
107
+ // const tr = state.tr
108
+ // const selection = TextSelection.create(tr.doc, insidePos)
109
+ // tr.setSelection(selection)
110
+ // view.dispatch(tr)
111
+
112
+ // // Focus the editor
113
+ // view.focus()
114
+ // })
115
+ // }
116
+ })
117
+ },
43
118
  })
44
119
 
45
120
  config.components.push({