@samuelgomez/astro 0.1.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 (115) hide show
  1. package/README.md +54 -0
  2. package/package.json +42 -0
  3. package/src/components/Accordion/index.astro +31 -0
  4. package/src/components/Alert/AlertDanger.astro +12 -0
  5. package/src/components/Alert/AlertInfo.astro +12 -0
  6. package/src/components/Alert/AlertSuccess.astro +12 -0
  7. package/src/components/Alert/AlertWarning.astro +12 -0
  8. package/src/components/Alert/index.astro +10 -0
  9. package/src/components/Badge/BadgeDanger.astro +15 -0
  10. package/src/components/Badge/BadgeInfo.astro +15 -0
  11. package/src/components/Badge/BadgeSuccess.astro +15 -0
  12. package/src/components/Badge/BadgeWarning.astro +15 -0
  13. package/src/components/Badge/index.astro +9 -0
  14. package/src/components/Box/index.astro +45 -0
  15. package/src/components/BoxPost/index.astro +66 -0
  16. package/src/components/Button/index.astro +35 -0
  17. package/src/components/Caniuse/index.astro +24 -0
  18. package/src/components/Codepen/index.astro +25 -0
  19. package/src/components/DebugGrid/index.astro +100 -0
  20. package/src/components/Demo/Demoui.astro +13 -0
  21. package/src/components/Demo/index.astro +105 -0
  22. package/src/components/Dialog/index.astro +74 -0
  23. package/src/components/FormattedDate/index.astro +17 -0
  24. package/src/components/GithubContributions/index.astro +88 -0
  25. package/src/components/Home/CardsBlog.astro +41 -0
  26. package/src/components/Home/CardsCodepen.astro +53 -0
  27. package/src/components/Home/CardsGithub.astro +44 -0
  28. package/src/components/Img/index.astro +28 -0
  29. package/src/components/Link/index.astro +33 -0
  30. package/src/components/Nav/NavToggle.astro +40 -0
  31. package/src/components/Nav/index.astro +71 -0
  32. package/src/components/Pagination/PaginationItem.astro +51 -0
  33. package/src/components/Pagination/index.astro +39 -0
  34. package/src/components/ScrollWatcher/index.astro +11 -0
  35. package/src/components/Social/index.astro +22 -0
  36. package/src/components/Support/index.astro +24 -0
  37. package/src/components/Svg/index.astro +30 -0
  38. package/src/components/SwitchTheme.astro +45 -0
  39. package/src/components/Table/Table.astro +15 -0
  40. package/src/components/Table/TableBody.astro +21 -0
  41. package/src/components/Table/TableHeader.astro +19 -0
  42. package/src/components/Table/index.astro +21 -0
  43. package/src/components/TableOfContent/index.astro +117 -0
  44. package/src/components/Tabs/TabList.astro +23 -0
  45. package/src/components/Tabs/TabListItem.astro +22 -0
  46. package/src/components/Tabs/TabPanel.astro +19 -0
  47. package/src/components/Tabs/TabsContainer.astro +174 -0
  48. package/src/components/Tabs/index.astro +46 -0
  49. package/src/components/Title/h1.astro +12 -0
  50. package/src/components/Title/h2.astro +12 -0
  51. package/src/components/Title/h3.astro +12 -0
  52. package/src/components/Title/h4.astro +12 -0
  53. package/src/components/Title/h5.astro +12 -0
  54. package/src/components/Title/h6.astro +12 -0
  55. package/src/components/Title/heading.astro +8 -0
  56. package/src/components/Title/index.astro +39 -0
  57. package/src/components/WrapperToHtml/index.astro +8 -0
  58. package/src/components/form/Field/index.astro +43 -0
  59. package/src/components/form/Fieldset/index.astro +14 -0
  60. package/src/components/form/Form/FieldDate.astro +32 -0
  61. package/src/components/form/Form/FieldEmail.astro +32 -0
  62. package/src/components/form/Form/FieldNumber.astro +35 -0
  63. package/src/components/form/Form/FieldPassword.astro +32 -0
  64. package/src/components/form/Form/FieldRadio.astro +32 -0
  65. package/src/components/form/Form/FieldSelect.astro +26 -0
  66. package/src/components/form/Form/FieldText.astro +35 -0
  67. package/src/components/form/Form/FieldTextarea.astro +25 -0
  68. package/src/components/form/Form/index.astro +5 -0
  69. package/src/components/form/Input/index.astro +27 -0
  70. package/src/components/form/InputRadio/index.astro +30 -0
  71. package/src/components/form/MoreInfo/index.astro +14 -0
  72. package/src/components/form/Select/index.astro +25 -0
  73. package/src/components/form/Status/index.astro +34 -0
  74. package/src/components/form/Textarea/index.astro +26 -0
  75. package/src/components/helpers/WrapperOrNot/index.astro +23 -0
  76. package/src/components/layout/Footer.astro +3 -0
  77. package/src/components/layout/Head.astro +154 -0
  78. package/src/components/layout/Header.astro +28 -0
  79. package/src/components/old/Grid.astro +18 -0
  80. package/src/components/old/Section.astro +19 -0
  81. package/src/components/old/SwitchTheme.astro +66 -0
  82. package/src/components/old/index-webco.astro +55 -0
  83. package/src/components/old/send.astro +28 -0
  84. package/src/helpers/dom.ts +19 -0
  85. package/src/helpers/isEmptyOrNull.test.ts +58 -0
  86. package/src/helpers/isEmptyOrNull.ts +6 -0
  87. package/src/helpers/setSlug.test.ts +20 -0
  88. package/src/helpers/setSlug.ts +7 -0
  89. package/src/helpers/setTocTitle.ts +2 -0
  90. package/src/helpers/setVariants.test.ts +26 -0
  91. package/src/helpers/setVariants.ts +18 -0
  92. package/src/icons/Add.astro +18 -0
  93. package/src/icons/Anchor.astro +22 -0
  94. package/src/icons/Arrow.astro +11 -0
  95. package/src/icons/AstroLogo.astro +35 -0
  96. package/src/icons/Check.astro +11 -0
  97. package/src/icons/ChevronDown.astro +11 -0
  98. package/src/icons/ChevronLeft.astro +11 -0
  99. package/src/icons/ChevronRight.astro +11 -0
  100. package/src/icons/Codepen.astro +36 -0
  101. package/src/icons/Cross.astro +11 -0
  102. package/src/icons/ExternalLink.astro +19 -0
  103. package/src/icons/Github.astro +36 -0
  104. package/src/icons/Github2.astro +11 -0
  105. package/src/icons/Grid.astro +19 -0
  106. package/src/icons/Info.astro +11 -0
  107. package/src/icons/Instagram.astro +11 -0
  108. package/src/icons/Linkedin.astro +11 -0
  109. package/src/icons/LogoSG.astro +66 -0
  110. package/src/icons/MoonSun.astro +17 -0
  111. package/src/icons/Send.astro +11 -0
  112. package/src/icons/Slash.astro +14 -0
  113. package/src/icons/Trash.astro +19 -0
  114. package/src/icons/Twitter.astro +14 -0
  115. package/src/types/Permutations.d.ts +3 -0
@@ -0,0 +1,105 @@
1
+ ---
2
+ import TabContainer from '@samuelgomez/astro/components/Tabs/TabsContainer.astro';
3
+ import TabPanel from '@samuelgomez/astro/components/Tabs/TabPanel.astro';
4
+ import TabList from '@samuelgomez/astro/components/Tabs/TabList.astro';
5
+ import TabListItem from '@samuelgomez/astro/components/Tabs/TabListItem.astro';
6
+ import WrapperOrNot from '@samuelgomez/astro/components/helpers/WrapperOrNot/index.astro';
7
+ import { Code } from 'astro/components';
8
+ import { setSlug } from '@samuelgomez/astro/helpers/setSlug';
9
+ /* import type { HTMLTag } from 'astro/types'; */
10
+
11
+ const html = await Astro.slots.render('html');
12
+ const css = await Astro.slots.render('css');
13
+ const js = await Astro.slots.render('js');
14
+ const jsx = await Astro.slots.render('code');
15
+
16
+ const { title, showJs, wrapperTag = 'span' } = Astro.props;
17
+ const idTab = title ? setSlug(title) : 'demo-code';
18
+
19
+ let scopedStyle;
20
+ let className = undefined;
21
+ let WrapperDemo = wrapperTag;
22
+
23
+ if (Boolean(css)) {
24
+ const style = css
25
+ ?.replace('<code>', '')
26
+ ?.replace('</code>', '')
27
+ ?.replace('{', '{\r')
28
+ ?.replace('}', '\r}')
29
+ ?.replace(';', ';\r')
30
+ ?.trim?.();
31
+
32
+ className = `demo-${Date.now() + '_' + Math.floor(Math.random() * 10000)}`;
33
+ scopedStyle = `.${className} { * > * { font-size: revert; } ${style} }`;
34
+ WrapperDemo = 'div';
35
+ }
36
+ ---
37
+
38
+ <TabContainer
39
+ id={idTab}
40
+ idTitle={`title-${idTab}`}
41
+ title={title}
42
+ >
43
+ <TabList idTitle={`title-${idTab}`}>
44
+ <TabListItem id={idTab} index={0}>Démo</TabListItem>
45
+ <TabListItem id={idTab} index={1}>HTML</TabListItem>
46
+ {
47
+ Boolean(css) && (
48
+ <TabListItem id={idTab} index={2}>
49
+ CSS
50
+ </TabListItem>
51
+ )
52
+ }
53
+ {
54
+ Boolean(js) && showJs && (
55
+ <TabListItem id={idTab} index={3}>
56
+ JS
57
+ </TabListItem>
58
+ )
59
+ }
60
+ {
61
+ Boolean(jsx) && (
62
+ <TabListItem id={idTab} index={4}>
63
+ JSX
64
+ </TabListItem>
65
+ )
66
+ }
67
+ </TabList>
68
+ <TabPanel id={idTab} index={0}>
69
+ <WrapperOrNot
70
+ class={className}
71
+ condition={Boolean(WrapperDemo)}
72
+ wrapper={WrapperDemo}
73
+ >
74
+ <Fragment set:html={html} />
75
+ </WrapperOrNot>
76
+ </TabPanel>
77
+ <TabPanel id={idTab} index={1}>
78
+ <Code code={html} lang="html" />
79
+ </TabPanel>
80
+ {
81
+ Boolean(css) && (
82
+ <TabPanel id={idTab} index={2}>
83
+ <Code code={css} lang="css" />
84
+ </TabPanel>
85
+ )
86
+ }
87
+ {
88
+ Boolean(js) && showJs && (
89
+ <TabPanel id={idTab} index={3}>
90
+ <Code code={js} lang="javascript" />
91
+ </TabPanel>
92
+ )
93
+ }
94
+ {
95
+ Boolean(jsx) && (
96
+ <TabPanel id={idTab} index={4}>
97
+ <Code code={jsx} lang="jsx" />
98
+ </TabPanel>
99
+ )
100
+ }
101
+ </TabContainer>
102
+
103
+ {Boolean(css) && <style set:html={scopedStyle} />}
104
+
105
+ {Boolean(js) && <script is:inline set:html={js} />}
@@ -0,0 +1,74 @@
1
+ ---
2
+ import Button, {
3
+ type ButtonProps,
4
+ } from '@samuelgomez/astro/components/Button/index.astro';
5
+ import Title, { type titleLevel } from '@samuelgomez/astro/components/Title/index.astro';
6
+ import Cross from '@samuelgomez/astro/icons/Cross.astro';
7
+
8
+ type Tvariant = 'error' | 'info' | 'success' | 'warning';
9
+
10
+ type Props = {
11
+ variant?: Tvariant;
12
+ id: string;
13
+ titleLevel?: titleLevel;
14
+ openerVariant?: ButtonProps['variant'];
15
+ };
16
+
17
+ const {
18
+ variant = '',
19
+ id,
20
+ titleLevel = 4,
21
+ openerVariant,
22
+ } = Astro.props as Props;
23
+
24
+ const className = Boolean(variant) ? `dialog-${variant}` : null;
25
+
26
+ const footerContent = await Astro.slots.render('footer-content');
27
+ ---
28
+
29
+ <Button variant={openerVariant} id={`open-dialog-${id}`}
30
+ ><slot name="opener" /></Button
31
+ >
32
+
33
+ <dialog
34
+ id={id}
35
+ class={className}
36
+ onclick="document.querySelector(`#${id}`).close()"
37
+ >
38
+ <div class="dialog-inner">
39
+ <header>
40
+ <Title level={titleLevel}><slot name="title" /></Title>
41
+ <form>
42
+ <Button
43
+ variant="small"
44
+ autofocus
45
+ formmethod="dialog"
46
+ type="submit"
47
+ aria-label="Fermer la boîte de dialogue"><Cross /></Button
48
+ >
49
+ </form>
50
+ </header>
51
+ <slot name="content" />
52
+ {
53
+ Boolean(footerContent) && (
54
+ <footer>
55
+ <slot name="footer-content" />
56
+ </footer>
57
+ )
58
+ }
59
+ </div>
60
+ </dialog>
61
+
62
+ <script is:inline define:vars={{ id }}>
63
+ const dialog = document.querySelector(`#${id}`);
64
+ const openBtn = document.querySelector(`#open-dialog-${id}`);
65
+ const closeBtn = dialog.querySelector("button[data-action='close']");
66
+ const validateBtn = dialog.querySelector("button[data-action='validate']");
67
+
68
+ const innerDialog = dialog.querySelector('.dialog-inner');
69
+ innerDialog?.addEventListener('click', (event) => event.stopPropagation());
70
+
71
+ openBtn?.addEventListener('click', () => dialog.showModal());
72
+ closeBtn?.addEventListener('click', () => dialog.close());
73
+ validateBtn?.addEventListener('click', () => dialog.close());
74
+ </script>
@@ -0,0 +1,17 @@
1
+ ---
2
+ type Props = {
3
+ date: Date;
4
+ };
5
+
6
+ const { date } = Astro.props;
7
+ ---
8
+
9
+ <time datetime={date.toISOString()}>
10
+ {
11
+ date.toLocaleDateString('fr-fr', {
12
+ year: 'numeric',
13
+ month: 'short',
14
+ day: 'numeric',
15
+ })
16
+ }
17
+ </time>
@@ -0,0 +1,88 @@
1
+ <div class="contribs-wrapper" aria-hidden="true">
2
+ <div class="contribs">
3
+ {[...Array(364)].map(() => <div class="contrib" />)}
4
+ </div>
5
+ </div>
6
+
7
+ <style>
8
+ :root {
9
+ --gh-c: hsl(var(--g5) / 20%);
10
+ --gh-size: 15px;
11
+ }
12
+
13
+ .contribs-wrapper {
14
+ overflow: hidden;
15
+ padding: 1rem;
16
+ border: 1px solid hsl(var(--g-h5) / 50%);
17
+ border-radius: 3px;
18
+ background-color: var(--bk);
19
+ width: max-content;
20
+ margin: 0 auto;
21
+ }
22
+ .contribs {
23
+ display: grid;
24
+ grid-template-columns: repeat(var(--gh-cols), var(--gh-size));
25
+ gap: 2px;
26
+ }
27
+ .contrib {
28
+ transition: background-color 0.5s ease-in-out;
29
+ height: var(--gh-size);
30
+ width: var(--gh-size);
31
+ background-color: var(--gh-c);
32
+ border-radius: 3px;
33
+ display: var(--gh-show, none);
34
+ }
35
+ </style>
36
+
37
+ <script>
38
+ function randomIntFromInterval(min: number, max: number) {
39
+ return Math.floor(Math.random() * (max - min + 1) + min);
40
+ }
41
+
42
+ const grey = 'hsl(var(--g-h5) / 20%)';
43
+ const colors = [
44
+ grey,
45
+ 'var(--tk8)',
46
+ grey,
47
+ 'var(--tk7)',
48
+ 'var(--tk6)',
49
+ grey,
50
+ 'var(--tk3)',
51
+ ];
52
+
53
+ function setColor(elt: HTMLElement) {
54
+ const randomIndex = randomIntFromInterval(0, colors.length - 1);
55
+ elt.style.setProperty('--gh-c', colors[randomIndex]);
56
+ }
57
+
58
+ const elts = document.querySelectorAll<HTMLElement>('.contrib');
59
+ const wrapper = document.querySelector('.contribs-wrapper');
60
+
61
+ window.addEventListener('load', () => {
62
+ [...elts].forEach(elt => {
63
+ setColor(elt);
64
+ });
65
+ });
66
+
67
+ wrapper?.addEventListener('mouseover', startAnimation);
68
+ wrapper?.addEventListener('mouseout', stopAnimation);
69
+
70
+ let intervals = [] as NodeJS.Timeout[];
71
+
72
+ function startAnimation() {
73
+ [...elts].forEach(elt => {
74
+ const randomInterval = randomIntFromInterval(300, 2000);
75
+ const interval = setInterval(() => {
76
+ setColor(elt);
77
+ }, randomInterval);
78
+ intervals.push(interval);
79
+ });
80
+ }
81
+
82
+ function stopAnimation() {
83
+ intervals.forEach(interval => {
84
+ clearInterval(interval);
85
+ });
86
+ intervals = [];
87
+ }
88
+ </script>
@@ -0,0 +1,41 @@
1
+ ---
2
+ import Link from '@samuelgomez/astro/components/Link/index.astro';
3
+ import BoxPost from '@samuelgomez/astro/components/BoxPost/index.astro';
4
+ import ChevronRight from '@samuelgomez/astro/icons/ChevronRight.astro';
5
+ import { getCollection } from 'astro:content';
6
+ import LogoSG from '@samuelgomez/astro/icons/LogoSG.astro';
7
+ import { projectAuth } from '@firebase/config';
8
+ const { currentUser } = projectAuth;
9
+
10
+ let posts = await getCollection('blog');
11
+
12
+ if (!currentUser) {
13
+ posts = posts.filter(({ data }) => data.published);
14
+ }
15
+
16
+ posts = posts
17
+ .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
18
+ .slice(0, 6);
19
+ ---
20
+
21
+ <section>
22
+ <header>
23
+ <LogoSG size="100" />
24
+ <h2>Derniers articles</h2>
25
+ <p>Voici les derniers articles de blog sur le développements Front-end.</p>
26
+ </header>
27
+ <section>
28
+ {
29
+ posts.map(({ slug, data }) => (
30
+ <BoxPost slug={slug} {...data} heroImage={undefined} category="blog">
31
+ {data?.description && <p>{data?.description}</p>}
32
+ </BoxPost>
33
+ ))
34
+ }
35
+ </section>
36
+ <footer>
37
+ <Link href="/blog">
38
+ Voir mes articles Front-end<ChevronRight slot="after-link" />
39
+ </Link>
40
+ </footer>
41
+ </section>
@@ -0,0 +1,53 @@
1
+ ---
2
+ import Codepen from '@samuelgomez/astro/icons/Codepen.astro';
3
+ import Link from '@samuelgomez/astro/components/Link/index.astro';
4
+ import Box from '@samuelgomez/astro/components/Box/index.astro';
5
+ import ChevronRight from '@samuelgomez/astro/icons/ChevronRight.astro';
6
+ import myImage from '@images/codepen-multiple-gradients.webp';
7
+ import myImage2 from '@images/codepen-button-border-gradient.webp';
8
+
9
+ const cards = [
10
+ {
11
+ src: myImage,
12
+ title: 'Multiple gradients',
13
+ description: 'Utilisation des radial-gradient CSS',
14
+ link: 'https://codepen.io/gsam59/full/vYbbXoa',
15
+ },
16
+ {
17
+ src: myImage2,
18
+ title: 'Button border gradient',
19
+ description: "Création d'un bordure animée avec un dégradé",
20
+ link: 'https://codepen.io/gsam59/full/ExpzOqG',
21
+ },
22
+ {
23
+ src: myImage,
24
+ title: 'Multiple gradients',
25
+ description: 'Utilisation des radial-gradient CSS',
26
+ link: 'https://codepen.io/gsam59/full/vYbbXoa',
27
+ },
28
+ ];
29
+ ---
30
+
31
+ <section>
32
+ <header>
33
+ <Codepen />
34
+ <h2>Derniers Codepen</h2>
35
+ <p>Voici les dernières démos Codepen pour ma veille CSS et HTML</p>
36
+ </header>
37
+ <section>
38
+ {
39
+ cards.map(({ link, ...card }) => (
40
+ <Box {...card} titleLevel={3} variant="center">
41
+ <Link href={link}>
42
+ Voir la démo <ChevronRight slot="after-link" />
43
+ </Link>
44
+ </Box>
45
+ ))
46
+ }
47
+ </section>
48
+ <footer>
49
+ <Link href="/about">
50
+ Voir mes Codepens<ChevronRight slot="after-link" />
51
+ </Link>
52
+ </footer>
53
+ </section>
@@ -0,0 +1,44 @@
1
+ ---
2
+ import Github from '@samuelgomez/astro/icons/Github.astro';
3
+ import Link from '@samuelgomez/astro/components/Link/index.astro';
4
+ import BoxPost from '@samuelgomez/astro/components/BoxPost/index.astro';
5
+ import ChevronRight from '@samuelgomez/astro/icons/ChevronRight.astro';
6
+ import GithubContributions from '@samuelgomez/astro/components/GithubContributions/index.astro';
7
+ import { getCollection } from 'astro:content';
8
+ import { projectAuth } from '@firebase/config';
9
+ const { currentUser } = projectAuth;
10
+
11
+ let posts = await getCollection('work');
12
+
13
+ if (!currentUser) {
14
+ posts = posts.filter(({ data }) => data.published);
15
+ }
16
+
17
+ posts = posts
18
+ .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
19
+ .slice(0, 6);
20
+ ---
21
+
22
+ <section>
23
+ <header>
24
+ <Github />
25
+ <h2>Derniers projets Open Source</h2>
26
+ <p>Voici les derniers projets Open Source sur lequels j'ai travaillé.</p>
27
+ <GithubContributions />
28
+ </header>
29
+
30
+ <section>
31
+ {
32
+ posts.map(({ slug, data }) => (
33
+ <BoxPost slug={slug} {...data} category="work">
34
+ {data?.description && <p>{data?.description}</p>}
35
+ </BoxPost>
36
+ ))
37
+ }
38
+ </section>
39
+ <footer>
40
+ <Link href="/work">
41
+ Voir mes projets Open Source<ChevronRight slot="after-link" />
42
+ </Link>
43
+ </footer>
44
+ </section>
@@ -0,0 +1,28 @@
1
+ ---
2
+ import type { ImageMetadata } from 'astro';
3
+ import { Image } from 'astro:assets';
4
+ import { isEmptyObject } from '@samuelgomez/astro/helpers/isEmptyOrNull';
5
+
6
+ const { src, alt = '' } = Astro.props;
7
+
8
+ let image = {} as ImageMetadata;
9
+
10
+ if (Boolean(src)) {
11
+ const baseImagePath = '/src/assets/images/';
12
+ const imagePath = `${baseImagePath}${src}`;
13
+ const imageGlob = `${baseImagePath}*.{jpeg,jpg,png,gif,webp,svg}`;
14
+
15
+ const images = import.meta.glob<{ default: ImageMetadata }>(
16
+ '/src/assets/images/*.{jpeg,jpg,png,gif,webp,svg}'
17
+ );
18
+
19
+ if (!images[imagePath]) {
20
+ throw new Error(`"${imagePath}" does not exist in glob: "${imageGlob}"`);
21
+ }
22
+
23
+ const { default: imageSrc } = await images[imagePath]();
24
+ image = imageSrc;
25
+ }
26
+ ---
27
+
28
+ {!isEmptyObject(image) && <Image src={image} alt={alt} quality="max" />}
@@ -0,0 +1,33 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ export type Props = HTMLAttributes<'a'>;
5
+
6
+ const { href = '', ...props } = Astro.props;
7
+
8
+ const url = new URL(href ?? '', Astro.url);
9
+ const isExternal = url.host !== Astro.url.host;
10
+ const pathname = new URL(Astro.request.url).pathname;
11
+ const isCurrent =
12
+ (pathname?.startsWith(href?.toString() ?? '') && href?.toString() !== '/') ||
13
+ (href?.toString() === '/' && pathname === '/');
14
+
15
+ const content = await Astro.slots.render('default');
16
+ ---
17
+
18
+ <a
19
+ href={href}
20
+ aria-current={isCurrent ? 'page' : null}
21
+ rel={isExternal ? 'noopener' : null}
22
+ {...props}
23
+ >
24
+ <slot name="before-link" />
25
+ {
26
+ Boolean(content) && (
27
+ <span>
28
+ <slot />
29
+ </span>
30
+ )
31
+ }
32
+ <slot name="after-link" /></a
33
+ >
@@ -0,0 +1,40 @@
1
+ ---
2
+ import Button from '@samuelgomez/astro/components/Button/index.astro';
3
+
4
+ const { 'aria-controls': ariaControls } = Astro.props;
5
+ ---
6
+
7
+ <Button
8
+ class="burger"
9
+ variant="ghost"
10
+ aria-label="Toggle Navigation"
11
+ aria-expanded="false"
12
+ aria-controls={ariaControls}
13
+ >
14
+ <div aria-hidden="true"></div>
15
+ <div aria-hidden="true"></div>
16
+ <div aria-hidden="true"></div>
17
+ </Button>
18
+
19
+ <script>
20
+ const main = document.querySelector('main');
21
+ const footer = document.querySelector('footer');
22
+ document?.querySelector('.burger')?.addEventListener('click', (e) => {
23
+ const button = e?.currentTarget as HTMLButtonElement;
24
+ button.setAttribute(
25
+ 'aria-expanded',
26
+ `${button?.getAttribute('aria-expanded') === 'false'}`
27
+ );
28
+ main?.toggleAttribute('inert');
29
+ footer?.toggleAttribute('inert');
30
+ });
31
+ document?.querySelector('.burger')?.addEventListener('keydown', (e) => {
32
+ const keyboardEvent = e as KeyboardEvent;
33
+ const button = e?.currentTarget as HTMLButtonElement;
34
+ if (keyboardEvent.key === 'Escape') {
35
+ button.setAttribute('aria-expanded', 'false');
36
+ main?.removeAttribute('inert');
37
+ footer?.removeAttribute('inert');
38
+ }
39
+ });
40
+ </script>
@@ -0,0 +1,71 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import Link from '@samuelgomez/astro/components/Link/index.astro';
4
+ import { setSlug } from '@samuelgomez/astro/helpers/setSlug';
5
+ import H3 from '@samuelgomez/astro/components/Title/h3.astro';
6
+
7
+ type Item = {
8
+ label: string;
9
+ href: string;
10
+ admin?: boolean;
11
+ };
12
+
13
+ export type NavProps = HTMLAttributes<'nav'> & {
14
+ items?: Item[];
15
+ label?: string;
16
+ class?: string;
17
+ id?: string;
18
+ mode?: 'horizontal' | 'vertical';
19
+ ordered?: boolean;
20
+ title?: string;
21
+ };
22
+
23
+ const {
24
+ items = [],
25
+ label,
26
+ class: className,
27
+ id,
28
+ mode = 'horizontal',
29
+ ordered = false,
30
+ title = false,
31
+ ...navProps
32
+ } = Astro.props as NavProps;
33
+
34
+ const List = ordered ? 'ol' : 'ul';
35
+
36
+ const labelId = label ? setSlug(label) : title ? setSlug(title) : null;
37
+
38
+ const isDev = import.meta.env.DEV;
39
+ ---
40
+
41
+ {
42
+ Boolean(items.length) && (
43
+ <>
44
+ {title && <H3 label={title} noLink />}
45
+ <nav
46
+ data-mode={mode}
47
+ id={id}
48
+ class={className}
49
+ aria-labelledby={labelId}
50
+ {...navProps}
51
+ >
52
+ {label && (
53
+ <span id={labelId} class="sr-only">
54
+ {label}
55
+ </span>
56
+ )}
57
+
58
+ <List>
59
+ {items.map(
60
+ ({ label, href, admin }) =>
61
+ (!admin || (admin && isDev)) && (
62
+ <li>
63
+ <Link href={href}>{label}</Link>
64
+ </li>
65
+ )
66
+ )}
67
+ </List>
68
+ </nav>
69
+ </>
70
+ )
71
+ }
@@ -0,0 +1,51 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import Link from '@samuelgomez/astro/components/Link/index.astro';
4
+ import Button from '@samuelgomez/astro/components/Button/index.astro';
5
+
6
+ export const ELLIPSIS = '...';
7
+
8
+ export const itemVariants = {
9
+ link: 'link',
10
+ button: 'button',
11
+ };
12
+
13
+ export type PaginationItemAs = keyof typeof itemVariants;
14
+
15
+ type Props = HTMLAttributes<'li'> & {
16
+ page: number | typeof ELLIPSIS;
17
+ isCurrentPage: boolean;
18
+ asItem?: PaginationItemAs;
19
+ href?: string;
20
+ };
21
+
22
+ const {
23
+ page,
24
+ isCurrentPage,
25
+ 'aria-label': ariaLabel,
26
+ asItem = 'link',
27
+ class: className,
28
+ ...restProps
29
+ } = Astro.props;
30
+ ---
31
+
32
+ <li>
33
+ {
34
+ page === ELLIPSIS ? (
35
+ <span aria-label={ariaLabel}>{page}</span>
36
+ ) : asItem === 'link' ? (
37
+ <Link aria-label={ariaLabel} {...restProps}>
38
+ {page}
39
+ </Link>
40
+ ) : (
41
+ <Button
42
+ variant="reverse"
43
+ disabled={isCurrentPage || null}
44
+ aria-label={ariaLabel}
45
+ {...restProps}
46
+ >
47
+ {page}
48
+ </Button>
49
+ )
50
+ }
51
+ </li>
@@ -0,0 +1,39 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import PaginationItem, { type PaginationItemAs } from './PaginationItem.astro';
4
+
5
+ type Props = HTMLAttributes<'nav'> & {
6
+ numberPages: number;
7
+ currentPage: number;
8
+ asItem?: PaginationItemAs;
9
+ };
10
+
11
+ const { numberPages, currentPage, asItem = 'link', ...navProps } = Astro.props;
12
+
13
+ const items = Array.from({ length: numberPages }, (_, index) => ({
14
+ page: index + 1,
15
+ isCurrentPage: index + 1 === currentPage,
16
+ }));
17
+ ---
18
+
19
+ {
20
+ Boolean(items.length) && (
21
+ <nav {...navProps} class="rounded-list">
22
+ <ol>
23
+ {items.map(({ page }) => (
24
+ <PaginationItem
25
+ page={page}
26
+ isCurrentPage={currentPage === page}
27
+ href={asItem === 'link' ? `?page=${page}` : undefined}
28
+ aria-label={
29
+ currentPage === page
30
+ ? `Page ${page}, current page`
31
+ : `Go to page ${page}`
32
+ }
33
+ asItem={asItem}
34
+ />
35
+ ))}
36
+ </ol>
37
+ </nav>
38
+ )
39
+ }