webcoreui 0.5.0 → 0.6.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.
- package/README.md +7 -0
- package/astro.d.ts +115 -68
- package/astro.js +8 -0
- package/components/Badge/Badge.astro +4 -0
- package/components/Badge/Badge.svelte +5 -1
- package/components/Badge/Badge.tsx +4 -0
- package/components/Badge/badge.module.scss +8 -0
- package/components/Badge/badge.ts +7 -3
- package/components/Breadcrumb/Breadcrumb.astro +51 -0
- package/components/Breadcrumb/Breadcrumb.svelte +45 -0
- package/components/Breadcrumb/Breadcrumb.tsx +51 -0
- package/components/Breadcrumb/breadcrumb.module.scss +26 -0
- package/components/Breadcrumb/breadcrumb.ts +12 -0
- package/components/Button/button.ts +13 -3
- package/components/Checkbox/checkbox.ts +4 -2
- package/components/Footer/Footer.astro +91 -0
- package/components/Footer/Footer.svelte +94 -0
- package/components/Footer/Footer.tsx +107 -0
- package/components/Footer/footer.module.scss +61 -0
- package/components/Footer/footer.ts +29 -0
- package/components/Icon/Icon.svelte +1 -1
- package/components/Icon/icon.ts +18 -1
- package/components/Icon/map.ts +8 -0
- package/components/List/list.ts +3 -1
- package/components/Masonry/Masonry.astro +54 -0
- package/components/Masonry/Masonry.svelte +54 -0
- package/components/Masonry/Masonry.tsx +62 -0
- package/components/Masonry/masonry.module.scss +18 -0
- package/components/Masonry/masonry.ts +36 -0
- package/components/Menu/Menu.astro +1 -1
- package/components/Menu/Menu.svelte +1 -1
- package/components/Menu/Menu.tsx +1 -1
- package/components/Menu/menu.ts +4 -2
- package/components/Modal/Modal.astro +2 -0
- package/components/Modal/Modal.svelte +2 -0
- package/components/Modal/Modal.tsx +2 -0
- package/components/Modal/modal.module.scss +29 -22
- package/components/Modal/modal.ts +1 -0
- package/components/Rating/rating.ts +3 -1
- package/components/Sidebar/Sidebar.astro +61 -0
- package/components/Sidebar/Sidebar.svelte +50 -0
- package/components/Sidebar/Sidebar.tsx +58 -0
- package/components/Sidebar/sidebar.module.scss +43 -0
- package/components/Sidebar/sidebar.ts +21 -0
- package/components/Switch/switch.ts +4 -2
- package/icons/circle-close.svg +3 -0
- package/icons/components.svg +3 -0
- package/icons/file.svg +3 -0
- package/icons/home.svg +4 -0
- package/icons/sun.svg +1 -1
- package/icons.d.ts +4 -0
- package/icons.js +4 -0
- package/package.json +8 -10
- package/react.d.ts +115 -68
- package/react.js +8 -0
- package/svelte.d.ts +116 -68
- package/svelte.js +8 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { FooterProps } from './footer'
|
|
3
|
+
|
|
4
|
+
import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.astro'
|
|
5
|
+
|
|
6
|
+
import styles from './footer.module.scss'
|
|
7
|
+
|
|
8
|
+
interface Props extends FooterProps {}
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
logo,
|
|
12
|
+
columns,
|
|
13
|
+
subText,
|
|
14
|
+
className,
|
|
15
|
+
wrapperClassName,
|
|
16
|
+
subTextClassName
|
|
17
|
+
} = Astro.props
|
|
18
|
+
|
|
19
|
+
const classes = [
|
|
20
|
+
styles.footer,
|
|
21
|
+
className
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const containerClasses = [
|
|
25
|
+
styles.container,
|
|
26
|
+
wrapperClassName
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const subTextClasses = [
|
|
30
|
+
styles.subtext,
|
|
31
|
+
subTextClassName
|
|
32
|
+
]
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<footer class:list={classes}>
|
|
36
|
+
<div class:list={containerClasses}>
|
|
37
|
+
<ConditionalWrapper condition={!!(logo?.url || logo?.html)}>
|
|
38
|
+
<div slot="wrapper" class={styles.wrapper}>children</div>
|
|
39
|
+
{logo?.url && (
|
|
40
|
+
<a href="/">
|
|
41
|
+
<img
|
|
42
|
+
src={logo.url}
|
|
43
|
+
alt={logo.alt || 'Logo'}
|
|
44
|
+
width={logo.width}
|
|
45
|
+
height={logo.height}
|
|
46
|
+
loading={logo.eager ? undefined : 'lazy'}
|
|
47
|
+
/>
|
|
48
|
+
</a>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{logo?.html && (
|
|
52
|
+
<a href="/" aria-label={logo.alt || 'Logo'}>
|
|
53
|
+
<Fragment set:html={logo.html} />
|
|
54
|
+
</a>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{!!columns?.length && (
|
|
58
|
+
<ConditionalWrapper condition={columns.length > 1}>
|
|
59
|
+
<div slot="wrapper" class={styles.columns}>children</div>
|
|
60
|
+
{columns.map(column => (
|
|
61
|
+
<ConditionalWrapper condition={!!column.title}>
|
|
62
|
+
<div slot="wrapper">children</div>
|
|
63
|
+
{column.title && (
|
|
64
|
+
<strong class={styles['column-title']}>{column.title}</strong>
|
|
65
|
+
)}
|
|
66
|
+
<ul class={styles.column}>
|
|
67
|
+
{column.items.map(item => (
|
|
68
|
+
<li>
|
|
69
|
+
<a
|
|
70
|
+
href={item.href}
|
|
71
|
+
target={item.target}
|
|
72
|
+
class:list={[item.active && styles.active]}
|
|
73
|
+
>
|
|
74
|
+
{item.name}
|
|
75
|
+
</a>
|
|
76
|
+
</li>
|
|
77
|
+
))}
|
|
78
|
+
</ul>
|
|
79
|
+
</ConditionalWrapper>
|
|
80
|
+
))}
|
|
81
|
+
</ConditionalWrapper>
|
|
82
|
+
)}
|
|
83
|
+
</ConditionalWrapper>
|
|
84
|
+
{(subText || Astro.slots.has('default')) && (
|
|
85
|
+
<div class:list={subTextClasses}>
|
|
86
|
+
{subText && <span set:html={subText} />}
|
|
87
|
+
<slot />
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</footer>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FooterProps } from './footer'
|
|
3
|
+
|
|
4
|
+
import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.svelte'
|
|
5
|
+
|
|
6
|
+
import { classNames } from '../../utils/classNames'
|
|
7
|
+
|
|
8
|
+
import styles from './footer.module.scss'
|
|
9
|
+
|
|
10
|
+
export let logo: FooterProps['logo'] = null
|
|
11
|
+
export let columns: FooterProps['columns'] = []
|
|
12
|
+
export let subText: FooterProps['subText'] = ''
|
|
13
|
+
export let className: FooterProps['className'] = ''
|
|
14
|
+
export let wrapperClassName: FooterProps['wrapperClassName'] = ''
|
|
15
|
+
export let subTextClassName: FooterProps['subTextClassName'] = ''
|
|
16
|
+
|
|
17
|
+
const classes = classNames([
|
|
18
|
+
styles.footer,
|
|
19
|
+
className
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
const containerClasses = classNames([
|
|
23
|
+
styles.container,
|
|
24
|
+
wrapperClassName
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const subTextClasses = classNames([
|
|
28
|
+
styles.subtext,
|
|
29
|
+
subTextClassName
|
|
30
|
+
])
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<footer class={classes}>
|
|
34
|
+
<div class={containerClasses}>
|
|
35
|
+
<ConditionalWrapper
|
|
36
|
+
condition={!!(logo?.url || logo?.html)}
|
|
37
|
+
class={styles.wrapper}
|
|
38
|
+
>
|
|
39
|
+
{#if logo?.url}
|
|
40
|
+
<a href="/">
|
|
41
|
+
<img
|
|
42
|
+
src={logo.url}
|
|
43
|
+
alt={logo.alt || 'Logo'}
|
|
44
|
+
width={logo.width}
|
|
45
|
+
height={logo.height}
|
|
46
|
+
loading={logo.eager ? undefined : 'lazy'}
|
|
47
|
+
/>
|
|
48
|
+
</a>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
51
|
+
{#if logo?.html}
|
|
52
|
+
<a href="/" aria-label={logo.alt || 'Logo'}>
|
|
53
|
+
{@html logo.html}
|
|
54
|
+
</a>
|
|
55
|
+
{/if}
|
|
56
|
+
|
|
57
|
+
{#if columns?.length}
|
|
58
|
+
<ConditionalWrapper
|
|
59
|
+
condition={columns.length > 1}
|
|
60
|
+
class={styles.columns}
|
|
61
|
+
>
|
|
62
|
+
{#each columns as column}
|
|
63
|
+
<ConditionalWrapper condition={!!column.title}>
|
|
64
|
+
{#if column.title}
|
|
65
|
+
<strong class={styles['column-title']}>{column.title}</strong>
|
|
66
|
+
{/if}
|
|
67
|
+
<ul class={styles.column}>
|
|
68
|
+
{#each column.items as item}
|
|
69
|
+
<li>
|
|
70
|
+
<a
|
|
71
|
+
href={item.href}
|
|
72
|
+
target={item.target}
|
|
73
|
+
class={item.active ? styles.active : undefined}
|
|
74
|
+
>
|
|
75
|
+
{item.name}
|
|
76
|
+
</a>
|
|
77
|
+
</li>
|
|
78
|
+
{/each}
|
|
79
|
+
</ul>
|
|
80
|
+
</ConditionalWrapper>
|
|
81
|
+
{/each}
|
|
82
|
+
</ConditionalWrapper>
|
|
83
|
+
{/if}
|
|
84
|
+
</ConditionalWrapper>
|
|
85
|
+
{#if subText || $$slots.default}
|
|
86
|
+
<div class={subTextClasses}>
|
|
87
|
+
{#if subText}
|
|
88
|
+
<span>{@html subText}</span>
|
|
89
|
+
{/if}
|
|
90
|
+
<slot />
|
|
91
|
+
</div>
|
|
92
|
+
{/if}
|
|
93
|
+
</div>
|
|
94
|
+
</footer>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { ReactFooterProps } from './footer'
|
|
3
|
+
|
|
4
|
+
import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
|
|
5
|
+
|
|
6
|
+
import { classNames } from '../../utils/classNames'
|
|
7
|
+
|
|
8
|
+
import styles from './footer.module.scss'
|
|
9
|
+
|
|
10
|
+
const Footer = ({
|
|
11
|
+
logo,
|
|
12
|
+
columns,
|
|
13
|
+
subText,
|
|
14
|
+
className,
|
|
15
|
+
wrapperClassName,
|
|
16
|
+
subTextClassName,
|
|
17
|
+
children
|
|
18
|
+
}: ReactFooterProps) => {
|
|
19
|
+
const classes = classNames([
|
|
20
|
+
styles.footer,
|
|
21
|
+
className
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const containerClasses = classNames([
|
|
25
|
+
styles.container,
|
|
26
|
+
wrapperClassName
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
const subTextClasses = classNames([
|
|
30
|
+
styles.subtext,
|
|
31
|
+
subTextClassName
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<footer className={classes}>
|
|
36
|
+
<div className={containerClasses}>
|
|
37
|
+
<ConditionalWrapper
|
|
38
|
+
condition={!!(logo?.url || logo?.html)}
|
|
39
|
+
wrapper={children => <div className={styles.wrapper}>{children}</div>}
|
|
40
|
+
>
|
|
41
|
+
{logo?.url && (
|
|
42
|
+
<a href="/">
|
|
43
|
+
<img
|
|
44
|
+
src={logo.url}
|
|
45
|
+
alt={logo.alt || 'Logo'}
|
|
46
|
+
width={logo.width}
|
|
47
|
+
height={logo.height}
|
|
48
|
+
loading={logo.eager ? undefined : 'lazy'}
|
|
49
|
+
/>
|
|
50
|
+
</a>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{logo?.html && (
|
|
54
|
+
<a
|
|
55
|
+
href="/"
|
|
56
|
+
aria-label={logo.alt || 'Logo'}
|
|
57
|
+
dangerouslySetInnerHTML={{ __html: logo.html }}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{!!columns?.length && (
|
|
62
|
+
<ConditionalWrapper
|
|
63
|
+
condition={columns.length > 1}
|
|
64
|
+
wrapper={children => <div className={styles.columns}>{children}</div>}
|
|
65
|
+
>
|
|
66
|
+
{columns.map((column, columnIndex) => (
|
|
67
|
+
<ConditionalWrapper
|
|
68
|
+
condition={!!column.title}
|
|
69
|
+
wrapper={children => <div>{children}</div>}
|
|
70
|
+
key={columnIndex}
|
|
71
|
+
>
|
|
72
|
+
{column.title && (
|
|
73
|
+
<strong className={styles['column-title']}>
|
|
74
|
+
{column.title}
|
|
75
|
+
</strong>
|
|
76
|
+
)}
|
|
77
|
+
<ul className={styles.column}>
|
|
78
|
+
{column.items.map((item, itemKey) => (
|
|
79
|
+
<li key={itemKey}>
|
|
80
|
+
<a
|
|
81
|
+
href={item.href}
|
|
82
|
+
target={item.target}
|
|
83
|
+
className={item.active ? styles.active : undefined}
|
|
84
|
+
>
|
|
85
|
+
{item.name}
|
|
86
|
+
</a>
|
|
87
|
+
</li>
|
|
88
|
+
))}
|
|
89
|
+
</ul>
|
|
90
|
+
</ConditionalWrapper>
|
|
91
|
+
))}
|
|
92
|
+
</ConditionalWrapper>
|
|
93
|
+
)}
|
|
94
|
+
</ConditionalWrapper>
|
|
95
|
+
{(subText || children) && (
|
|
96
|
+
<div className={subTextClasses}>
|
|
97
|
+
{subText && <span dangerouslySetInnerHTML={{ __html: subText }} />}
|
|
98
|
+
{children}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</footer>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default Footer
|
|
107
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
@import '../../scss/config.scss';
|
|
2
|
+
|
|
3
|
+
.footer {
|
|
4
|
+
@include border(top, primary-50);
|
|
5
|
+
|
|
6
|
+
.column-title {
|
|
7
|
+
@include spacing(mb-default);
|
|
8
|
+
|
|
9
|
+
display: block;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.column {
|
|
13
|
+
@include spacing(0);
|
|
14
|
+
|
|
15
|
+
list-style-type: none;
|
|
16
|
+
|
|
17
|
+
a {
|
|
18
|
+
@include typography(primary-10, none);
|
|
19
|
+
|
|
20
|
+
&:hover,
|
|
21
|
+
&.active {
|
|
22
|
+
@include typography(primary, underline);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.container {
|
|
29
|
+
@include spacing(auto-default, px-default);
|
|
30
|
+
|
|
31
|
+
max-width: 1200px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.wrapper {
|
|
35
|
+
@include layout(flex, column, default, h-between);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.columns {
|
|
39
|
+
@include layout(flex, column, default);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.subtext {
|
|
43
|
+
@include border(top, primary-60);
|
|
44
|
+
@include spacing(mt-default, py-default);
|
|
45
|
+
@include layout(flex, column, default, h-between);
|
|
46
|
+
|
|
47
|
+
span {
|
|
48
|
+
@include typography(md, primary-20);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@include media('sm') {
|
|
53
|
+
.wrapper,
|
|
54
|
+
.subtext {
|
|
55
|
+
@include layout(row, none);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.columns {
|
|
59
|
+
@include layout(row, '5xl');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ButtonProps } from '../Button/button'
|
|
2
|
+
|
|
3
|
+
export type FooterProps = {
|
|
4
|
+
logo?: {
|
|
5
|
+
url?: string
|
|
6
|
+
alt?: string
|
|
7
|
+
width?: number
|
|
8
|
+
height?: number
|
|
9
|
+
eager?: boolean
|
|
10
|
+
html?: string
|
|
11
|
+
} | null
|
|
12
|
+
columns?: {
|
|
13
|
+
title?: string
|
|
14
|
+
items: {
|
|
15
|
+
href: string
|
|
16
|
+
name: string
|
|
17
|
+
target?: ButtonProps['target']
|
|
18
|
+
active?: boolean
|
|
19
|
+
}[]
|
|
20
|
+
}[]
|
|
21
|
+
subText?: string
|
|
22
|
+
className?: string
|
|
23
|
+
wrapperClassName?: string
|
|
24
|
+
subTextClassName?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ReactFooterProps = {
|
|
28
|
+
children?: React.ReactNode
|
|
29
|
+
} & FooterProps
|
package/components/Icon/icon.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
export type IconProps = {
|
|
2
|
-
type:
|
|
2
|
+
type: 'alert'
|
|
3
|
+
| 'arrow-down'
|
|
4
|
+
| 'arrow-left'
|
|
5
|
+
| 'arrow-right'
|
|
6
|
+
| 'check'
|
|
7
|
+
| 'circle-check'
|
|
8
|
+
| 'circle-close'
|
|
9
|
+
| 'close'
|
|
10
|
+
| 'components'
|
|
11
|
+
| 'file'
|
|
12
|
+
| 'github'
|
|
13
|
+
| 'home'
|
|
14
|
+
| 'info'
|
|
15
|
+
| 'moon'
|
|
16
|
+
| 'order'
|
|
17
|
+
| 'search'
|
|
18
|
+
| 'sun'
|
|
19
|
+
| 'warning'
|
|
3
20
|
size?: number
|
|
4
21
|
color?: string
|
|
5
22
|
}
|
package/components/Icon/map.ts
CHANGED
|
@@ -4,8 +4,12 @@ import ArrowLeft from '../../icons/arrow-left.svg?raw'
|
|
|
4
4
|
import ArrowRight from '../../icons/arrow-right.svg?raw'
|
|
5
5
|
import Check from '../../icons/check.svg?raw'
|
|
6
6
|
import CircleCheck from '../../icons/circle-check.svg?raw'
|
|
7
|
+
import CircleClose from '../../icons/circle-close.svg?raw'
|
|
7
8
|
import Close from '../../icons/close.svg?raw'
|
|
9
|
+
import Components from '../../icons/components.svg?raw'
|
|
10
|
+
import File from '../../icons/file.svg?raw'
|
|
8
11
|
import Github from '../../icons/github.svg?raw'
|
|
12
|
+
import Home from '../../icons/home.svg?raw'
|
|
9
13
|
import Info from '../../icons/info.svg?raw'
|
|
10
14
|
import Moon from '../../icons/moon.svg?raw'
|
|
11
15
|
import Order from '../../icons/order.svg?raw'
|
|
@@ -20,8 +24,12 @@ const iconMap = {
|
|
|
20
24
|
'arrow-right': ArrowRight,
|
|
21
25
|
'check': Check,
|
|
22
26
|
'circle-check': CircleCheck,
|
|
27
|
+
'circle-close': CircleClose,
|
|
23
28
|
'close': Close,
|
|
29
|
+
'components': Components,
|
|
30
|
+
'file': File,
|
|
24
31
|
'github': Github,
|
|
32
|
+
'home': Home,
|
|
25
33
|
'info': Info,
|
|
26
34
|
'moon': Moon,
|
|
27
35
|
'order': Order,
|
package/components/List/list.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ButtonProps } from '../Button/button'
|
|
2
|
+
|
|
1
3
|
export type ListEventType = {
|
|
2
4
|
value: string
|
|
3
5
|
name: string
|
|
@@ -19,7 +21,7 @@ export type ListProps = {
|
|
|
19
21
|
name: string
|
|
20
22
|
value?: string
|
|
21
23
|
href?: string
|
|
22
|
-
target?: '
|
|
24
|
+
target?: ButtonProps['target']
|
|
23
25
|
selected?: boolean
|
|
24
26
|
disabled?: boolean
|
|
25
27
|
icon?: string
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { MasonryProps } from './masonry'
|
|
3
|
+
|
|
4
|
+
import styles from './masonry.module.scss'
|
|
5
|
+
|
|
6
|
+
interface Props extends MasonryProps {}
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
items,
|
|
10
|
+
element = 'section',
|
|
11
|
+
gap,
|
|
12
|
+
columns = 3,
|
|
13
|
+
sequential,
|
|
14
|
+
className
|
|
15
|
+
} = Astro.props
|
|
16
|
+
|
|
17
|
+
const classes = [
|
|
18
|
+
styles.masonry,
|
|
19
|
+
className
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const Component = element
|
|
23
|
+
|
|
24
|
+
const componentProps = {
|
|
25
|
+
'class:list': classes,
|
|
26
|
+
'style': gap ? `--w-masonry-gap: ${gap};` : null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const chunkSize = Math.ceil(items.length / columns)
|
|
30
|
+
const columnGroups = Array.from({ length: columns }, (_, i) => {
|
|
31
|
+
return sequential
|
|
32
|
+
? items.slice(i * chunkSize, (i + 1) * chunkSize)
|
|
33
|
+
: items.filter((_, index) => index % columns === i)
|
|
34
|
+
})
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
<Component {...componentProps}>
|
|
38
|
+
{columnGroups.map(column => (
|
|
39
|
+
<div class={styles.column}>
|
|
40
|
+
{column.map(item => (
|
|
41
|
+
item.component
|
|
42
|
+
? <item.component {...item.props}>
|
|
43
|
+
{typeof item.children === 'object'
|
|
44
|
+
? <item.children.component {...item.children.props}>
|
|
45
|
+
<Fragment set:html={item.children.children} />
|
|
46
|
+
</item.children.component>
|
|
47
|
+
: <Fragment set:html={item.children} />
|
|
48
|
+
}
|
|
49
|
+
</item.component>
|
|
50
|
+
: <Fragment set:html={item.html} />
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</Component>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SvelteMasonryProps } from './masonry'
|
|
3
|
+
|
|
4
|
+
import { classNames } from '../../utils/classNames'
|
|
5
|
+
|
|
6
|
+
import styles from './masonry.module.scss'
|
|
7
|
+
|
|
8
|
+
export let items: SvelteMasonryProps['items'] = []
|
|
9
|
+
export let element: SvelteMasonryProps['element'] = 'section'
|
|
10
|
+
export let gap: SvelteMasonryProps['gap'] = ''
|
|
11
|
+
export let columns: SvelteMasonryProps['columns'] = 3
|
|
12
|
+
export let sequential: SvelteMasonryProps['sequential'] = false
|
|
13
|
+
export let className: SvelteMasonryProps['className'] = ''
|
|
14
|
+
|
|
15
|
+
const classes = classNames([
|
|
16
|
+
styles.masonry,
|
|
17
|
+
className
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const componentProps = {
|
|
21
|
+
class: classes,
|
|
22
|
+
style: gap ? `--w-masonry-gap: ${gap};` : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const chunkSize = Math.ceil(items.length / columns!)
|
|
26
|
+
const columnGroups = Array.from({ length: columns! }, (_, i) => {
|
|
27
|
+
return sequential
|
|
28
|
+
? items.slice(i * chunkSize, (i + 1) * chunkSize)
|
|
29
|
+
: items.filter((_, index) => index % columns! === i)
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
<svelte:element this={element} {...componentProps}>
|
|
35
|
+
{#each columnGroups as group}
|
|
36
|
+
<div class={styles.column}>
|
|
37
|
+
{#each group as item}
|
|
38
|
+
{#if item.component}
|
|
39
|
+
<svelte:component this={item.component} {...item.props}>
|
|
40
|
+
{#if typeof item.children === 'object' && item.children.component}
|
|
41
|
+
<svelte:component this={item.children.component} {...item.children.props}>
|
|
42
|
+
{@html item.children.children}
|
|
43
|
+
</svelte:component>
|
|
44
|
+
{:else}
|
|
45
|
+
{@html item.children}
|
|
46
|
+
{/if}
|
|
47
|
+
</svelte:component>
|
|
48
|
+
{:else}
|
|
49
|
+
{@html item.html}
|
|
50
|
+
{/if}
|
|
51
|
+
{/each}
|
|
52
|
+
</div>
|
|
53
|
+
{/each}
|
|
54
|
+
</svelte:element>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { ReactMasonryProps } from './masonry'
|
|
3
|
+
|
|
4
|
+
import { classNames } from '../../utils/classNames'
|
|
5
|
+
|
|
6
|
+
import styles from './masonry.module.scss'
|
|
7
|
+
|
|
8
|
+
const Masonry = ({
|
|
9
|
+
items,
|
|
10
|
+
element = 'section',
|
|
11
|
+
gap,
|
|
12
|
+
columns = 3,
|
|
13
|
+
sequential,
|
|
14
|
+
className
|
|
15
|
+
}: ReactMasonryProps) => {
|
|
16
|
+
const classes = classNames([
|
|
17
|
+
styles.masonry,
|
|
18
|
+
className
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
const componentProps = {
|
|
22
|
+
className: classes,
|
|
23
|
+
style: gap
|
|
24
|
+
? { '--w-masonry-gap': gap } as React.CSSProperties
|
|
25
|
+
: undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const chunkSize = Math.ceil(items.length / columns!)
|
|
29
|
+
const columnGroups = Array.from({ length: columns! }, (_, i) => {
|
|
30
|
+
return sequential
|
|
31
|
+
? items.slice(i * chunkSize, (i + 1) * chunkSize)
|
|
32
|
+
: items.filter((_, index) => index % columns! === i)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const Element = element as keyof JSX.IntrinsicElements
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Element {...componentProps}>
|
|
39
|
+
{columnGroups.map((column, columnKey) => (
|
|
40
|
+
<div className={styles.column} key={columnKey}>
|
|
41
|
+
{column.map((item, itemKey) => (
|
|
42
|
+
item.component
|
|
43
|
+
? <item.component {...item.props} key={itemKey}>
|
|
44
|
+
{typeof item.children === 'object'
|
|
45
|
+
? <item.children.component {...item.children.props}>
|
|
46
|
+
<span dangerouslySetInnerHTML={{ __html: String(item.children.children) }} />
|
|
47
|
+
</item.children.component>
|
|
48
|
+
: <span dangerouslySetInnerHTML={{ __html: String(item.children) }} />
|
|
49
|
+
}
|
|
50
|
+
</item.component>
|
|
51
|
+
: <span
|
|
52
|
+
key={itemKey}
|
|
53
|
+
dangerouslySetInnerHTML={{ __html: String(item.html) }}
|
|
54
|
+
/>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
))}
|
|
58
|
+
</Element>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default Masonry
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@import '../../scss/config.scss';
|
|
2
|
+
|
|
3
|
+
body {
|
|
4
|
+
--w-masonry-gap: 5px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.masonry {
|
|
8
|
+
@include layout(flex);
|
|
9
|
+
|
|
10
|
+
gap: var(--w-masonry-gap);
|
|
11
|
+
overflow-x: auto;
|
|
12
|
+
|
|
13
|
+
.column {
|
|
14
|
+
@include layout(flex, column);
|
|
15
|
+
|
|
16
|
+
gap: var(--w-masonry-gap);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import type { SvelteComponent } from 'svelte'
|
|
3
|
+
|
|
4
|
+
type ChildrenProps<ComponentType> = {
|
|
5
|
+
component: ComponentType
|
|
6
|
+
children?: string | number
|
|
7
|
+
props?: {
|
|
8
|
+
[key: string]: any
|
|
9
|
+
}
|
|
10
|
+
} | string | number
|
|
11
|
+
|
|
12
|
+
type Items<ComponentType> = {
|
|
13
|
+
component?: ComponentType;
|
|
14
|
+
children?: ChildrenProps<ComponentType>
|
|
15
|
+
html?: string
|
|
16
|
+
props?: {
|
|
17
|
+
[key: string]: any
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type MasonryProps = {
|
|
22
|
+
items: Items<(_props: any) => any>[]
|
|
23
|
+
element?: string
|
|
24
|
+
gap?: string
|
|
25
|
+
columns?: number
|
|
26
|
+
sequential?: boolean
|
|
27
|
+
className?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type SvelteMasonryProps = {
|
|
31
|
+
items: Items<typeof SvelteComponent<any>>[]
|
|
32
|
+
} & Omit<MasonryProps, 'items'>
|
|
33
|
+
|
|
34
|
+
export type ReactMasonryProps = {
|
|
35
|
+
items: Items<FC<any>>[]
|
|
36
|
+
} & Omit<MasonryProps, 'items'>
|