astro-pure 1.0.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.
Files changed (50) hide show
  1. package/bun.lockb +0 -0
  2. package/bunfig.toml +2 -0
  3. package/components/advanced/Comment.astro +148 -0
  4. package/components/advanced/GithubCard.astro +148 -0
  5. package/components/advanced/LinkPreview.astro +82 -0
  6. package/components/advanced/MediumZoom.astro +50 -0
  7. package/components/advanced/QRCode.astro +35 -0
  8. package/components/advanced/Quote.astro +44 -0
  9. package/components/advanced/index.ts +11 -0
  10. package/components/user/Aside.astro +74 -0
  11. package/components/user/Button.astro +79 -0
  12. package/components/user/Card.astro +23 -0
  13. package/components/user/CardList.astro +28 -0
  14. package/components/user/CardListChildren.astro +24 -0
  15. package/components/user/Collapse.astro +84 -0
  16. package/components/user/FormattedDate.astro +21 -0
  17. package/components/user/Label.astro +18 -0
  18. package/components/user/Spoiler.astro +11 -0
  19. package/components/user/Steps.astro +84 -0
  20. package/components/user/TabItem.astro +18 -0
  21. package/components/user/Tabs.astro +266 -0
  22. package/components/user/Timeline.astro +38 -0
  23. package/components/user/index.ts +17 -0
  24. package/index.ts +74 -0
  25. package/package.json +38 -0
  26. package/plugins/link-preview.ts +110 -0
  27. package/plugins/rehype-steps.ts +98 -0
  28. package/plugins/rehype-tabs.ts +112 -0
  29. package/plugins/virtual-user-config.ts +83 -0
  30. package/schemas/favicon.ts +42 -0
  31. package/schemas/head.ts +18 -0
  32. package/schemas/logo.ts +28 -0
  33. package/schemas/social.ts +51 -0
  34. package/types/common.d.ts +48 -0
  35. package/types/index.d.ts +6 -0
  36. package/types/integrations-config.ts +43 -0
  37. package/types/theme-config.ts +125 -0
  38. package/types/user-config.ts +24 -0
  39. package/utils/clsx.ts +24 -0
  40. package/utils/collections.ts +48 -0
  41. package/utils/date.ts +17 -0
  42. package/utils/docsContents.ts +36 -0
  43. package/utils/index.ts +23 -0
  44. package/utils/module.d.ts +25 -0
  45. package/utils/server.ts +11 -0
  46. package/utils/tailwind.ts +7 -0
  47. package/utils/theme.ts +40 -0
  48. package/utils/toast.ts +3 -0
  49. package/utils/toc.ts +41 -0
  50. package/virtual.d.ts +2 -0
package/bun.lockb ADDED
Binary file
package/bunfig.toml ADDED
@@ -0,0 +1,2 @@
1
+ [install]
2
+ registry = "https://registry.npmmirror.com/"
@@ -0,0 +1,148 @@
1
+ ---
2
+ import config from 'virtual:config'
3
+
4
+ import { cn } from '../../utils'
5
+
6
+ const { class: className } = Astro.props
7
+ ---
8
+
9
+ {
10
+ config.integ.waline.enable && (
11
+ <comment-component>
12
+ <div id='waline' class={cn('not-prose', className)}>
13
+ Comment seems to stuck. Try to refresh?✨
14
+ </div>
15
+ </comment-component>
16
+ )
17
+ }
18
+
19
+ <script>
20
+ import { init as walineInit } from '@waline/client'
21
+ import type { WalineEmojiPresets } from '@waline/client'
22
+ import config from 'virtual:config'
23
+
24
+ import '@waline/client/style'
25
+
26
+ const walineConfig = config.integ.waline
27
+
28
+ class Comment extends HTMLElement {
29
+ constructor() {
30
+ super()
31
+ }
32
+
33
+ connectedCallback() {
34
+ // Prevent Vue log errors
35
+ ;(globalThis as unknown as { __VUE_OPTIONS_API__: boolean }).__VUE_OPTIONS_API__ = true
36
+ ;(globalThis as unknown as { __VUE_PROD_DEVTOOLS__: boolean }).__VUE_PROD_DEVTOOLS__ = false
37
+ ;(
38
+ globalThis as unknown as { __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: boolean }
39
+ ).__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false
40
+
41
+ const emoji = walineConfig.emoji?.map(
42
+ (preset) => `${config.npmCDN}/@waline/emojis@1.2.0/${preset}` as WalineEmojiPresets
43
+ )
44
+
45
+ walineInit({
46
+ el: '#waline',
47
+ serverURL: walineConfig.server || '',
48
+ emoji,
49
+ reaction: ['/icons/heart-item.svg'],
50
+ ...walineConfig.additionalConfigs
51
+ })
52
+ }
53
+ }
54
+
55
+ if (walineConfig.enable) customElements.define('comment-component', Comment)
56
+ </script>
57
+
58
+ <style>
59
+ /* Basic theme */
60
+ #waline {
61
+ /* Font size */
62
+ --waline-font-size: 16px;
63
+ /* Basic color */
64
+ --waline-white: hsl(var(--background), var(--tw-bg-opacity, 1));
65
+ --waline-light-grey: #999;
66
+ --waline-dark-grey: #666;
67
+ /* Theme color */
68
+ --waline-theme-color: hsl(var(--foreground) / var(--tw-text-opacity, 1));
69
+ --waline-active-color: hsl(var(--primary) / var(--tw-text-opacity, 1));
70
+ /* Layout */
71
+ --waline-color: hsl(var(--muted-foreground) / var(--tw-text-opacity, 1));
72
+ --waline-bg-color: hsl(var(--muted) / var(--tw-text-opacity, 1));
73
+ --waline-bg-color-light: hsl(var(--input) / var(--tw-text-opacity, 1));
74
+ --waline-bg-color-hover: #f0f0f0;
75
+ --waline-border-color: hsl(var(--border) / var(--tw-border-opacity, 1));
76
+ --waline-disable-bg-color: #f8f8f8;
77
+ --waline-disable-color: #bbb;
78
+ --waline-code-bg-color: #282c34;
79
+ /* Special */
80
+ --waline-bq-color: #f0f0f0;
81
+ /* Avatar */
82
+ --waline-avatar-size: 3.25rem;
83
+ --waline-m-avatar-size: calc(var(--waline-avatar-size) * 9 / 13);
84
+ /* Badge */
85
+ --waline-badge-color: hsl(var(--border) / var(--tw-border-opacity, 1));
86
+ --waline-badge-font-size: 0.775em;
87
+ /* Info */
88
+ --waline-info-bg-color: var(--waline-bg-color-light);
89
+ --waline-info-color: var(--waline-color);
90
+ --waline-info-font-size: 0.625em;
91
+ /* Render selection */
92
+ --waline-border: 1px solid var(--waline-border-color);
93
+ --waline-avatar-radius: 50%;
94
+ --waline-box-shadow: none;
95
+ }
96
+
97
+ /* Reaction buttons */
98
+ #waline :global(.wl-reaction-title, .wl-reaction-text) {
99
+ display: none;
100
+ }
101
+ #waline :global(.wl-reaction) {
102
+ overflow: visible;
103
+ margin-bottom: 0.5em;
104
+ }
105
+ #waline :global(.wl-reaction-img) {
106
+ width: auto;
107
+ display: flex;
108
+ height: 35px;
109
+ align-items: center;
110
+ column-gap: 0.4rem;
111
+ }
112
+ #waline :global(.wl-reaction-votes) {
113
+ position: inherit;
114
+ top: inherit;
115
+ min-width: inherit;
116
+ inset-inline-end: inherit;
117
+ display: flex;
118
+ font-weight: normal;
119
+ border: none;
120
+ background: none;
121
+ color: inherit;
122
+ padding: 0.2rem 0.4rem;
123
+ border-radius: 6px;
124
+ }
125
+ #waline :global(.wl-reaction-loading) {
126
+ position: inherit;
127
+ top: inherit;
128
+ min-width: inherit;
129
+ }
130
+ #waline :global(.wl-reaction-item.active .wl-reaction-votes) {
131
+ background: var(--waline-theme-color);
132
+ color: var(--waline-bg-color);
133
+ }
134
+
135
+ #waline :global(.wl-reaction-votes:after) {
136
+ margin-left: 0.25em;
137
+ content: 'Like(s)';
138
+ display: inline-block;
139
+ clear: both;
140
+ border: 0;
141
+ }
142
+ #waline :global(.wl-reaction img) {
143
+ filter: invert(25%);
144
+ }
145
+ :global(.dark) #waline :global(.wl-reaction img) {
146
+ filter: invert(75%);
147
+ }
148
+ </style>
@@ -0,0 +1,148 @@
1
+ ---
2
+ const { repo: repoRaw } = Astro.props
3
+
4
+ // Remove 'https://github.com/' headings from the repo string
5
+ const repo = repoRaw.replace(/^https:\/\/github\.com\//, '')
6
+
7
+ const [owner, repoName] = repo.split('/')
8
+ ---
9
+
10
+ <github-card class='not-prose loading' data-repo={repo}>
11
+ <a
12
+ href={`https://github.com/${repo}`}
13
+ target='_blank'
14
+ class='group block flex flex-col gap-y-2 rounded-xl border border-border px-5 py-4 transition-colors hover:bg-muted hover:text-muted-foreground'
15
+ >
16
+ <div class='flex items-center justify-between'>
17
+ <div class='flex items-center gap-x-2 text-foreground group-hover:text-primary'>
18
+ <div id='gh-avatar' class='gh-text me-2 size-8 bg-cover' style='border-radius:999px'></div>
19
+ <span class='text-lg transition-colors'>{owner}</span>
20
+ <span class='text-muted-foreground'>/</span>
21
+ <span class='text-lg font-bold transition-colors'>{repoName}</span>
22
+ </div>
23
+ <div class='rounded-full bg-primary-foreground p-1'>
24
+ <svg class='size-6'>
25
+ <use href='/icons/social.svg#mingcute-github-line'></use>
26
+ </svg>
27
+ </div>
28
+ </div>
29
+ <p id='gh-description' class='gh-text'>Waiting for api.github.com...</p>
30
+ <div class='flex items-center justify-between'>
31
+ <div class='gh-text flex flex-wrap items-center gap-x-5'>
32
+ <div class='flex items-center gap-x-2'>
33
+ <svg class='size-5'><use href='/icons/github-card.svg#mingcute-star-line'></use></svg>
34
+ <span id='gh-stars' class='leading-tight'>???</span>
35
+ </div>
36
+ <div class='flex items-center gap-x-2'>
37
+ <svg class='size-5'
38
+ ><use href='/icons/github-card.svg#mingcute-git-branch-line'></use></svg
39
+ >
40
+ <span id='gh-forks' class='leading-tight'>???</span>
41
+ </div>
42
+ <div class='flex items-center gap-x-2'>
43
+ <svg class='size-5'><use href='/icons/github-card.svg#mingcute-balance-line'></use></svg>
44
+ <span id='gh-license' class='leading-tight'>???</span>
45
+ </div>
46
+ </div>
47
+ <span id='gh-language' class='gh-text leading-tight'>?????</span>
48
+ </div>
49
+ </a>
50
+ </github-card>
51
+
52
+ <style>
53
+ @keyframes pulsate {
54
+ 0% {
55
+ opacity: 1;
56
+ }
57
+ 50% {
58
+ opacity: 0.4;
59
+ }
60
+ to {
61
+ opacity: 1;
62
+ }
63
+ }
64
+ .loading .gh-text {
65
+ color: transparent;
66
+ border-radius: calc(var(--radius) - 3px);
67
+ background-color: hsl(var(--primary-foreground) / var(--tw-text-opacity));
68
+ animation: pulsate 2s infinite linear;
69
+ user-select: none;
70
+ }
71
+ .loading .gh-text:nth-child(2) {
72
+ animation-delay: 1s;
73
+ }
74
+ </style>
75
+
76
+ <script>
77
+ interface GithubProps {
78
+ stargazers_count: number
79
+ forks: number
80
+ language: string
81
+ owner: { avatar_url: string }
82
+ license?: { spdx_id: string }
83
+ description: string
84
+ }
85
+
86
+ class GithubCard extends HTMLElement {
87
+ async fetchGithub(repo: string): Promise<GithubProps | null> {
88
+ try {
89
+ const res = await fetch(`https://api.github.com/repos/${repo}`, {
90
+ referrerPolicy: 'no-referrer'
91
+ })
92
+ if (!res.ok) {
93
+ throw new Error(`HTTP error! status: ${res.status}`)
94
+ }
95
+ return (await res.json()) as GithubProps
96
+ } catch (e) {
97
+ console.error('Failed to fetch Github data:', e)
98
+ return null
99
+ }
100
+ }
101
+
102
+ numberFormat(value: number): string {
103
+ return Intl.NumberFormat('en-us', {
104
+ notation: 'compact',
105
+ maximumFractionDigits: 1
106
+ }).format(value)
107
+ }
108
+
109
+ async connectedCallback() {
110
+ if (!this.dataset.repo) return
111
+ try {
112
+ const data = await this.fetchGithub(this.dataset.repo)
113
+ if (!data) return
114
+ ;(this.querySelector('#gh-stars') as HTMLElement).textContent = this.numberFormat(
115
+ data.stargazers_count
116
+ )
117
+ ;(this.querySelector('#gh-forks') as HTMLElement).textContent = this.numberFormat(
118
+ data.forks
119
+ )
120
+ ;(this.querySelector('#gh-language') as HTMLElement).textContent = data.language || 'N/A'
121
+ ;(this.querySelector('#gh-description') as HTMLElement).textContent =
122
+ typeof data.description === 'string'
123
+ ? data.description.replace(/:[a-zA-Z0-9_]+:/g, '')
124
+ : 'Description not set'
125
+
126
+ const licenseEl = this.querySelector('#gh-license') as HTMLElement
127
+ if (data.license?.spdx_id) {
128
+ licenseEl.textContent = data.license.spdx_id
129
+ } else {
130
+ licenseEl.classList.add('no-license')
131
+ }
132
+
133
+ const avatarEl = this.querySelector('#gh-avatar') as HTMLElement
134
+ if (avatarEl) {
135
+ avatarEl.style.backgroundImage = `url(${data.owner.avatar_url})`
136
+ avatarEl.style.backgroundColor = 'transparent'
137
+ }
138
+
139
+ this.classList.remove('loading')
140
+ } catch (e) {
141
+ console.error('Error setting Github data:', e)
142
+ ;(this.querySelector('#gh-description') as HTMLElement).textContent = 'Failed to fetch data'
143
+ }
144
+ }
145
+ }
146
+
147
+ customElements.define('github-card', GithubCard)
148
+ </script>
@@ -0,0 +1,82 @@
1
+ ---
2
+ import { Image } from 'astro:assets'
3
+
4
+ import { parseOpenGraph } from '../../plugins/link-preview'
5
+ import { cn } from '../../utils'
6
+
7
+ export interface Props {
8
+ /** URL to fetch Open Graph data. */
9
+ href: string
10
+ /** Hide any image or video even if set in the OpenGraph data. */
11
+ hideMedia?: boolean
12
+ zoomable?: boolean
13
+ }
14
+
15
+ const { href, hideMedia = false, zoomable = true } = Astro.props
16
+
17
+ const meta = await parseOpenGraph(href)
18
+ const domain = meta?.url ? new URL(meta.url).hostname.replace('www.', '') : ''
19
+ ---
20
+
21
+ {
22
+ meta && meta.title ? (
23
+ <div class='not-prose link-preview-container my-2 flex justify-center sm:my-4'>
24
+ <article
25
+ class:list={[
26
+ 'link-preview flex flex-col overflow-hidden rounded-lg border max-sm:max-w-sm sm:flex-row',
27
+ {
28
+ 'link-preview--has-video max-sm:max-w-none sm:flex-col':
29
+ !hideMedia && meta.video && meta.videoType,
30
+ 'link-preview--no-media': hideMedia || !((meta.video && meta.videoType) || meta.image)
31
+ }
32
+ ]}
33
+ >
34
+ {hideMedia ? null : meta.video && meta.videoType ? (
35
+ <video controls preload='metadata' width='1200' height='630'>
36
+ <source src={meta.video} type={meta.videoType} />
37
+ </video>
38
+ ) : meta.image ? (
39
+ <Image
40
+ class={cn('m-0 sm:max-w-60', zoomable && 'zoomable')}
41
+ src={meta.image}
42
+ alt={meta.imageAlt || ''}
43
+ width='1200'
44
+ height='630'
45
+ />
46
+ ) : null}
47
+ <a
48
+ class='group font-normal text-muted-foreground no-underline hover:text-muted-foreground'
49
+ href={href}
50
+ target='_blank'
51
+ >
52
+ <div class='link-preview__content flex h-full flex-col gap-y-1 px-3 py-2 transition-colors group-hover:bg-muted sm:px-5 sm:py-4'>
53
+ <header class='line-clamp-1 font-medium text-foreground transition-colors group-hover:text-primary'>
54
+ {meta.title}
55
+ </header>
56
+ <p class='link-preview__description line-clamp-2'>
57
+ {meta.description}{' '}
58
+ {domain && <small class='link-preview__domain ml-1'>({domain})</small>}
59
+ </p>
60
+ </div>
61
+ </a>
62
+ </article>
63
+ </div>
64
+ ) : (
65
+ <div class='link-preview link-preview--no-metadata'>
66
+ <a {href} target='_blank'>
67
+ {href}
68
+ </a>
69
+ </div>
70
+ )
71
+ }
72
+
73
+ <style>
74
+ /* Default styles */
75
+ .link-preview img,
76
+ .link-preview video {
77
+ aspect-ratio: 1200 / 630;
78
+ width: 100%;
79
+ height: auto;
80
+ object-fit: cover;
81
+ }
82
+ </style>
@@ -0,0 +1,50 @@
1
+ ---
2
+ // https://github.com/francoischalifour/medium-zoom
3
+ import config from 'virtual:config'
4
+
5
+ interface Props {
6
+ selector?: string
7
+ background?: string
8
+ }
9
+
10
+ const { selector = config.integ.mediumZoom.selector, background = 'hsl(var(--background) / 0.8)' } =
11
+ Astro.props
12
+ ---
13
+
14
+ <script is:inline src={`${config.npmCDN}/medium-zoom/dist/pure/medium-zoom.min.umd.js`}></script>
15
+ <script is:inline define:vars={{ selector, background }}>
16
+ mediumZoom(selector, { background })
17
+ </script>
18
+ <style is:global>
19
+ .medium-zoom-overlay {
20
+ position: fixed;
21
+ top: 0;
22
+ right: 0;
23
+ bottom: 0;
24
+ left: 0;
25
+ opacity: 0;
26
+ transition: opacity 0.3s;
27
+ will-change: opacity;
28
+ }
29
+ .medium-zoom--opened .medium-zoom-overlay {
30
+ cursor: pointer;
31
+ cursor: zoom-out;
32
+ opacity: 1;
33
+ z-index: 999;
34
+ }
35
+ .medium-zoom-image {
36
+ cursor: pointer;
37
+ cursor: zoom-in;
38
+ transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1) !important;
39
+ }
40
+ .medium-zoom-image--hidden {
41
+ visibility: hidden;
42
+ }
43
+ .medium-zoom-image--opened {
44
+ position: relative;
45
+ cursor: pointer;
46
+ cursor: zoom-out;
47
+ will-change: transform;
48
+ z-index: 999;
49
+ }
50
+ </style>
@@ -0,0 +1,35 @@
1
+ ---
2
+ import config from 'virtual:config'
3
+
4
+ const { content } = Astro.props
5
+ ---
6
+
7
+ <div
8
+ id='qrcode-container'
9
+ aria-expanded='false'
10
+ class='absolute z-10 -mt-2 box-content max-h-0 overflow-hidden rounded-xl border bg-muted p-4 opacity-0 transition-all duration-300 ease-in-out *:my-0 aria-expanded:max-h-[256px] aria-expanded:translate-y-4 aria-expanded:opacity-100'
11
+ >
12
+ </div>
13
+
14
+ <script is:inline src={`${config.npmCDN}/qrcodejs/qrcode.min.js`}></script>
15
+ <script is:inline define:vars={{ content }}>
16
+ const renderContent = content ?? window.location.href
17
+ // Load qrcode
18
+ function loadqrcode(qrcodeContainer) {
19
+ if (!qrcodeContainer) throw new Error('qrcode container not found')
20
+ if (qrcodeContainer.innerHTML !== '') return
21
+ new QRCode(qrcodeContainer, renderContent)
22
+ }
23
+ const getQRCode = document.getElementById('get-qrcode')
24
+
25
+ const qrcodeContainer = document.getElementById('qrcode-container')
26
+ if (!qrcodeContainer) throw new Error('qrcode container not found')
27
+ getQRCode?.addEventListener('click', () => {
28
+ if (qrcodeContainer.ariaExpanded === 'true') {
29
+ qrcodeContainer.ariaExpanded = 'false'
30
+ } else {
31
+ loadqrcode(qrcodeContainer)
32
+ qrcodeContainer.ariaExpanded = 'true'
33
+ }
34
+ })
35
+ </script>
@@ -0,0 +1,44 @@
1
+ ---
2
+ import { cn } from '../../utils'
3
+
4
+ const { class: className } = Astro.props
5
+ ---
6
+
7
+ <quote-component class={cn('not-prose inline-block', className)}>
8
+ <div
9
+ class='flex flex-row items-center gap-x-3 rounded-full border border-border px-4 py-2 text-sm shadow-sm'
10
+ >
11
+ <span class='relative flex items-center justify-center'>
12
+ <span
13
+ class='absolute size-2 animate-ping rounded-full border border-green-400 bg-green-400 opacity-75'
14
+ ></span>
15
+ <span class='size-2 rounded-full bg-green-400'></span>
16
+ </span>
17
+ <p id='quote-sentence' class='font-medium text-muted-foreground'>Loading...</p>
18
+ </div>
19
+ </quote-component>
20
+
21
+ <script>
22
+ import config from 'virtual:config'
23
+
24
+ const { quote } = config.integ
25
+
26
+ class Quote extends HTMLElement {
27
+ constructor() {
28
+ super()
29
+ }
30
+
31
+ render(sentence: string) {
32
+ const quoteEl = this.querySelector('#quote-sentence') as HTMLElement
33
+ if (!quoteEl) return
34
+ quoteEl.innerText = sentence
35
+ }
36
+
37
+ connectedCallback() {
38
+ fetch(quote.server)
39
+ .then((response) => response.json())
40
+ .then((data) => this.render(quote.target(data)))
41
+ }
42
+ }
43
+ customElements.define('quote-component', Quote)
44
+ </script>
@@ -0,0 +1,11 @@
1
+ // Web content render
2
+ export { default as Quote } from './Quote.astro'
3
+ export { default as GithubCard } from './GithubCard.astro'
4
+ export { default as LinkPreview } from './LinkPreview.astro'
5
+
6
+ // Data transformer
7
+ export { default as QRCode } from './QRCode.astro'
8
+ export { default as MediumZoom } from './MediumZoom.astro'
9
+
10
+ // Individual server integration
11
+ export { default as Comment } from './Comment.astro'
@@ -0,0 +1,74 @@
1
+ ---
2
+ // https://github.com/withastro/starlight/blob/main/packages/starlight/user-components/Aside.astro
3
+ import { AstroError } from 'astro/errors'
4
+
5
+ import { cn } from '../../utils'
6
+
7
+ const asideVariants = ['note', 'tip', 'caution', 'danger'] as const
8
+ const icons = {
9
+ note: 'ui.svg#mingcute-information-line',
10
+ tip: 'aside.svg#mingcute-bulb-line',
11
+ caution: 'aside.svg#mingcute-alert-line',
12
+ danger: 'aside.svg#mingcute-alert-octagon-line'
13
+ } as const
14
+
15
+ interface Props {
16
+ type?: (typeof asideVariants)[number]
17
+ title?: string
18
+ }
19
+
20
+ let { type = 'note', title } = Astro.props
21
+
22
+ if (!asideVariants.includes(type)) {
23
+ throw new AstroError(
24
+ 'Invalid `type` prop passed to the `<Aside>` component.\n',
25
+ `Received: ${JSON.stringify(type)}\n` +
26
+ `Expected one of ${asideVariants.map((i) => JSON.stringify(i)).join(', ')}`
27
+ )
28
+ }
29
+
30
+ if (!title) {
31
+ title = type.toUpperCase()
32
+ }
33
+ ---
34
+
35
+ <aside aria-label={title} class='aside my-3 overflow-hidden rounded-xl border'>
36
+ <div
37
+ class={cn('aside-container border-l-8 border-primary px-4 py-3 bg-primary', `aside-${type}`)}
38
+ >
39
+ <p class='not-prose flex items-center gap-x-2 font-medium text-primary' aria-hidden='true'>
40
+ <svg class='size-6 text-primary'>
41
+ <use href={`/icons/${icons[type]}`}></use>
42
+ </svg>
43
+ {title}
44
+ </p>
45
+ <div class='aside-content mt-2'>
46
+ <slot />
47
+ </div>
48
+ </div>
49
+ </aside>
50
+
51
+ <style>
52
+ .aside > .aside-container {
53
+ --tw-bg-opacity: 0.07;
54
+
55
+ &.aside-tip {
56
+ --primary: 234 60% 60%;
57
+ }
58
+ &.aside-caution {
59
+ --primary: 41 90% 50%;
60
+ }
61
+ &.aside-danger {
62
+ --primary: 339 90% 60%;
63
+ }
64
+
65
+ .aside-content {
66
+ & > :first-child {
67
+ margin-top: 0;
68
+ }
69
+ & > :last-child {
70
+ margin-bottom: 0;
71
+ }
72
+ }
73
+ }
74
+ </style>
@@ -0,0 +1,79 @@
1
+ ---
2
+ import { cn } from '../../utils'
3
+
4
+ const { as: Tag = 'a', class: className, title, href, style = 'button', ...props } = Astro.props
5
+ ---
6
+
7
+ <Tag
8
+ class={cn(
9
+ 'group inline-flex items-center gap-x-1 rounded-lg bg-muted border border-border px-2 py-1 text-sm text-muted-foreground transition-all hover:bg-primary-foreground no-underline',
10
+ className,
11
+ !href && 'cursor-default',
12
+ style === 'pill' && 'rounded-xl'
13
+ )}
14
+ href={href}
15
+ data-astro-prefetch
16
+ {...props}
17
+ >
18
+ <slot name='before'>
19
+ {
20
+ style === 'back' && (
21
+ <svg
22
+ xmlns='http://www.w3.org/2000/svg'
23
+ width='16'
24
+ height='16'
25
+ viewBox='0 0 24 24'
26
+ fill='none'
27
+ stroke-width='2.5'
28
+ stroke-linecap='round'
29
+ stroke-linejoin='round'
30
+ class='stroke-muted-foreground group-hover:stroke-primary'
31
+ >
32
+ <line
33
+ x1='19'
34
+ y1='12'
35
+ x2='5'
36
+ y2='12'
37
+ class='translate-x-3 scale-x-0 transition-all duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100'
38
+ />
39
+ <polyline
40
+ points='12 19 5 12 12 5'
41
+ class='translate-x-1 transition-all duration-300 ease-in-out group-hover:translate-x-0'
42
+ />
43
+ </svg>
44
+ )
45
+ }
46
+ </slot>
47
+ <slot>
48
+ {title && <p class='my-0'>{title}</p>}
49
+ </slot>
50
+ <slot name='after'>
51
+ {
52
+ style === 'ahead' && (
53
+ <svg
54
+ xmlns='http://www.w3.org/2000/svg'
55
+ width='16'
56
+ height='16'
57
+ viewBox='0 0 24 24'
58
+ fill='none'
59
+ stroke-width='2.5'
60
+ stroke-linecap='round'
61
+ stroke-linejoin='round'
62
+ class='stroke-muted-foreground group-hover:stroke-primary'
63
+ >
64
+ <line
65
+ x1='5'
66
+ y1='12'
67
+ x2='19'
68
+ y2='12'
69
+ class='translate-x-4 scale-x-0 transition-all duration-300 ease-in-out group-hover:translate-x-1 group-hover:scale-x-100'
70
+ />
71
+ <polyline
72
+ points='12 5 19 12 12 19'
73
+ class='translate-x-0 transition-all duration-300 ease-in-out group-hover:translate-x-1'
74
+ />
75
+ </svg>
76
+ )
77
+ }
78
+ </slot>
79
+ </Tag>