@yatoday/astro-ui 0.7.2 → 0.8.3

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.
package/astro.d.ts CHANGED
@@ -12,6 +12,7 @@ import type { Card3Props as YtCard3Props } from './components/Card3/types'
12
12
  import type { Card4Props as YtCard4Props } from './components/Card4/types'
13
13
  import type { Card5Props as YtCard5Props } from './components/Card5/types'
14
14
  import type { ConditionalWrapperProps as YtConditionalWrapperProps } from './components/ConditionalWrapper/types'
15
+ import type { CopyToClipboardProps as YtCopyToClipboardProps } from './components/CopyToClipboard/types'
15
16
  import type { DarkModeProps as YtDarkModeProps } from './components/DarkMode/types'
16
17
  import type { HeadlineProps as YtHeadlineProps } from './components/Headline/types'
17
18
  import type { HeroSectionProps as YtHeroSectionProps } from './components/HeroSection/types'
@@ -61,6 +62,7 @@ declare module '@yatoday/astro-ui/astro' {
61
62
  export function Card4(_props: YtCard4Props): any
62
63
  export function Card5(_props: YtCard5Props): any
63
64
  export function ConditionalWrapper(_props: YtConditionalWrapperProps): any
65
+ export function CopyToClipboard(_props: YtCopyToClipboardProps): any
64
66
  export function DarkMode(_props: YtDarkModeProps): any
65
67
  export function Headline(_props: YtHeadlineProps): any
66
68
  export function HeroSection(_props: YtHeroSectionProps): any
@@ -109,6 +111,7 @@ declare module '@yatoday/astro-ui/astro' {
109
111
  export type Card4Props = YtCard4Props
110
112
  export type Card5Props = YtCard5Props
111
113
  export type ConditionalWrapperProps = YtConditionalWrapperProps
114
+ export type CopyToClipboardProps = YtCopyToClipboardProps
112
115
  export type DarkModeProps = YtDarkModeProps
113
116
  export type HeadlineProps = YtHeadlineProps
114
117
  export type HeroSectionProps = YtHeroSectionProps
package/astro.js CHANGED
@@ -12,6 +12,7 @@ import Card3Component from './components/Card3/Card3.astro'
12
12
  import Card4Component from './components/Card4/Card4.astro'
13
13
  import Card5Component from './components/Card5/Card5.astro'
14
14
  import ConditionalWrapperComponent from './components/ConditionalWrapper/ConditionalWrapper.astro'
15
+ import CopyToClipboardComponent from './components/CopyToClipboard/CopyToClipboard.astro'
15
16
  import DarkModeComponent from './components/DarkMode/DarkMode.astro'
16
17
  import HeadlineComponent from './components/Headline/Headline.astro'
17
18
  import HeroSectionComponent from './components/HeroSection/HeroSection.astro'
@@ -60,6 +61,7 @@ export const Card3 = Card3Component
60
61
  export const Card4 = Card4Component
61
62
  export const Card5 = Card5Component
62
63
  export const ConditionalWrapper = ConditionalWrapperComponent
64
+ export const CopyToClipboard = CopyToClipboardComponent
63
65
  export const DarkMode = DarkModeComponent
64
66
  export const Headline = HeadlineComponent
65
67
  export const HeroSection = HeroSectionComponent
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { themeVariants } from '../theme-variants';
3
+
4
+ describe('Button Theme Variants', () => {
5
+ it('has the correct default variant', () => {
6
+ const classes = themeVariants({ variant: 'default' });
7
+ expect(classes).toContain('bg-secondary');
8
+ expect(classes).toContain('text-secondary-foreground');
9
+ });
10
+
11
+ it('has the correct primary variant', () => {
12
+ const classes = themeVariants({ variant: 'primary' });
13
+ expect(classes).toContain('bg-primary');
14
+ expect(classes).toContain('text-primary-foreground');
15
+ });
16
+
17
+ it('has the correct size classes for default size', () => {
18
+ const classes = themeVariants({ size: 'default' });
19
+ expect(classes).toContain('px-6');
20
+ expect(classes).toContain('h-10');
21
+ });
22
+
23
+ it('has the correct size classes for small size', () => {
24
+ const classes = themeVariants({ size: 'sm' });
25
+ expect(classes).toContain('h-8');
26
+ expect(classes).toContain('px-4');
27
+ });
28
+ });
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import type { Card0Props as Props } from './types';
3
3
  import { twMerge } from 'tailwind-merge';
4
+ import { Fragment } from 'astro/components';
4
5
 
5
6
  const { badge, as = 'article', classes = {} } = Astro.props;
6
7
 
@@ -17,7 +18,7 @@ const { container: containerClass = '', badge: badgeClass = 'top-2 left-2' } = c
17
18
  >
18
19
  <div class={twMerge('absolute z-10', badgeClass)}>
19
20
  <slot name="badge">
20
- {badge && <Fragment set:html={badge} />}
21
+ {badge && <span set:html={badge} />}
21
22
  </slot>
22
23
  </div>
23
24
 
@@ -1,7 +1,30 @@
1
1
  <script lang="ts">
2
+ import { twMerge } from 'tailwind-merge';
3
+ import type { Card0Props } from './types';
2
4
 
5
+ export let badge: Card0Props['badge'] = undefined;
6
+ export let as: Card0Props['as'] = 'article';
7
+ export let classes: Card0Props['classes'] = {};
8
+
9
+ const { container: containerClass = '', badge: badgeClass = 'top-2 left-2' } = classes;
3
10
  </script>
4
11
 
5
- <div>
6
- TODO
7
- </div>
12
+ <svelte:element
13
+ this={as}
14
+ class={twMerge(
15
+ 'relative flex flex-col justify-between gap-6 overflow-hidden rounded-lg border border-input bg-card text-card-foreground py-6 group',
16
+ containerClass
17
+ )}
18
+ >
19
+ <div class={twMerge('absolute z-10', badgeClass)}>
20
+ <slot name="badge">
21
+ {#if badge}
22
+ {@html badge}
23
+ {/if}
24
+ </slot>
25
+ </div>
26
+
27
+ <slot name="image" />
28
+
29
+ <slot />
30
+ </svelte:element>
@@ -48,6 +48,8 @@ const {
48
48
  <p class={cn('text-muted-foreground text-sm/5 md:text-base', descriptionClass)} set:html={description} />
49
49
  )
50
50
  }
51
+
52
+ <slot />
51
53
  </div>
52
54
 
53
55
  {
@@ -0,0 +1,76 @@
1
+ ---
2
+ import type { CopyToClipboardProps as Props } from './types';
3
+ import Button from "../Button/Button.astro";
4
+ import { Icon } from 'astro-icon/components';
5
+
6
+ const {
7
+ variant = 'outline',
8
+ size = 'icon',
9
+ text = '',
10
+ class: className = '',
11
+ type = 'button',
12
+ ...rest
13
+ } = Astro.props;
14
+ ---
15
+
16
+ <Button
17
+ data-yt-copy-to-clipboard={text}
18
+ type={type}
19
+ class={className}
20
+ variant={variant}
21
+ size={size}
22
+ {...rest}
23
+ >
24
+ <span class="copy-icon">
25
+ <Icon name="tabler:copy" />
26
+ </span>
27
+ <span class="check-icon" style="display: none;">
28
+ <Icon name="tabler:check" />
29
+ </span>
30
+ </Button>
31
+
32
+ <script>
33
+ import { on, get } from '../../utils';
34
+ import copyToClipboard from "./copy";
35
+
36
+ const init = () => {
37
+ // Only run once
38
+ if (!window.copyToClipboardInitialized) {
39
+ window.copyToClipboardInitialized = true;
40
+
41
+ const btn = get('[data-yt-copy-to-clipboard]', true) as NodeListOf<HTMLElement>;
42
+ if (btn && btn.length === 0) return;
43
+
44
+ btn.forEach(addCopyListeners);
45
+ }
46
+ };
47
+
48
+ const addCopyListeners = (btn: HTMLElement) => {
49
+ btn.addEventListener('click', (e) => {
50
+ e.preventDefault();
51
+
52
+ if(btn.dataset.ytCopyToClipboard) {
53
+ copyToClipboard(btn.dataset.ytCopyToClipboard);
54
+
55
+ // Show check icon
56
+ const copyIcon = btn.querySelector('.copy-icon') as HTMLElement;
57
+ const checkIcon = btn.querySelector('.check-icon') as HTMLElement;
58
+
59
+ if (copyIcon && checkIcon) {
60
+ copyIcon.style.display = 'none';
61
+ checkIcon.style.display = 'block';
62
+
63
+ // Reset back to copy icon after 3 seconds
64
+ setTimeout(() => {
65
+ copyIcon.style.display = 'block';
66
+ checkIcon.style.display = 'none';
67
+ }, 3000);
68
+ }
69
+
70
+ }
71
+ });
72
+ };
73
+
74
+ on(document, 'astro:after-swap', init);
75
+ init();
76
+ </script>
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import type {CopyToClipboardProps} from './types';
3
+ import Button from '../Button/Button.svelte';
4
+ import Copy from "lucide-svelte/icons/copy";
5
+ import Check from "lucide-svelte/icons/check";
6
+ import copyToClipboard from "./copy.ts";
7
+
8
+ let {
9
+ class: className = '',
10
+ variant = 'outline',
11
+ size = 'icon',
12
+ ref = $bindable(null),
13
+ text = undefined,
14
+ type = 'button',
15
+ children,
16
+ ...restProps
17
+ }: CopyToClipboardProps = $props();
18
+
19
+ let showCheck = $state(false);
20
+
21
+ /**
22
+ * Copies the text passed as param to the system clipboard
23
+ * Check if using HTTPS and navigator.clipboard is available
24
+ * Then uses standard clipboard API, otherwise uses fallback
25
+ */
26
+ const copy = async (value: string) => {
27
+ await copyToClipboard(value);
28
+
29
+ showCheck = true;
30
+ setTimeout(() => {
31
+ showCheck = false;
32
+ }, 3000);
33
+ }
34
+ </script>
35
+
36
+ <Button
37
+ type={type}
38
+ class={className}
39
+ variant={variant}
40
+ size={size}
41
+ onclick={() => copy(text)}
42
+ {...restProps}
43
+ >
44
+ {#if showCheck}
45
+ <Check />
46
+ {:else}
47
+ <Copy />
48
+ {/if}
49
+
50
+ </Button>
@@ -0,0 +1,26 @@
1
+ function unsecuredCopyToClipboard(text: string) {
2
+ const textArea = document.createElement('textarea');
3
+ textArea.value = text;
4
+ document.body.appendChild(textArea);
5
+ textArea.focus();
6
+ textArea.select();
7
+ try {
8
+ document.execCommand('copy');
9
+ } catch (err) {
10
+ console.error('Unable to copy to clipboard', err);
11
+ }
12
+ document.body.removeChild(textArea);
13
+ }
14
+
15
+ /**
16
+ * Copies the text passed as param to the system clipboard
17
+ * Check if using HTTPS and navigator.clipboard is available
18
+ * Then uses standard clipboard API, otherwise uses fallback
19
+ */
20
+ export default async function copyToClipboard (value: string) {
21
+ if (window.isSecureContext && navigator.clipboard) {
22
+ await navigator.clipboard.writeText(value);
23
+ } else {
24
+ unsecuredCopyToClipboard(value);
25
+ }
26
+ }
@@ -0,0 +1,10 @@
1
+ import type { WithElementRef } from 'bits-ui';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ import type { ButtonSize, ButtonVariant } from '../Button/theme-variants.ts';
4
+
5
+ export type CopyToClipboardProps = WithElementRef<HTMLButtonAttributes> & {
6
+ variant?: ButtonVariant;
7
+ size?: ButtonSize;
8
+ text?: string;
9
+ class?: string;
10
+ };
@@ -1,7 +1,73 @@
1
1
  <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { twMerge } from 'tailwind-merge';
4
+ import Icon from '@iconify/svelte';
5
+ import type { DarkModeProps } from './types';
2
6
 
7
+ export let label: DarkModeProps['label'] = 'Toggle between Dark and Light mode';
8
+ export let class: DarkModeProps['class'] = 'dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-hidden focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5';
9
+ export let iconClass: DarkModeProps['iconClass'] = 'w-6 h-6';
10
+ export let iconName: DarkModeProps['iconName'] = 'tabler:sun';
11
+ export let initialMode: DarkModeProps['initialMode'] = 'system';
12
+
13
+ let root: HTMLElement;
14
+
15
+ function applyTheme(theme: string) {
16
+ if (theme === 'dark') {
17
+ root.classList.add('dark');
18
+ } else {
19
+ root.classList.remove('dark');
20
+ }
21
+ }
22
+
23
+ function initialize() {
24
+ if ((initialMode && initialMode.endsWith(':only')) || (!localStorage.theme && initialMode !== 'system')) {
25
+ applyTheme(initialMode.replace(':only', ''));
26
+ } else if (
27
+ localStorage.theme === 'dark' ||
28
+ (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
29
+ ) {
30
+ applyTheme('dark');
31
+ } else {
32
+ applyTheme('light');
33
+ }
34
+ }
35
+
36
+ function toggleDarkMode() {
37
+ if (initialMode.endsWith(':only')) {
38
+ return;
39
+ }
40
+ root.classList.toggle('dark');
41
+ localStorage.theme = root.classList.contains('dark') ? 'dark' : 'light';
42
+ }
43
+
44
+ onMount(() => {
45
+ // Only run once
46
+ if (!window.darkModeInitialized) {
47
+ window.darkModeInitialized = true;
48
+
49
+ root = document.documentElement;
50
+
51
+ // Listen for view transitions (if using Astro)
52
+ document.addEventListener('astro:after-swap', () => {
53
+ initialize();
54
+ });
55
+
56
+ initialize();
57
+ }
58
+ });
3
59
  </script>
4
60
 
5
- <div>
6
- TODO
7
- </div>
61
+ {#if !initialMode.endsWith(':only')}
62
+ <button
63
+ type="button"
64
+ class={twMerge('cursor-pointer inline-flex items-center', class)}
65
+ aria-label={label}
66
+ data-yt-toggle-color-scheme
67
+ on:click={toggleDarkMode}
68
+ >
69
+ <slot>
70
+ <Icon icon={iconName} class={iconClass} />
71
+ </slot>
72
+ </button>
73
+ {/if}
@@ -5,7 +5,7 @@ import { cn } from '../../utils';
5
5
  const {
6
6
  title = await Astro.slots.render('title'),
7
7
  subtitle = await Astro.slots.render('subtitle'),
8
- tagline,
8
+ tagline = await Astro.slots.render('tagline'),
9
9
  position = 'center',
10
10
  classes = {},
11
11
  as = 'h2',
@@ -14,11 +14,7 @@ const {
14
14
  ...rest
15
15
  } = Astro.props;
16
16
 
17
- const {
18
- container: containerClass = '',
19
- swiper: swiperClass = '',
20
- swiperThumb: swiperThumbClass = '',
21
- } = classes;
17
+ const { container: containerClass = '', swiper: swiperClass = '', swiperThumb: swiperThumbClass = '' } = classes;
22
18
  ---
23
19
 
24
20
  <style>
@@ -28,7 +24,7 @@ const {
28
24
  }
29
25
  </style>
30
26
 
31
- <div class={cn("@container relative", containerClass)} data-image-gallery-ikea={id}>
27
+ <div class={cn('@container relative', containerClass)} data-image-gallery-ikea={id}>
32
28
  <div class="absolute left-0 top-0 z-20 hidden md:block group w-20">
33
29
  <!-- Thumb Slider -->
34
30
  <button
@@ -6,12 +6,12 @@ import type { LayoutProps as Props } from './types';
6
6
 
7
7
  import { I18N } from 'vendor:config';
8
8
 
9
- const { metadata = {} } = Astro.props;
9
+ const { metadata = {}, class: className } = Astro.props;
10
10
  const { language, textDirection } = I18N;
11
11
  ---
12
12
 
13
13
  <!doctype html>
14
- <html lang={language} dir={textDirection}>
14
+ <html lang={language} dir={textDirection} class={className}>
15
15
  <head>
16
16
  <meta charset="UTF-8" />
17
17
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -10,4 +10,5 @@ export type LayoutProps = {
10
10
  openGraph?: MetaDataOpenGraph;
11
11
  twitter?: MetaDataTwitter;
12
12
  metadata?: MetaData;
13
+ class?: string;
13
14
  };
@@ -7,11 +7,12 @@ import { Icon } from 'astro-icon/components';
7
7
  import { cn } from '../../utils';
8
8
 
9
9
  const {
10
- title = '',
11
- subtitle = '',
12
- tagline = '',
10
+ title = await Astro.slots.render('title'),
11
+ subtitle = await Astro.slots.render('subtitle'),
12
+ tagline = await Astro.slots.render('tagline'),
13
13
  icons = [],
14
14
  images = [],
15
+ position = 'center',
15
16
  isAfterContent = false,
16
17
  id,
17
18
  isDark = false,
@@ -26,7 +27,7 @@ const {
26
27
  containerClass={`${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
27
28
  bg={bg}
28
29
  >
29
- <Headline title={title} subtitle={subtitle} tagline={tagline} />
30
+ <Headline title={title} subtitle={subtitle} tagline={tagline} position={position} />
30
31
 
31
32
  <div class="flex flex-wrap justify-center gap-x-6 sm:gap-x-12 lg:gap-x-24">
32
33
  {
@@ -14,7 +14,7 @@ const {
14
14
  title = await Astro.slots.render('title'),
15
15
  subtitle = await Astro.slots.render('subtitle'),
16
16
  tagline = await Astro.slots.render('tagline'),
17
-
17
+ position = 'center',
18
18
  items = [] as Card1Props[] | Card2Props[] | Card3Props[],
19
19
  isAfterContent = false,
20
20
 
@@ -35,7 +35,13 @@ const Component = via;
35
35
  bg={bg}
36
36
  containerClass={`${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container}`}
37
37
  >
38
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
38
+ <Headline
39
+ title={title}
40
+ subtitle={subtitle}
41
+ tagline={tagline}
42
+ classes={classes?.headline as Record<string, string>}
43
+ position={position}
44
+ />
39
45
 
40
46
  {
41
47
  items && (
@@ -20,7 +20,7 @@ const {
20
20
  isReversed = false,
21
21
  isAfterContent = false,
22
22
  defaultIcon = 'tabler:check',
23
-
23
+ position = 'center',
24
24
  id,
25
25
  isDark = false,
26
26
  classes = {},
@@ -34,7 +34,13 @@ const {
34
34
  containerClass={`${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
35
35
  bg={bg}
36
36
  >
37
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
37
+ <Headline
38
+ title={title}
39
+ subtitle={subtitle}
40
+ tagline={tagline}
41
+ classes={classes?.headline as Record<string, string>}
42
+ position={position}
43
+ />
38
44
 
39
45
  <div class={`md:flex ${isReversed ? 'md:flex-row-reverse' : ''} md:gap-16`}>
40
46
  <div class="md:basis-1/2 self-center">
@@ -12,7 +12,7 @@ const {
12
12
  items = [],
13
13
  columns = 2,
14
14
  isAfterContent = false,
15
-
15
+ position = 'center',
16
16
  id,
17
17
  isDark = false,
18
18
  classes = {},
@@ -26,7 +26,14 @@ const {
26
26
  containerClass={`${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
27
27
  bg={bg}
28
28
  >
29
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
29
+ <Headline
30
+ title={title}
31
+ subtitle={subtitle}
32
+ tagline={tagline}
33
+ classes={classes?.headline as Record<string, string>}
34
+ position={position}
35
+ />
36
+
30
37
  <ItemGrid0 columns={columns}>
31
38
  {
32
39
  items &&
@@ -11,7 +11,7 @@ const {
11
11
  tagline = await Astro.slots.render('tagline'),
12
12
  isAfterContent = false,
13
13
  callToAction = await Astro.slots.render('actions'),
14
-
14
+ position = 'center',
15
15
  id,
16
16
  isDark = false,
17
17
  classes = {},
@@ -25,7 +25,13 @@ const {
25
25
  containerClass={`${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
26
26
  bg={bg}
27
27
  >
28
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
28
+ <Headline
29
+ title={title}
30
+ subtitle={subtitle}
31
+ tagline={tagline}
32
+ classes={classes?.headline as Record<string, string>}
33
+ position={position}
34
+ />
29
35
  <slot />
30
36
 
31
37
  {
@@ -16,7 +16,7 @@ const {
16
16
  items = [],
17
17
  columns = 4,
18
18
  image = await Astro.slots.render('image'),
19
-
19
+ position = 'center',
20
20
  isBeforeContent,
21
21
  isAfterContent,
22
22
  id,
@@ -36,7 +36,13 @@ const Component = via;
36
36
  }`}
37
37
  bg={bg}
38
38
  >
39
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
39
+ <Headline
40
+ title={title}
41
+ subtitle={subtitle}
42
+ tagline={tagline}
43
+ classes={classes?.headline as Record<string, string>}
44
+ position={position}
45
+ />
40
46
 
41
47
  {
42
48
  image && (
@@ -11,6 +11,7 @@ const {
11
11
  tagline,
12
12
  items = [],
13
13
  callToAction,
14
+ position = 'center',
14
15
  id,
15
16
  isDark = false,
16
17
  classes = {},
@@ -26,7 +27,13 @@ const {
26
27
  ${classes?.container ?? ''}`}
27
28
  bg={bg}
28
29
  >
29
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
30
+ <Headline
31
+ title={title}
32
+ subtitle={subtitle}
33
+ tagline={tagline}
34
+ classes={classes?.headline as Record<string, string>}
35
+ position={position}
36
+ />
30
37
 
31
38
  <Stats0 items={items} classes={classes?.items as Record<string, string>} />
32
39
 
@@ -5,13 +5,13 @@ import WidgetWrapper from '../WidgetWrapper/WidgetWrapper.astro';
5
5
  import SwiperSlider from '../SwiperSlider/SwiperSlider.astro';
6
6
  import { Image as AstroImage, getImage } from 'astro:assets';
7
7
  import { fetchLocalImages } from '../../utils/images';
8
- import Image from '../Image/Image.astro';
9
8
 
10
9
  const {
11
10
  title = await Astro.slots.render('title'),
12
11
  subtitle = await Astro.slots.render('subtitle'),
13
12
  tagline = await Astro.slots.render('tagline'),
14
13
  imagesFolder,
14
+ position = 'center',
15
15
  isAfterContent = false,
16
16
 
17
17
  id = (Math.random() + 1).toString(36).substring(7),
@@ -34,7 +34,13 @@ const imagePaths = Object.keys(images).filter((imagePath) => {
34
34
  bg={bg}
35
35
  containerClass={`gallery ${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container}`}
36
36
  >
37
- <Headline title={title} subtitle={subtitle} tagline={tagline} classes={classes?.headline as Record<string, string>} />
37
+ <Headline
38
+ title={title}
39
+ subtitle={subtitle}
40
+ tagline={tagline}
41
+ classes={classes?.headline as Record<string, string>}
42
+ position={position}
43
+ />
38
44
 
39
45
  {
40
46
  imagePaths && (
@@ -53,7 +59,7 @@ const imagePaths = Object.keys(images).filter((imagePath) => {
53
59
  data-pswp-width={optimizedImage.attributes.width}
54
60
  data-pswp-height={optimizedImage.attributes.height}
55
61
  target="_blank"
56
- class="group overflow-hidden rounded-md border-primary cursor-zoom-in block mb-4"
62
+ class="group overflow-hidden rounded-md border-primary cursor-zoom-in block"
57
63
  >
58
64
  <AstroImage
59
65
  src={image}
@@ -7,14 +7,14 @@ import Button from '../Button/Button.astro';
7
7
  import Card5 from '../Card5/Card5.astro';
8
8
 
9
9
  const {
10
- title = '',
11
- subtitle = '',
12
- tagline = '',
10
+ title = await Astro.slots.render('title'),
11
+ subtitle = await Astro.slots.render('subtitle'),
12
+ tagline = await Astro.slots.render('tagline'),
13
13
  items = [],
14
14
  callToAction,
15
15
  columns = 3,
16
16
  isAfterContent = false,
17
-
17
+ position = 'center',
18
18
  id,
19
19
  isDark = false,
20
20
  classes = {},
@@ -28,7 +28,7 @@ const {
28
28
  containerClass={`max-w-6xl mx-auto ${isAfterContent ? 'pt-0 md:pt-0 lg:pt-0' : ''} ${classes?.container ?? ''}`}
29
29
  bg={bg}
30
30
  >
31
- <Headline title={title} subtitle={subtitle} tagline={tagline} />
31
+ <Headline title={title} subtitle={subtitle} tagline={tagline} position={position} />
32
32
 
33
33
  <ItemGrid0 columns={columns}>
34
34
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yatoday/astro-ui",
3
3
  "type": "module",
4
- "version": "0.7.2",
4
+ "version": "0.8.3",
5
5
  "scripts": {
6
6
  "prepare": "husky",
7
7
  "pre-commit": "lint-staged",
@@ -20,7 +20,10 @@
20
20
  "fix": "npm run fix:eslint && npm run fix:prettier",
21
21
  "fix:eslint": "eslint --fix .",
22
22
  "fix:prettier": "prettier -w .",
23
- "create-component": "node scripts/createComponent.js"
23
+ "create-component": "node scripts/createComponent.js",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage"
24
27
  },
25
28
  "dependencies": {
26
29
  "lodash.merge": "^4.6.2",
@@ -39,6 +42,11 @@
39
42
  "@astrojs/svelte": "^7.0.5",
40
43
  "@eslint/js": "9.18.0",
41
44
  "@iconify-json/tabler": "^1.2.10",
45
+ "@tailwindcss/cli": "^4.0.13",
46
+ "@tailwindcss/typography": "^0.5.16",
47
+ "@tailwindcss/vite": "^4.0.12",
48
+ "@testing-library/jest-dom": "^6.6.3",
49
+ "@testing-library/svelte": "^5.2.7",
42
50
  "@types/html-escaper": "^3.0.4",
43
51
  "@types/js-yaml": "^4.0.9",
44
52
  "@types/lodash.merge": "^4.6.9",
@@ -54,21 +62,20 @@
54
62
  "eslint-plugin-svelte": "2.46.1",
55
63
  "husky": "9.1.7",
56
64
  "js-yaml": "^4.1.0",
57
- "jsdom": "26.0.0",
65
+ "jsdom": "^26.0.0",
58
66
  "lint-staged": "15.4.2",
67
+ "lucide-svelte": "^0.469.0",
59
68
  "prettier": "^3.4.2",
60
69
  "prettier-plugin-astro": "^0.14.1",
61
70
  "svelte": "5.20.1",
62
71
  "svelte-eslint-parser": "0.43.0",
63
- "tailwindcss": "^4.0.12",
64
72
  "tailwind-merge": "^3.0.0",
65
73
  "tailwind-variants": "^0.3.1",
66
- "@tailwindcss/cli": "^4.0.13",
67
- "@tailwindcss/typography": "^0.5.16",
68
- "@tailwindcss/vite": "^4.0.12",
74
+ "tailwindcss": "^4.0.12",
69
75
  "typescript": "5.7.3",
70
76
  "typescript-eslint": "8.21.0",
71
- "vite-tsconfig-paths": "5.1.4"
77
+ "vite-tsconfig-paths": "5.1.4",
78
+ "vitest": "^3.1.3"
72
79
  },
73
80
  "exports": {
74
81
  ".": "./index.js",
package/styles/styles.css CHANGED
@@ -279,6 +279,9 @@
279
279
  .relative {
280
280
  position: relative;
281
281
  }
282
+ .static {
283
+ position: static;
284
+ }
282
285
  .sticky {
283
286
  position: sticky;
284
287
  }
package/svelte.d.ts CHANGED
@@ -13,6 +13,7 @@ import type { SvelteCard3Props as YtSvelteCard3Props } from './components/Card3/
13
13
  import type { SvelteCard4Props as YtSvelteCard4Props } from './components/Card4/types'
14
14
  import type { SvelteCard5Props as YtSvelteCard5Props } from './components/Card5/types'
15
15
  import type { SvelteConditionalWrapperProps as YtSvelteConditionalWrapperProps } from './components/ConditionalWrapper/types'
16
+ import type { SvelteCopyToClipboardProps as YtSvelteCopyToClipboardProps } from './components/CopyToClipboard/types'
16
17
  import type { SvelteDarkModeProps as YtSvelteDarkModeProps } from './components/DarkMode/types'
17
18
  import type { SvelteHeadlineProps as YtSvelteHeadlineProps } from './components/Headline/types'
18
19
  import type { SvelteHeroSectionProps as YtSvelteHeroSectionProps } from './components/HeroSection/types'
@@ -62,6 +63,7 @@ declare module '@yatoday/astro-ui/svelte' {
62
63
  export const Card4: Component<YtSvelteCard4Props>
63
64
  export const Card5: Component<YtSvelteCard5Props>
64
65
  export const ConditionalWrapper: Component<YtSvelteConditionalWrapperProps>
66
+ export const CopyToClipboard: Component<YtSvelteCopyToClipboardProps>
65
67
  export const DarkMode: Component<YtSvelteDarkModeProps>
66
68
  export const Headline: Component<YtSvelteHeadlineProps>
67
69
  export const HeroSection: Component<YtSvelteHeroSectionProps>
@@ -110,6 +112,7 @@ declare module '@yatoday/astro-ui/svelte' {
110
112
  export type Card4Props = YtSvelteCard4Props
111
113
  export type Card5Props = YtSvelteCard5Props
112
114
  export type ConditionalWrapperProps = YtSvelteConditionalWrapperProps
115
+ export type CopyToClipboardProps = YtSvelteCopyToClipboardProps
113
116
  export type DarkModeProps = YtSvelteDarkModeProps
114
117
  export type HeadlineProps = YtSvelteHeadlineProps
115
118
  export type HeroSectionProps = YtSvelteHeroSectionProps
package/svelte.js CHANGED
@@ -12,6 +12,7 @@ import Card3Component from './components/Card3/Card3.svelte'
12
12
  import Card4Component from './components/Card4/Card4.svelte'
13
13
  import Card5Component from './components/Card5/Card5.svelte'
14
14
  import ConditionalWrapperComponent from './components/ConditionalWrapper/ConditionalWrapper.svelte'
15
+ import CopyToClipboardComponent from './components/CopyToClipboard/CopyToClipboard.svelte'
15
16
  import DarkModeComponent from './components/DarkMode/DarkMode.svelte'
16
17
  import HeadlineComponent from './components/Headline/Headline.svelte'
17
18
  import HeroSectionComponent from './components/HeroSection/HeroSection.svelte'
@@ -60,6 +61,7 @@ export const Card3 = Card3Component
60
61
  export const Card4 = Card4Component
61
62
  export const Card5 = Card5Component
62
63
  export const ConditionalWrapper = ConditionalWrapperComponent
64
+ export const CopyToClipboard = CopyToClipboardComponent
63
65
  export const DarkMode = DarkModeComponent
64
66
  export const Headline = HeadlineComponent
65
67
  export const HeroSection = HeroSectionComponent