free-astro-components 1.0.13 → 1.1.1

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/README.md CHANGED
@@ -32,6 +32,9 @@ Explore and utilize a variety of components that can help you build your web pro
32
32
  - **ModalHeader**: The header section of the modal, usually containing a title.
33
33
  - **ModalBody**: The main content area of the modal.
34
34
  - **ModalFooter**: The footer section of the modal, typically containing action buttons.
35
+ - **Accordion**
36
+ - A component for creating collapsible content sections.
37
+ - **AccordionItem**: A subcomponent for individual accordion items.
35
38
 
36
39
  ## Getting Started
37
40
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A collection of free Astro components",
4
4
  "author": "Denis Ventura",
5
5
  "type": "module",
6
- "version": "1.0.13",
6
+ "version": "1.1.1",
7
7
  "exports": {
8
8
  ".": {
9
9
  "import": {
@@ -0,0 +1,111 @@
1
+ ---
2
+ import '../css/main.css'
3
+ import type { AccordionItem } from '../../.'
4
+
5
+ interface Props {
6
+ variant?: 'light' | 'shadow' | 'bordered' | 'splitted'
7
+ class?: string
8
+ children: AccordionItem | AccordionItem[]
9
+ }
10
+
11
+ const { variant = 'light', class: className } = Astro.props
12
+ const variantClasses = {
13
+ light: 'ac-accordion--light',
14
+ shadow: 'ac-accordion--shadow',
15
+ bordered: 'ac-accordion--bordered',
16
+ splitted: 'ac-accordion--splitted',
17
+ }[variant]
18
+ ---
19
+
20
+ <div class:list={['ac-accordion', variantClasses, className]}>
21
+ <slot />
22
+ </div>
23
+
24
+ <style>
25
+ :root {
26
+ --ac-accordion-background-color: rgb(var(--ac-color-100));
27
+ --ac-accordion-border-color: var(--ac-color-200);
28
+ --ac-accordion-border-width: var(--ac-border);
29
+ --ac-accordion-rounded: var(--ac-rounded-2xl);
30
+ --ac-accordion-spacing: var(--ac-spacing-4);
31
+ }
32
+
33
+ .ac-accordion {
34
+ background: transparent;
35
+ display: flex;
36
+ flex-direction: column;
37
+
38
+ &.ac-accordion--light {
39
+ > * + * {
40
+ border-color: rgb(var(--ac-accordion-border-color));
41
+ border-top-width: var(--ac-accordion-border-width);
42
+ }
43
+ }
44
+
45
+ &.ac-accordion--shadow {
46
+ background-color: var(--ac-accordion-background-color);
47
+ border-radius: var(--ac-accordion-rounded);
48
+ border-width: var(--ac-accordion-border-width);
49
+ border-color: rgba(var(--ac-accordion-border-color), 0.5);
50
+ box-shadow:
51
+ 0 10px 15px -3px rgb(0 0 0 / 0.1),
52
+ 0 -5px 15px -4px rgb(0 0 0 / 0.05);
53
+ padding: 0 var(--ac-accordion-spacing);
54
+
55
+ > * + * {
56
+ border-color: rgb(var(--ac-accordion-border-color));
57
+ border-top-width: var(--ac-accordion-border-width);
58
+ }
59
+ }
60
+
61
+ &.ac-accordion--bordered {
62
+ border-color: rgb(var(--ac-accordion-border-color));
63
+ border-radius: var(--ac-accordion-rounded);
64
+ border-width: calc(var(--ac-accordion-border-width) * 2);
65
+ padding: 0 var(--ac-accordion-spacing);
66
+
67
+ > * + * {
68
+ border-color: rgb(var(--ac-accordion-border-color));
69
+ border-top-width: var(--ac-accordion-border-width);
70
+ }
71
+ }
72
+
73
+ &.ac-accordion--splitted {
74
+ gap: var(--ac-spacing-2);
75
+
76
+ > * {
77
+ background-color: var(--ac-accordion-background-color);
78
+ border-radius: var(--ac-accordion-rounded);
79
+ border-width: var(--ac-accordion-border-width);
80
+ border-color: rgba(var(--ac-accordion-border-color), 0.5);
81
+ padding: 0 var(--ac-accordion-spacing);
82
+ box-shadow:
83
+ 0 10px 15px -3px rgb(0 0 0 / 0.1),
84
+ 0 -5px 15px -4px rgb(0 0 0 / 0.05);
85
+ }
86
+ }
87
+ }
88
+ </style>
89
+
90
+ <script>
91
+ import {
92
+ DOMLoaded,
93
+ handleResize,
94
+ hasViewportWidthChanged,
95
+ } from '../utils/utils'
96
+ import { setAccordionHeight } from '../utils/accordion'
97
+
98
+ DOMLoaded(() => {
99
+ const accordions = document.querySelectorAll<HTMLDetailsElement>(
100
+ '[data-accordion-item]'
101
+ )
102
+
103
+ setAccordionHeight(accordions)
104
+
105
+ handleResize(() => {
106
+ if (hasViewportWidthChanged()) {
107
+ setAccordionHeight(accordions)
108
+ }
109
+ })
110
+ })
111
+ </script>
@@ -0,0 +1,92 @@
1
+ ---
2
+ import Icon from './Icon.astro'
3
+
4
+ interface Props {
5
+ open?: boolean
6
+ title: string
7
+ name?: string
8
+ class?: string
9
+ }
10
+
11
+ const { open, title, name, class: className } = Astro.props
12
+ ---
13
+
14
+ <details
15
+ data-accordion-item
16
+ class:list={['ac-accordion-item', className]}
17
+ name={name}
18
+ open={open}
19
+ >
20
+ <summary class="ac-accordion-item-title">
21
+ <span>{title}</span>
22
+ <Icon icon="add" />
23
+ </summary>
24
+ <div class="ac-accordion-item-content">
25
+ <slot />
26
+ </div>
27
+ </details>
28
+
29
+ <style>
30
+ .ac-accordion-item {
31
+ margin: 0;
32
+ font-family: var(--ac-font-sans);
33
+ height: var(--ac-accordion-item-collapsed);
34
+ overflow: hidden;
35
+
36
+ &[open] {
37
+ height: var(--ac-accordion-item-expanded);
38
+
39
+ .ac-accordion-item-title {
40
+ color: rgb(var(--ac-color-700));
41
+
42
+ & > svg {
43
+ transform: rotate(-45deg);
44
+ }
45
+ }
46
+ }
47
+
48
+ &.accordion-item--animated {
49
+ transition: height 0.3s linear;
50
+ }
51
+ }
52
+
53
+ .ac-accordion-item-title {
54
+ align-items: center;
55
+ color: rgb(var(--ac-color-500));
56
+ cursor: pointer;
57
+ display: flex;
58
+ font-size: var(--ac-text-lg);
59
+ font-weight: var(--ac-font-medium);
60
+ gap: var(--ac-spacing-3);
61
+ justify-content: space-between;
62
+ line-height: var(--ac-leading-normal);
63
+ list-style-type: none;
64
+ padding: var(--ac-accordion-spacing) 0;
65
+ transition: color 0.3s ease-in-out;
66
+
67
+ &:hover,
68
+ &:focus {
69
+ color: rgb(var(--ac-color-700));
70
+ }
71
+
72
+ &::marker,
73
+ &::-webkit-details-marker {
74
+ display: none;
75
+ }
76
+
77
+ & > span {
78
+ flex: 1 1 auto;
79
+ }
80
+
81
+ & > svg {
82
+ width: var(--ac-spacing-6);
83
+ height: var(--ac-spacing-6);
84
+ transition: transform 0.3s ease-in-out;
85
+ color: rgb(var(--ac-color-400));
86
+ }
87
+ }
88
+
89
+ .ac-accordion-item-content {
90
+ padding: 0 0 var(--ac-accordion-spacing);
91
+ }
92
+ </style>
@@ -7,7 +7,11 @@ interface Props {
7
7
  id: string
8
8
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'full'
9
9
  class?: string
10
- children: ModalHeader | ModalBody | ModalFooter
10
+ children:
11
+ | ModalHeader
12
+ | ModalBody
13
+ | ModalFooter
14
+ | [ModalHeader, ModalBody, ModalFooter]
11
15
  }
12
16
 
13
17
  const { id, size = 'md', class: className } = Astro.props
package/src/index.js CHANGED
@@ -14,4 +14,6 @@ export { default as ModalHeader } from './components/ModalHeader.astro'
14
14
  export { default as ModalBody } from './components/ModalBody.astro'
15
15
  export { default as ModalFooter } from './components/ModalFooter.astro'
16
16
  export { openModal } from './utils/modal.ts'
17
- export { closeModal } from './utils/modal.ts'
17
+ export { closeModal } from './utils/modal.ts'
18
+ export { default as Accordion } from './components/Accordion.astro'
19
+ export { default as AccordionItem } from './components/AccordionItem.astro'
@@ -65,3 +65,11 @@ export const openModal: openModal
65
65
  // closeModal function
66
66
  export type closeModal = typeof import('../index.js').closeModal
67
67
  export const closeModal: closeModal
68
+
69
+ // Accordion component
70
+ export type Accordion = typeof import('../index.js').Accordion
71
+ export const Accordion: Accordion
72
+
73
+ // AccordionItem component
74
+ export type AccordionItem = typeof import('../index.js').AccordionItem
75
+ export const AccordionItem: AccordionItem
@@ -0,0 +1,40 @@
1
+ export const setAccordionHeight = (
2
+ accordions: NodeListOf<HTMLDetailsElement>,
3
+ ) => {
4
+ const originalStates = Array.from(accordions).map(
5
+ (accordion) => accordion.open,
6
+ )
7
+
8
+ accordions.forEach((accordion) => {
9
+ accordion.classList.remove('accordion-item--animated')
10
+ resetAccordionHeight(accordion)
11
+ assignHeight(accordion)
12
+ })
13
+
14
+ accordions.forEach((accordion, index) => {
15
+ accordion.open = originalStates[index]
16
+ accordion.classList.add('accordion-item--animated')
17
+ })
18
+ }
19
+
20
+ const resetAccordionHeight = (accordion: HTMLDetailsElement) => {
21
+ accordion.style.removeProperty('--ac-accordion-item-expanded')
22
+ accordion.style.removeProperty('--ac-accordion-item-collapsed')
23
+ }
24
+
25
+ const assignHeight = (accordion: HTMLDetailsElement) => {
26
+ accordion.open = false
27
+ const collapsedHeight = accordion.offsetHeight
28
+
29
+ accordion.open = true
30
+ const expandedHeight = accordion.scrollHeight
31
+
32
+ accordion.style.setProperty(
33
+ '--ac-accordion-item-expanded',
34
+ `${expandedHeight}px`,
35
+ )
36
+ accordion.style.setProperty(
37
+ '--ac-accordion-item-collapsed',
38
+ `${collapsedHeight}px`,
39
+ )
40
+ }
@@ -1,3 +1,5 @@
1
+ let lastWidth: number;
2
+
1
3
  export const DOMLoaded = (callback: () => void) => {
2
4
  if (document.readyState === 'loading') {
3
5
  document.addEventListener('DOMContentLoaded', callback)
@@ -6,6 +8,39 @@ export const DOMLoaded = (callback: () => void) => {
6
8
  }
7
9
  }
8
10
 
11
+ export const debounce = (callback: (...args: any[]) => void, delay: number) => {
12
+ let timeout: number
13
+
14
+ return (...args: any[]) => {
15
+ clearTimeout(timeout)
16
+ timeout = window.setTimeout(() => callback(...args), delay)
17
+ }
18
+ }
19
+
20
+ export const handleResize = (callback: () => void) => {
21
+ const debouncedCallback = debounce(callback, 300)
22
+ window.addEventListener('resize', debouncedCallback)
23
+
24
+ return () => {
25
+ window.removeEventListener('resize', debouncedCallback)
26
+ }
27
+ }
28
+
9
29
  export const isTouchDevice = () => {
10
30
  return window.matchMedia('(pointer: coarse)').matches
11
31
  }
32
+
33
+ export const hasViewportWidthChanged = (): boolean => {
34
+ if (typeof window !== 'undefined') {
35
+ const currentWidth = window.innerWidth;
36
+ const widthChanged = currentWidth !== lastWidth;
37
+
38
+ if (widthChanged) {
39
+ lastWidth = currentWidth;
40
+ }
41
+
42
+ return widthChanged;
43
+ }
44
+
45
+ return false;
46
+ };