Skip to content

Commit 8541189

Browse files
committed
feat: add shimmer component
1 parent a853056 commit 8541189

27 files changed

+739
-77
lines changed

apps/test/app/examples/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { default as MessageMarkdown } from './message-markdown.vue'
88
export { default as Message } from './message.vue'
99
export { default as PromptInput } from './prompt-input.vue'
1010
export { default as Response } from './response.vue'
11+
export { default as Shimmer } from './shimmer.vue'

apps/test/app/examples/shimmer.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
import { Shimmer } from '@repo/elements/shimmer'
3+
</script>
4+
5+
<template>
6+
<div class="flex flex-col items-center justify-center gap-4 p-8">
7+
<Shimmer>This text has a shimmer effect</Shimmer>
8+
<Shimmer as="h1" class="font-bold text-4xl">
9+
Large Heading
10+
</Shimmer>
11+
<Shimmer :duration="3" :spread="3">
12+
Slower shimmer with wider spread
13+
</Shimmer>
14+
</div>
15+
</template>

apps/test/app/pages/index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import MessageMarkdown from '~/examples/message-markdown.vue'
1010
import Message from '~/examples/message.vue'
1111
import PromptInput from '~/examples/prompt-input.vue'
1212
import Response from '~/examples/response.vue'
13+
import Shimmer from '~/examples/shimmer.vue'
1314
1415
const components = [
1516
{ name: 'Message', Component: Message },
@@ -22,6 +23,7 @@ const components = [
2223
{ name: 'MessageMarkdown', Component: MessageMarkdown },
2324
{ name: 'CodeBlock', Component: CodeBlock },
2425
{ name: 'Image', Component: Image },
26+
{ name: 'Shimmer', Component: Shimmer },
2527
]
2628
</script>
2729

apps/www/assets/css/code-theme.css

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/* Base styles for light mode (Tailwind default mode) */
2+
pre code.hljs {
3+
display: block;
4+
overflow-x: auto;
5+
padding: 1em;
6+
}
7+
code.hljs {
8+
padding: 3px 5px;
9+
}
10+
.hljs {
11+
color: #24292e;
12+
background: #ffffff;
13+
}
14+
.hljs-doctag,
15+
.hljs-keyword,
16+
.hljs-meta .hljs-keyword,
17+
.hljs-template-tag,
18+
.hljs-template-variable,
19+
.hljs-type,
20+
.hljs-variable.language_ {
21+
color: #d73a49;
22+
}
23+
.hljs-title,
24+
.hljs-title.class_,
25+
.hljs-title.class_.inherited__,
26+
.hljs-title.function_ {
27+
color: #6f42c1;
28+
}
29+
.hljs-attr,
30+
.hljs-attribute,
31+
.hljs-literal,
32+
.hljs-meta,
33+
.hljs-number,
34+
.hljs-operator,
35+
.hljs-variable,
36+
.hljs-selector-attr,
37+
.hljs-selector-class,
38+
.hljs-selector-id {
39+
color: #005cc5;
40+
}
41+
.hljs-regexp,
42+
.hljs-string,
43+
.hljs-meta .hljs-string {
44+
color: #032f62;
45+
}
46+
.hljs-built_in,
47+
.hljs-symbol {
48+
color: #e36209;
49+
}
50+
.hljs-comment,
51+
.hljs-code,
52+
.hljs-formula {
53+
color: #6a737d;
54+
}
55+
.hljs-name,
56+
.hljs-quote,
57+
.hljs-selector-tag,
58+
.hljs-selector-pseudo {
59+
color: #22863a;
60+
}
61+
.hljs-subst {
62+
color: #24292e;
63+
}
64+
.hljs-section {
65+
color: #005cc5;
66+
font-weight: bold;
67+
}
68+
.hljs-bullet {
69+
color: #735c0f;
70+
}
71+
.hljs-emphasis {
72+
color: #24292e;
73+
font-style: italic;
74+
}
75+
.hljs-strong {
76+
color: #24292e;
77+
font-weight: bold;
78+
}
79+
.hljs-addition {
80+
color: #22863a;
81+
background-color: #f0fff4;
82+
}
83+
.hljs-deletion {
84+
color: #b31d28;
85+
background-color: #ffeef0;
86+
}
87+
.hljs-char.escape_,
88+
.hljs-link,
89+
.hljs-params,
90+
.hljs-property,
91+
.hljs-punctuation,
92+
.hljs-tag {
93+
/* purposely ignored */
94+
}
95+
96+
/* Dark mode styles using Tailwind's `dark:` prefix */
97+
.dark pre code.hljs {
98+
display: block;
99+
overflow-x: auto;
100+
padding: 1em;
101+
}
102+
.dark code.hljs {
103+
padding: 3px 5px;
104+
}
105+
.dark .hljs {
106+
color: #c9d1d9;
107+
background: #0d1117;
108+
}
109+
.dark .hljs-doctag,
110+
.dark .hljs-keyword,
111+
.dark .hljs-meta .hljs-keyword,
112+
.dark .hljs-template-tag,
113+
.dark .hljs-template-variable,
114+
.dark .hljs-type,
115+
.dark .hljs-variable.language_ {
116+
color: #ff7b72;
117+
}
118+
.dark .hljs-title,
119+
.dark .hljs-title.class_,
120+
.dark .hljs-title.class_.inherited__,
121+
.dark .hljs-title.function_ {
122+
color: #d2a8ff;
123+
}
124+
.dark .hljs-attr,
125+
.dark .hljs-attribute,
126+
.dark .hljs-literal,
127+
.dark .hljs-meta,
128+
.dark .hljs-number,
129+
.dark .hljs-operator,
130+
.dark .hljs-variable,
131+
.dark .hljs-selector-attr,
132+
.dark .hljs-selector-class,
133+
.dark .hljs-selector-id {
134+
color: #79c0ff;
135+
}
136+
.dark .hljs-regexp,
137+
.dark .hljs-string,
138+
.dark .hljs-meta .hljs-string {
139+
color: #a5d6ff;
140+
}
141+
.dark .hljs-built_in,
142+
.dark .hljs-symbol {
143+
color: #ffa657;
144+
}
145+
.dark .hljs-comment,
146+
.dark .hljs-code,
147+
.dark .hljs-formula {
148+
color: #8b949e;
149+
}
150+
.dark .hljs-name,
151+
.dark .hljs-quote,
152+
.dark .hljs-selector-tag,
153+
.dark .hljs-selector-pseudo {
154+
color: #7ee787;
155+
}
156+
.dark .hljs-subst {
157+
color: #c9d1d9;
158+
}
159+
.dark .hljs-section {
160+
color: #1f6feb;
161+
font-weight: bold;
162+
}
163+
.dark .hljs-bullet {
164+
color: #f2cc60;
165+
}
166+
.dark .hljs-emphasis {
167+
color: #c9d1d9;
168+
font-style: italic;
169+
}
170+
.dark .hljs-strong {
171+
color: #c9d1d9;
172+
font-weight: bold;
173+
}
174+
.dark .hljs-addition {
175+
color: #aff5b4;
176+
background-color: #033a16;
177+
}
178+
.dark .hljs-deletion {
179+
color: #ffdcd7;
180+
background-color: #67060c;
181+
}
182+
.dark .hljs-char.escape_,
183+
.dark .hljs-link,
184+
.dark .hljs-params,
185+
.dark .hljs-property,
186+
.dark .hljs-punctuation,
187+
.dark .hljs-tag {
188+
/* purposely ignored */
189+
}

apps/www/assets/css/tailwind.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,14 @@
109109
body {
110110
@apply bg-background text-foreground;
111111
}
112+
:root {
113+
--color-background: hsl(var(--background));
114+
--color-muted-foreground: hsl(var(--muted-foreground));
115+
}
116+
.dark {
117+
--color-background: hsl(var(--background));
118+
--color-muted-foreground: hsl(var(--muted-foreground));
119+
}
112120
}
121+
122+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<!-- eslint-disable vue/no-v-html -->
2+
<script setup lang="ts">
3+
import { cn } from '@repo/shadcn-vue/lib/utils'
4+
import hljs from 'highlight.js'
5+
import { computed, onMounted, ref } from 'vue'
6+
import { MagicString } from 'vue/compiler-sfc'
7+
import '~/assets/css/code-theme.css'
8+
9+
interface Props {
10+
componentName?: string
11+
id?: string
12+
type?: string
13+
icon?: string
14+
class?: string
15+
extension?: string
16+
}
17+
18+
const props = withDefaults(defineProps<Props>(), {
19+
icon: 'lucide:square-terminal',
20+
extension: 'vue',
21+
})
22+
const rawString = ref('')
23+
const codeHtml = ref('')
24+
25+
// Create a map of all possible components using import.meta.glob
26+
const rawComponents = import.meta.glob('../../../packages/examples/src/**/*.{vue,ts,js,d.ts}', {
27+
query: '?raw',
28+
import: 'default',
29+
})
30+
31+
// helper to convert componentName to a filename in kebab-case
32+
function toFileName(name?: string, ext?: string) {
33+
if (!name)
34+
throw new Error('componentName is required')
35+
36+
const kebab = name
37+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
38+
.replace(/[\s_]+/g, '-')
39+
.toLowerCase()
40+
41+
return `${kebab}.${ext || 'vue'}`
42+
}
43+
44+
// Compute the component path based on props
45+
const componentPath = computed(
46+
() => `../../../packages/examples/src/${toFileName(props.componentName, props.extension)}`,
47+
)
48+
49+
// Load and process the component code on mount
50+
onMounted(() => {
51+
loadAndProcessComponentCode()
52+
})
53+
54+
// Function to load and process the component code
55+
async function loadAndProcessComponentCode() {
56+
try {
57+
const componentCode = await fetchComponentCode()
58+
rawString.value = updateImportPaths(componentCode)
59+
codeHtml.value = hljs.highlightAuto(rawString.value, ['ts', 'html', 'css', 'js', 'd.ts']).value
60+
}
61+
catch (error: any) {
62+
throw new Error('Error loading component code:', error)
63+
}
64+
}
65+
66+
// Fetch the raw code of the component dynamically
67+
async function fetchComponentCode() {
68+
const path = componentPath.value
69+
const loadRawComponent = rawComponents[path]
70+
if (!loadRawComponent)
71+
throw new Error(`Component not found: ${path}`)
72+
73+
const mod = await loadRawComponent()
74+
// Ensure 'mod' is string before calling trim, or handle if not string.
75+
if (typeof mod !== 'string') {
76+
throw new TypeError('Raw component code is not a string')
77+
}
78+
return mod.trim()
79+
}
80+
81+
// Update import paths in the raw code using MagicString
82+
function updateImportPaths(code: string) {
83+
const magicString = new MagicString(code)
84+
magicString.replaceAll('@repo/elements/', '@/components/ai-elements/')
85+
magicString.replaceAll('~/composables/', '@/composables/')
86+
return magicString.toString()
87+
}
88+
</script>
89+
90+
<template>
91+
<div
92+
:icon="icon"
93+
:class="cn('relative flex max-h-[32rem]', $props.class)"
94+
:code="rawString"
95+
>
96+
<CodeCopy
97+
class="absolute -top-10 right-0"
98+
:code="rawString"
99+
/>
100+
<code class="min-w-full overflow-auto px-2 leading-4">
101+
<pre
102+
class="text-sm"
103+
v-html="codeHtml"
104+
/>
105+
</code>
106+
</div>
107+
</template>

apps/www/components/ComponentLoader.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ withDefaults(defineProps<Props>(), {
2323
>
2424
<ComponentViewer :component-name="componentName" />
2525
</div>
26+
<CodeViewerTab
27+
v-bind="$props"
28+
label="Code"
29+
/>
2630
</CodeGroup>
2731
</ClientOnly>
2832
</div>
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)