From a6776533a6bed36b2352a91d0820628071e137cd Mon Sep 17 00:00:00 2001 From: Frontendland Date: Thu, 3 Oct 2024 09:13:18 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20Masonry=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + scripts/buildTypes.js | 3 +- src/components/Masonry/Masonry.astro | 54 ++++++++++++ src/components/Masonry/Masonry.svelte | 54 ++++++++++++ src/components/Masonry/Masonry.tsx | 62 ++++++++++++++ src/components/Masonry/masonry.module.scss | 18 ++++ src/components/Masonry/masonry.ts | 36 ++++++++ src/data.ts | 45 ++++++++++ src/pages/masonry.astro | 99 ++++++++++++++++++++++ src/static/Box.astro | 17 +++- src/static/Box.svelte | 13 ++- src/static/Box.tsx | 13 ++- 12 files changed, 412 insertions(+), 6 deletions(-) create mode 100644 src/components/Masonry/Masonry.astro create mode 100644 src/components/Masonry/Masonry.svelte create mode 100644 src/components/Masonry/Masonry.tsx create mode 100644 src/components/Masonry/masonry.module.scss create mode 100644 src/components/Masonry/masonry.ts create mode 100644 src/pages/masonry.astro diff --git a/README.md b/README.md index a4321ce..02c9c85 100644 --- a/README.md +++ b/README.md @@ -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); @@ -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) diff --git a/scripts/buildTypes.js b/scripts/buildTypes.js index f75a1e2..cb46646 100644 --- a/scripts/buildTypes.js +++ b/scripts/buildTypes.js @@ -31,6 +31,7 @@ const buildTypes = type => { 'DataTable', 'Input', 'List', + 'Masonry', 'Pagination', 'Radio', 'Select', @@ -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')} diff --git a/src/components/Masonry/Masonry.astro b/src/components/Masonry/Masonry.astro new file mode 100644 index 0000000..a4847f0 --- /dev/null +++ b/src/components/Masonry/Masonry.astro @@ -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) +}) +--- + + + {columnGroups.map(column => ( +
+ {column.map(item => ( + item.component + ? + {typeof item.children === 'object' + ? + + + : + } + + : + ))} +
+ ))} +
diff --git a/src/components/Masonry/Masonry.svelte b/src/components/Masonry/Masonry.svelte new file mode 100644 index 0000000..56d882d --- /dev/null +++ b/src/components/Masonry/Masonry.svelte @@ -0,0 +1,54 @@ + + + + + {#each columnGroups as group} +
+ {#each group as item} + {#if item.component} + + {#if typeof item.children === 'object' && item.children.component} + + {@html item.children.children} + + {:else} + {@html item.children} + {/if} + + {:else} + {@html item.html} + {/if} + {/each} +
+ {/each} +
diff --git a/src/components/Masonry/Masonry.tsx b/src/components/Masonry/Masonry.tsx new file mode 100644 index 0000000..e236233 --- /dev/null +++ b/src/components/Masonry/Masonry.tsx @@ -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 ( + + {columnGroups.map((column, columnKey) => ( +
+ {column.map((item, itemKey) => ( + item.component + ? + {typeof item.children === 'object' + ? + + + : + } + + : + ))} +
+ ))} +
+ ) +} + +export default Masonry diff --git a/src/components/Masonry/masonry.module.scss b/src/components/Masonry/masonry.module.scss new file mode 100644 index 0000000..1750121 --- /dev/null +++ b/src/components/Masonry/masonry.module.scss @@ -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); + } +} diff --git a/src/components/Masonry/masonry.ts b/src/components/Masonry/masonry.ts new file mode 100644 index 0000000..41aa843 --- /dev/null +++ b/src/components/Masonry/masonry.ts @@ -0,0 +1,36 @@ +import type { FC } from 'react' +import type { SvelteComponent } from 'svelte' + +type ChildrenProps = { + component: ComponentType; + children?: string | number + props?: { + [key: string]: any + } +} | string | number + +type Items = { + component?: ComponentType; + children?: ChildrenProps + 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>[] +} & Omit + +export type ReactMasonryProps = { + items: Items>[] +} & Omit diff --git a/src/data.ts b/src/data.ts index 89bd897..d05d437 100644 --- a/src/data.ts +++ b/src/data.ts @@ -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' @@ -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 +})) diff --git a/src/pages/masonry.astro b/src/pages/masonry.astro new file mode 100644 index 0000000..131d899 --- /dev/null +++ b/src/pages/masonry.astro @@ -0,0 +1,99 @@ +--- +import ComponentWrapper from '@static/ComponentWrapper.astro' +import Layout from '@static/Layout.astro' + +import AstroMasonry from '@components/Masonry/Masonry.astro' +import SvelteMasonry from '@components/Masonry/Masonry.svelte' +import ReactMasonry from '@components/Masonry/Masonry.tsx' + +import { getSections } from '@helpers' +import { + masonryComponentItems, + masonryItems, + reactMasonryComponentItems, + reactMasonryItems, + svelteMasonryComponentItems, + svelteMasonryItems +} from '@data' + +const sections = getSections({ + title: 'masonrys', + components: [AstroMasonry, SvelteMasonry, ReactMasonry] +}) + +const masonryItemsMap: any = { + astro: masonryItems, + svelte: svelteMasonryItems, + react: reactMasonryItems +} + +const masonryComponentItemsMap: any = { + astro: masonryComponentItems, + svelte: svelteMasonryComponentItems, + react: reactMasonryComponentItems +} +--- + + +

Masonry

+
+ + + + + + + + + + + +
+ + {sections.map(section => ( +

{section.title}

+ + {section.subTitle &&

} + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ ))} + + diff --git a/src/static/Box.astro b/src/static/Box.astro index c5a7b11..113d7c5 100644 --- a/src/static/Box.astro +++ b/src/static/Box.astro @@ -2,13 +2,26 @@ import styles from './box.module.scss' const { - fullWidth = false + fullWidth = false, + width, + height } = Astro.props const classes = [ styles.box, fullWidth && styles.full ] + +const dimensions: any = { + width, + height +} + +const cssStyles = Object.keys(dimensions) + .filter(key => dimensions[key]) + .map(key => `${key}:${dimensions[key]}px`).join(';') --- -
+
+ +
diff --git a/src/static/Box.svelte b/src/static/Box.svelte index 6c53847..a98ef96 100644 --- a/src/static/Box.svelte +++ b/src/static/Box.svelte @@ -4,11 +4,22 @@ import styles from './box.module.scss' export let fullWidth = false + export let width = 0 + export let height = 0 const classes = classNames([ styles.box, fullWidth && styles.full ]) + + const dimensions = { + width, + height + } + + const cssStyles = Object.keys(dimensions) + .filter(key => dimensions[key]) + .map(key => `${key}:${dimensions[key]}px`).join(';') -
+
diff --git a/src/static/Box.tsx b/src/static/Box.tsx index d1ae3a2..ded5f4b 100644 --- a/src/static/Box.tsx +++ b/src/static/Box.tsx @@ -7,19 +7,28 @@ import styles from './box.module.scss' type BoxProps = { fullWidth: boolean children: React.ReactNode + width?: number + height?: number } const Box = ({ fullWidth = false, - children + children, + width, + height }: BoxProps) => { const classes = classNames([ styles.box, fullWidth && styles.full ]) + const dimensions: React.CSSProperties = { + width, + height + } + return ( -
{children}
+
{children}
) }