hand JSBloqs
hand GutenBloqs
hand kuworking
What?
nochedía
Masonry
By kuworking
import React, { useRef, useState, useEffect } from 'react'
import styled from '@emotion/styled'

const items = [...Array(30)].map((_, i) => ({
  name: 'Elemento',
  categories: `category_${parseInt(i / 5)}`,
  link: 'https://www.kuworking.com',
  image: `https://source.unsplash.com/random/250x175?sig=${i}`,
}))

export default () => {
  const masonryProps = {
    columns: [['(min-width: 1000px)', '(min-width: 800px)', '(min-width: 600px)'], [5, 4, 3], 2],
    categories: [...new Set(items.map(el => el.categories))],
    elements: items,
  }

  return (
    <>
      <link href="https://fonts.googleapis.com/css2?family=Text+Me+One&display=swap" rel="stylesheet"></link>
      <link href="/20201011_iframe.css" type="text/css" rel="stylesheet" />
      <Main id="bloq">
        <Container>
          <Title>MASONRY</Title>
          <Div>
            <CssMasonry {...masonryProps}>
              {el => (
                <a href={el.link} target="_blank" rel="noopener noreferrer">
                  <div>
                    <img src={el.image} />
                    <div>{el.name}</div>
                    <div>{el.categories}</div>
                  </div>
                </a>
              )}
            </CssMasonry>
          </Div>
        </Container>
      </Main>
    </>
  )
}

const q = px => `@media (min-width: ${px}px)`

const Main = styled.main`
  font-family: 'Text Me One', sans-serif;
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: center;
  transition: font-size 0.5s ease;
`

const Container = styled.div`
  max-width: 1200px;
  width: 100%;
  padding: 0px 10px;

  display: flex;
  flex-direction: column;
`

const Div = styled.div`
  & .cssmasonry_categories {
    margin: 20px 0px;
    display: flex;
    flex-wrap: wrap;

    & > span {
      padding: 10px;
      background: #d5d6c4;
      color: #000;
      border-radius: 8px;

      margin: 5px;
      ${q(700)} {
        margin: 10px;
      }

      font-weight: 700;
      cursor: pointer;
      transition: background 0.2s ease;
      &:hover {
        background: #666;
        color: #fff;
      }
    }
  }

  & .cssmasonry_masonry {
    position: relative;
    align-self: center;
    width: 100%;
    height: 100%;

    & > div {
      transition: transform 0.2s cubic-bezier(0.05, 0.9, 0.69, 0.96);
    }

    & .cssmasonry_masonry_element {
      position: absolute;
      will-change: transform;
      /* This cannot have any padding here, otherwise the gap gets screwed */

      & > div {
        padding: 10px;

        & > a {
          display: block;
          background: #ecece4;
          border-radius: 8px;
          padding: 10px;
          transition: all 0.2s ease;

          &:hover {
            background: linear-gradient(45deg, #ecece4, #add1ea);

            & img {
              filter: brightness(1.2);
            }
          }
        }
      }

      & img {
        width: 100%;
        height: 100%;
        border-radius: 8px;
        padding: 10px;
        background: #fff;
      }
    }
  }

  width: 100%;
  margin-bottom: 100px;
`

const Title = styled.h1`
  font-family: 'Text Me One', sans-serif;
  font-size: 6rem;
  margin-top: 100px;
`

/** -----------------------------------------  */
/** -----------------------------------------  */

/**
 * Returns a number of columns (defined in values) based on the provided media queries
 */
export const CssMasonry = ({
  columns: query,
  elements: initialElements,
  categories = null,
  firstCategory,
  categoriesComponents,
  children,
}) => {
  // get columns based on client width
  //  const columns = useMedia(['(min-width: 1000px)', '(min-width: 800px)', '(min-width: 600px)'], [5, 4, 3], 2)
  const columns = useMedia(...query)

  // get the ref (bind) to monitor the contentRect (here only interested in width) (this is the global container)
  const [bind, { width }] = useMeasure()

  // to show only specific categories
  const [elements, setElements] = useState(
    initialElements.map((post, i) => ({
      post: post,
      key: i,
      height: 0, // initial height
      currentWidth: 0,
      currentHeight: 0,
      currentRatio: 1,
      showCat: firstCategory ? post.categories.includes(firstCategory) : 1,
    }))
  )

  // define `showCat` depending of contents of `el.post.categories`
  const reSetElementsCat = cat => {
    setElements(
      elements.map((el, i) => {
        return {
          ...el,
          showCat: cat ? el.post.categories.includes(cat) : 1,
        }
      })
    )
  }

  // resets elements with
  const reSetElements = (image, h, w) => {
    const el = elements.find(el => el.key === image.key)
    el.currentHeight = parseInt(h)
    el.currentWidth = parseInt(w)
    el.currentRatio = w / h
    setElements([...elements])
  }

  // heights of the columns, change when a div is inserted in one of them
  const columnHeights = Array(columns).fill(0) // Each column gets a height starting with zero

  // Calculate x-y positions
  const transitions = elements.map(el => {
    if (!el.showCat) return { ...el, x: 0, y: 0, width: 0, height: 0 }
    const column = columnHeights.indexOf(Math.min(...columnHeights)) // Basic masonry-grid placing, puts tile into the smallest column using Math.min
    const elWidth = parseInt((width / columns) * column) // el ancho del elemento basado en la columna
    const elHeight = parseInt(width / columns / el.currentRatio) // la altura del elemento considerando su ratio de serie
    const x = elWidth // X = container width / number of columns * column index, Y = it's just the height of the current column
    const y = (columnHeights[column] += elHeight) - elHeight // X = container width / number of columns * column index, Y = it's just the height of the current column
    return { ...el, x, y, width: width / columns, height: el.height }
  })

  return (
    <div className="cssmasonry_grid">
      {categories && (
        <div className="cssmasonry_categories">
          {(categoriesComponents &&
            categoriesComponents.map(({ tag, cat }, i) => (
              <span key={`cat${i}`} onClick={() => reSetElementsCat(cat)}>
                {tag()}
              </span>
            ))) || (
            <>
              <span onClick={() => reSetElementsCat(null)}>inicio</span>
              {categories.map((el, i) => (
                <span key={`cat${i}`} onClick={() => reSetElementsCat(el)}>
                  {el}
                </span>
              ))}
            </>
          )}
        </div>
      )}
      <div className="cssmasonry_masonry" {...bind} style={{ height: Math.max(...columnHeights) }}>
        {transitions.map(t => {
          const { item, key, post, x, y, ...rest } = t

          /**
           * If the transform is sent to Element and there it is loacted with {...bind},
           * ..then somehow I never get the height of the container
           */
          return (
            <div key={`item${key}`} style={{ transform: `translate3d(${x}px,${y}px,0)`, ...rest }}>
              <Element image={t} reSetElements={reSetElements} children={children} />
            </div>
          )
        })}
      </div>
    </div>
  )
}

/**
 * Component for each element
 */
const Element = ({ image, reSetElements, style, children }) => {
  const { post } = image

  const [bind, { width, height }] = useMeasure()

  useEffect(() => {
    if (height !== 0 && width !== 0) reSetElements(image, height, width)
  }, [height, width])

  /**
   * I need this middle div because padding is influencing the "gap" and I need to isolate it
   */
  return (
    <div className="cssmasonry_masonry_element" style={{ display: image.showCat ? 'block' : 'none' }} {...bind}>
      <div>{children(post)}</div>
    </div>
  )
}

/**
 * Returns a number of columns (defined in values) based on the provided media queries
 */
export const useMedia = (queries, values, defaultValue) => {
  // matchMedia is a js equivalent to find if a given media matches the current document
  const match = () =>
    values[queries.findIndex(q => typeof window !== 'undefined' && matchMedia(q).matches)] || defaultValue
  const [value, set] = useState(match)

  useEffect(() => {
    const handler = () => set(match)
    window.addEventListener('resize', handler)
    return () => window.removeEventListener('resize', handler)
  }, [])
  return value
}

/**
 * Returns a ref to add to an element, and for this element returns the contentRect via ResizeObserver
 */
export const useMeasure = () => {
  const ref = useRef()
  const [bounds, set] = useState({ left: 0, top: 0, width: 0, height: 0 })
  const [ro] = useState(() => typeof window !== 'undefined' && new ResizeObserver(([entry]) => set(entry.contentRect)))
  useEffect(() => (ro.observe(ref.current), ro.disconnect), [])
  return [{ ref }, bounds]
}
Privacidad
by kuworking.com
[ 2020 >> kuworking ]