Skip to content

Commit

Permalink
✨ Add Masonry component
Browse files Browse the repository at this point in the history
  • Loading branch information
Frontendland committed Oct 3, 2024
1 parent 49d8ef4 commit a677653
Show file tree
Hide file tree
Showing 12 changed files with 412 additions and 6 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ html body {
--w-collapsible-initial-height: 0;
--w-collapsible-max-height: 100%;

// Masonry component
--w-masonry-gap: 5px;

// Progress component
--w-progress-color: var(--w-color-primary);
--w-progress-background: var(--w-color-primary-50);
Expand Down Expand Up @@ -211,6 +214,7 @@ import { Accordion } from 'webcoreui/react'
- [Icon](https://github.com/Frontendland/webcoreui/tree/main/src/components/Icon)
- [Input](https://github.com/Frontendland/webcoreui/tree/main/src/components/Input)
- [List](https://github.com/Frontendland/webcoreui/tree/main/src/components/List)
- [Masonry](https://github.com/Frontendland/webcoreui/tree/main/src/components/Masonry)
- [Menu](https://github.com/Frontendland/webcoreui/tree/main/src/components/Menu)
- [Modal](https://github.com/Frontendland/webcoreui/tree/main/src/components/Modal)
- [Pagination](https://github.com/Frontendland/webcoreui/tree/main/src/components/Pagination)
Expand Down
3 changes: 2 additions & 1 deletion scripts/buildTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const buildTypes = type => {
'DataTable',
'Input',
'List',
'Masonry',
'Pagination',
'Radio',
'Select',
Expand All @@ -45,7 +46,7 @@ const buildTypes = type => {
}

return format(`
import { SvelteComponent } from 'svelte'
import type { SvelteComponent } from 'svelte'
${components.map(component => {
return `import type { ${getTypeName(component)} } from './components/${component}/${component.toLowerCase()}'`
}).join('\n')}
Expand Down
54 changes: 54 additions & 0 deletions src/components/Masonry/Masonry.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
import type { MasonryProps } from './masonry'
import styles from './masonry.module.scss'
interface Props extends MasonryProps {}
const {
items,
element = 'section',
gap,
columns = 3,
sequential,
className
} = Astro.props
const classes = [
styles.masonry,
className
]
const Component = element
const componentProps = {
'class:list': classes,
'style': gap ? `--w-masonry-gap: ${gap};` : null
}
const chunkSize = Math.ceil(items.length / columns)
const columnGroups = Array.from({ length: columns }, (_, i) => {
return sequential
? items.slice(i * chunkSize, (i + 1) * chunkSize)
: items.filter((_, index) => index % columns === i)
})
---

<Component {...componentProps}>
{columnGroups.map(column => (
<div class={styles.column}>
{column.map(item => (
item.component
? <item.component {...item.props}>
{typeof item.children === 'object'
? <item.children.component {...item.children.props}>
<Fragment set:html={item.children.children} />
</item.children.component>
: <Fragment set:html={item.children} />
}
</item.component>
: <Fragment set:html={item.html} />
))}
</div>
))}
</Component>
54 changes: 54 additions & 0 deletions src/components/Masonry/Masonry.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script lang="ts">
import type { SvelteMasonryProps } from './masonry'
import { classNames } from '../../utils/classNames'
import styles from './masonry.module.scss'
export let items: SvelteMasonryProps['items'] = []
export let element: SvelteMasonryProps['element'] = 'section'
export let gap: SvelteMasonryProps['gap'] = ''
export let columns: SvelteMasonryProps['columns'] = 3
export let sequential: SvelteMasonryProps['sequential'] = false
export let className: SvelteMasonryProps['className'] = ''
const classes = classNames([
styles.masonry,
className
])
const componentProps = {
class: classes,
style: gap ? `--w-masonry-gap: ${gap};` : null
}
const chunkSize = Math.ceil(items.length / columns!)
const columnGroups = Array.from({ length: columns! }, (_, i) => {
return sequential
? items.slice(i * chunkSize, (i + 1) * chunkSize)
: items.filter((_, index) => index % columns! === i)
})
</script>


<svelte:element this={element} {...componentProps}>
{#each columnGroups as group}
<div class={styles.column}>
{#each group as item}
{#if item.component}
<svelte:component this={item.component} {...item.props}>
{#if typeof item.children === 'object' && item.children.component}
<svelte:component this={item.children.component} {...item.children.props}>
{@html item.children.children}
</svelte:component>
{:else}
{@html item.children}
{/if}
</svelte:component>
{:else}
{@html item.html}
{/if}
{/each}
</div>
{/each}
</svelte:element>
62 changes: 62 additions & 0 deletions src/components/Masonry/Masonry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react'
import type { ReactMasonryProps } from './masonry'

import { classNames } from '../../utils/classNames'

import styles from './masonry.module.scss'

const Masonry = ({
items,
element = 'section',
gap,
columns = 3,
sequential,
className
}: ReactMasonryProps) => {
const classes = classNames([
styles.masonry,
className
])

const componentProps = {
className: classes,
style: gap
? { '--w-masonry-gap': gap } as React.CSSProperties
: undefined
}

const chunkSize = Math.ceil(items.length / columns!)
const columnGroups = Array.from({ length: columns! }, (_, i) => {
return sequential
? items.slice(i * chunkSize, (i + 1) * chunkSize)
: items.filter((_, index) => index % columns! === i)
})

const Element = element as keyof JSX.IntrinsicElements

return (
<Element {...componentProps}>
{columnGroups.map((column, columnKey) => (
<div className={styles.column} key={columnKey}>
{column.map((item, itemKey) => (
item.component
? <item.component {...item.props} key={itemKey}>
{typeof item.children === 'object'
? <item.children.component {...item.children.props}>
<span dangerouslySetInnerHTML={{ __html: String(item.children.children) }} />
</item.children.component>
: <span dangerouslySetInnerHTML={{ __html: String(item.children) }} />
}
</item.component>
: <span
key={itemKey}
dangerouslySetInnerHTML={{ __html: String(item.html) }}
/>
))}
</div>
))}
</Element>
)
}

export default Masonry
18 changes: 18 additions & 0 deletions src/components/Masonry/masonry.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@import '../../scss/config.scss';

body {
--w-masonry-gap: 5px;
}

.masonry {
@include layout(flex);

gap: var(--w-masonry-gap);
overflow-x: auto;

.column {
@include layout(flex, column);

gap: var(--w-masonry-gap);
}
}
36 changes: 36 additions & 0 deletions src/components/Masonry/masonry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { FC } from 'react'
import type { SvelteComponent } from 'svelte'

type ChildrenProps<ComponentType> = {
component: ComponentType;
children?: string | number
props?: {
[key: string]: any
}
} | string | number

type Items<ComponentType> = {
component?: ComponentType;
children?: ChildrenProps<ComponentType>
html?: string
props?: {
[key: string]: any
}
}

export type MasonryProps = {
items: Items<(_props: any) => any>[]
element?: string
gap?: string
columns?: number
sequential?: boolean
className?: string
}

export type SvelteMasonryProps = {
items: Items<typeof SvelteComponent<any>>[]
} & Omit<MasonryProps, 'items'>

export type ReactMasonryProps = {
items: Items<FC<any>>[]
} & Omit<MasonryProps, 'items'>
45 changes: 45 additions & 0 deletions src/data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/* eslint-disable max-lines */
import type { ButtonProps } from '@components/Button/button'

import Alert from '@components/Alert/Alert.astro'
import Box from '@static/Box.astro'
import SvelteAlert from '@components/Alert/Alert.svelte'
import SvelteBox from '@static/Box.svelte'
import ReactAlert from '@components/Alert/Alert.tsx'
import ReactBox from '@static/Box.tsx'

import successIcon from './icons/circle-check.svg?raw'
import componentsIcon from './icons/components.svg?raw'
import fileIcon from './icons/file.svg?raw'
Expand Down Expand Up @@ -327,3 +334,41 @@ export const itemGroupsWithIcons = [
items: itemGroup2[0].items
}
]

export const masonryItems = [
{ component: Box, props: { height: 100 }, children: 1 },
{ component: Box, props: { height: 50 }, children: 2 },
{ component: Box, props: { height: 70 }, children: 3 },
{ component: Box, props: { height: 90 }, children: 4 },
{ component: Box, props: { height: 140 }, children: 5 },
{ component: Box, props: { height: 120 }, children: 6 }
]

export const masonryComponentItems = [
{ component: Alert, children: 1 },
{ component: Alert, props: { theme: 'info' }, children: 2 },
{ component: Alert, children: 3 },
{ component: Alert, props: { theme: 'success' }, children: 4 },
{ component: Alert, props: { height: 140 }, children: 5 },
{ component: Alert, props: { theme: 'warning' }, children: 6 }
]

export const svelteMasonryItems = masonryItems.map(item => ({
...item,
component: SvelteBox
}))

export const svelteMasonryComponentItems = masonryComponentItems.map(item => ({
...item,
component: SvelteAlert
}))

export const reactMasonryItems = masonryItems.map(item => ({
...item,
component: ReactBox
}))

export const reactMasonryComponentItems = masonryComponentItems.map(item => ({
...item,
component: ReactAlert
}))
Loading

0 comments on commit a677653

Please sign in to comment.