Ver Fonte

check in. has ts warnings

Jimmy Chion há 3 anos atrás
pai
commit
48177466fc

+ 3 - 1
.babelrc

@@ -1,4 +1,6 @@
 {
   "presets": ["next/babel"],
-  "plugins": [["styled-components", {"ssr": true}]]
+  "plugins": [
+    ["styled-components", {"ssr": true}]
+  ]
 }

Diff do ficheiro suprimidas por serem muito extensas
+ 259 - 44
package-lock.json


+ 12 - 6
package.json

@@ -1,5 +1,6 @@
 {
   "name": "grainy-gradients",
+  "author": "Jimmy Chion",
   "version": "0.1.0",
   "private": true,
   "scripts": {
@@ -9,6 +10,15 @@
     "lint": "next lint"
   },
   "dependencies": {
+    "antd": "^4.16.13",
+    "highlight.js": "^11.2.0",
+    "react": "17.0.2",
+    "react-color": "^2.19.3",
+    "react-dom": "17.0.2",
+    "react-simple-code-editor": "^0.11.0",
+    "zustand": "^3.5.9"
+  },
+  "devDependencies": {
     "@babel/core": "^7.15.0",
     "@types/node": "^16.6.2",
     "@types/react": "^17.0.19",
@@ -16,6 +26,8 @@
     "@typescript-eslint/eslint-plugin": "^4.29.2",
     "@typescript-eslint/parser": "^4.29.2",
     "babel-plugin-styled-components": "^1.13.2",
+    "eslint": "^7.32.0",
+    "eslint-config-next": "11.1.0",
     "eslint-plugin-import": "^2.24.1",
     "eslint-plugin-jsx-a11y": "^6.4.1",
     "eslint-plugin-prettier": "^3.4.0",
@@ -23,13 +35,7 @@
     "eslint-plugin-react-hooks": "^4.2.0",
     "next": "11.1.0",
     "prettier": "^2.3.2",
-    "react": "17.0.2",
-    "react-dom": "17.0.2",
     "styled-components": "^5.3.0",
     "typescript": "^4.3.5"
-  },
-  "devDependencies": {
-    "eslint": "^7.32.0",
-    "eslint-config-next": "11.1.0"
   }
 }

BIN
public/checkers.png


+ 1 - 0
public/noise.svg

@@ -0,0 +1 @@
+<svg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'><filter id='noiseFilter'><feTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(#noiseFilter)'/></svg>

+ 90 - 0
src/components/CodeBlocks/Css.tsx

@@ -0,0 +1,90 @@
+import { Form } from 'antd';
+import hljs from 'highlight.js/lib/core';
+import cssLang from 'highlight.js/lib/languages/css';
+import { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import shallow from 'zustand/shallow';
+import SliderInput from './SliderInput';
+import { Row, LeftCol, RightCol } from '~/components/layout';
+import { useInputStore } from '~/components/store';
+hljs.registerLanguage('css', cssLang);
+
+export const CssSection = () => {
+  const [cssProps, setCssProps] = useInputStore(
+    (state) => [state.cssProps, state.setCssProps],
+    shallow
+  );
+  const [contrast, setContrast] = useState(170);
+  const [brightness, setBrightness] = useState(1000);
+  const [angle, setAngle] = useState(20);
+  const [gradientType, setGradientType] = useState('linear-gradient');
+  const [color1, setColor1] = useState('blue');
+  const [color2, setColor2] = useState('transparent');
+
+  useEffect(() => {
+    setCssProps({
+      gradientType,
+      color1,
+      color2,
+      contrast,
+      brightness,
+      angle,
+    });
+  }, [setCssProps, contrast, brightness, angle, gradientType, color1, color2]);
+
+  const gradientCss = `/* css gradient: second layer */
+{
+  width: 250px;
+  height: 250px;
+  background: ${gradientType}(${angle}deg, ${color1}, ${color2}), url(/checkers.png);
+  /* filter: contrast(${contrast}%) brightness(${brightness}%); */
+}`;
+  return (
+    <Row>
+      <LeftCol>
+        <Form labelCol={{ span: 3 }}>
+          <SliderInput
+            label="contrast"
+            name="contrast"
+            min={0}
+            max={1000}
+            step={10}
+            onChange={(val: number) => setContrast(val)}
+            value={typeof contrast === 'number' ? contrast : 10}
+          />
+          <SliderInput
+            label="brightness"
+            name="brightness"
+            min={0}
+            max={5000}
+            step={50}
+            onChange={(val: number) => setBrightness(val)}
+            value={typeof brightness === 'number' ? brightness : 0}
+          />
+          <SliderInput
+            label="angle"
+            name="angle"
+            min={0}
+            max={360}
+            onChange={(val: number) => setAngle(val)}
+            value={typeof angle === 'number' ? angle : 0}
+          />
+        </Form>
+        <pre className="hljs">
+          <code
+            dangerouslySetInnerHTML={{
+              __html: hljs.highlight(gradientCss, { language: 'css' }).value,
+            }}
+          />
+        </pre>{' '}
+      </LeftCol>
+      <RightCol>
+        <Gradient css={gradientCss} />
+      </RightCol>
+    </Row>
+  );
+};
+
+const Gradient = styled.div`
+  ${(p) => p.css}
+`;

+ 60 - 0
src/components/CodeBlocks/Result.tsx

@@ -0,0 +1,60 @@
+import { Form } from 'antd';
+import hljs from 'highlight.js/lib/core';
+import cssLang from 'highlight.js/lib/languages/css';
+import { useState } from 'react';
+import styled from 'styled-components';
+import shallow from 'zustand/shallow';
+import SliderInput from './SliderInput';
+import { Row, LeftCol, RightCol } from '~/components/layout';
+import { useInputStore } from '~/components/store';
+hljs.registerLanguage('css', cssLang);
+
+const symbols = /[\r\n%#()<>?[\\\]^`{|}]/g;
+
+export const ResultSection = () => {
+  const [svgProps, cssProps] = useInputStore((state) => [state.svgProps, state.cssProps], shallow);
+  // const [contrast, setContrast] = useState(170);
+  // const [brightness, setBrightness] = useState(1000);
+
+  const { gradientType, angle, color1, color2, contrast, brightness } = cssProps;
+  const { size, baseFrequency, numOctaves } = svgProps;
+  const resultCssDisplay = `/* resulting css */
+{
+  width: 250px;
+  height: 250px;
+  background: ${gradientType}(${angle}deg, ${color1}, ${color2}), url(/that/noise.svg);
+  filter: contrast(${contrast}%) brightness(${brightness}%);
+}
+  `;
+  const svgString = `<svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'><filter id='noiseFilter'><feTurbulence type='fractalNoise' baseFrequency='${baseFrequency}' numOctaves='${numOctaves}' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(#noiseFilter)'/></svg>`;
+  const liveCss = `
+  width: 250px;
+  height: 250px;
+  background: ${gradientType}(${angle}deg, ${color1}, ${color2}), url("data:image/svg+xml,${svgString.replace(
+    symbols,
+    encodeURIComponent
+  )}");
+  filter: contrast(${contrast}%) brightness(${brightness}%);
+  `;
+  return (
+    <Row>
+      <LeftCol>
+        <Form labelCol={{ span: 3 }}></Form>
+        <pre className="hljs">
+          <code
+            dangerouslySetInnerHTML={{
+              __html: hljs.highlight(resultCssDisplay, { language: 'css' }).value,
+            }}
+          />
+        </pre>{' '}
+      </LeftCol>
+      <RightCol>
+        <Result css={liveCss} />
+      </RightCol>
+    </Row>
+  );
+};
+
+const Result = styled.div`
+  ${(p) => p.css}
+`;

+ 32 - 0
src/components/CodeBlocks/SliderInput.tsx

@@ -0,0 +1,32 @@
+import { Form, Slider, InputNumber } from 'antd';
+import styled from 'styled-components';
+
+const SliderInput = (props) => {
+  const { label, name, onChange, value, min, max, step } = props;
+  return (
+    <Form.Item label={label} name={name}>
+      <SliderAndInput>
+        <SliderContainer>
+          <Slider step={step} min={min} max={max} onChange={onChange} value={value} />
+        </SliderContainer>
+        <InputNumber
+          step={step}
+          style={{ margin: '0 16px' }}
+          min={min}
+          value={value}
+          onChange={onChange}
+        />
+      </SliderAndInput>
+    </Form.Item>
+  );
+};
+
+export default SliderInput;
+
+const SliderContainer = styled.div`
+  width: 50%;
+`;
+
+const SliderAndInput = styled.div`
+  display: flex;
+`;

+ 94 - 0
src/components/CodeBlocks/Svg.tsx

@@ -0,0 +1,94 @@
+import { Form } from 'antd';
+import hljs from 'highlight.js/lib/core';
+import xmlLang from 'highlight.js/lib/languages/xml';
+import { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import shallow from 'zustand/shallow';
+import SliderInput from './SliderInput';
+import { Row, LeftCol, RightCol } from '~/components/layout';
+import { useInputStore } from '~/components/store';
+hljs.registerLanguage('xml', xmlLang);
+
+const symbols = /[\r\n%#()<>?[\\\]^`{|}]/g;
+
+export const SvgSection = () => {
+  const [setSvgProps] = useInputStore((state) => [state.setSvgProps], shallow);
+
+  const [size, setSize] = useState(200);
+  const [baseFrequency, setBaseFrequency] = useState(0.65);
+  const [numOctaves, setNumOctaves] = useState(3);
+
+  useEffect(() => {
+    setSvgProps({ size, baseFrequency, numOctaves });
+  }, [size, baseFrequency, numOctaves, setSvgProps]);
+
+  const svgString = `<!-- svg: first layer -->
+<svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'>
+  <filter id='noiseFilter'>
+    <feTurbulence 
+      type='fractalNoise' 
+      baseFrequency='${baseFrequency}' 
+      numOctaves='${numOctaves}' 
+      stitchTiles='stitch'/>
+  </filter>
+  
+  <rect width='100%' height='100%' filter='url(#noiseFilter)'/>
+</svg>`;
+
+  return (
+    <Row>
+      <LeftCol>
+        <Form labelCol={{ span: 3 }}>
+          <SliderInput
+            label="SVG size"
+            name="size"
+            min={1}
+            max={400}
+            onChange={(val) => setSize(val)}
+            value={typeof size === 'number' ? size : 1}
+          />
+          <SliderInput
+            label="baseFrequency"
+            name="baseFrequency"
+            min={0}
+            max={10}
+            step={0.01}
+            onChange={(val) => setBaseFrequency(val)}
+            value={typeof baseFrequency === 'number' ? baseFrequency : 1}
+          />
+          <SliderInput
+            label="numOctaves"
+            name="numOctaves"
+            min={0}
+            max={6}
+            onChange={(val) => setNumOctaves(val)}
+            value={typeof numOctaves === 'number' ? numOctaves : 1}
+          />
+        </Form>
+
+        <pre className="hljs">
+          <code
+            dangerouslySetInnerHTML={{
+              __html: hljs.highlight(svgString, { language: 'xml' }).value,
+            }}
+          />
+        </pre>
+      </LeftCol>
+      <RightCol>
+        <Noise size={size} code={svgString} />
+      </RightCol>
+    </Row>
+  );
+};
+
+type NoiseProps = {
+  noiseWidth: number;
+  noiseHeight: number;
+};
+
+// prettier-ignore
+const Noise = styled.div<NoiseProps>`
+  width: ${p=>p.size}px;
+  height: ${p=>p.size}px;
+  background: url("data:image/svg+xml,${(p) => p.code.replace(symbols, encodeURIComponent)}");
+`;

+ 3 - 0
src/components/CodeBlocks/index.tsx

@@ -0,0 +1,3 @@
+export * from './Svg';
+export * from './Css';
+export * from './Result';

+ 112 - 0
src/components/CopyToClipboard/index.tsx

@@ -0,0 +1,112 @@
+import { Tooltip } from 'antd'
+import { TooltipPlacement } from 'antd/lib/tooltip'
+import React, { SyntheticEvent } from 'react'
+
+interface ICopyToClipboard {
+  textToCopy: string
+  timeoutInMs?: number
+  initialText?: string | null
+  successText?: string
+  placement?: TooltipPlacement
+}
+
+const CopyToClipboard: React.FC<ICopyToClipboard> = ({
+  children,
+  textToCopy,
+  timeoutInMs = 2000,
+  successText = 'Copied to clipboard!',
+  placement = 'top',
+  initialText = 'Copy',
+}) => {
+  const [isCopied, setIsCopied] = React.useState(false)
+  const copyUrlToClipboard = async (e: SyntheticEvent) => {
+    try {
+      await clipboardCopy(textToCopy)
+      e.stopPropagation()
+      setIsCopied(true)
+      setTimeout(() => {
+        setIsCopied(false)
+      }, timeoutInMs)
+    } catch {
+      //noop
+    }
+  }
+  return (
+    <Tooltip title={isCopied ? successText : initialText} placement={placement}>
+      {/* eslint-disable-next-line  jsx-a11y/click-events-have-key-events*/}
+      <div
+        role="button"
+        tabIndex={0}
+        style={{ display: 'inline-block' }}
+        onClick={copyUrlToClipboard}
+      >
+        {children}
+      </div>
+    </Tooltip>
+  )
+}
+export default CopyToClipboard
+
+/** a modified version of https://github.com/feross/clipboard-copy
+ * copy/pasted bc it's not an ES module
+ * TODO: replace this with a package when there's a good one that's stable */
+function makeError() {
+  return new DOMException('The request is not allowed', 'NotAllowedError')
+}
+
+async function copyClipboardApi(text: string) {
+  // Use the Async Clipboard API when available. Requires a secure browsing
+  // context (i.e. HTTPS)
+  if (!navigator.clipboard) {
+    throw makeError()
+  }
+  return navigator.clipboard.writeText(text)
+}
+
+async function copyExecCommand(text: string) {
+  // Put the text to copy into a <span>
+  const span = document.createElement('span')
+  span.textContent = text
+
+  // Preserve consecutive spaces and newlines
+  span.style.whiteSpace = 'pre'
+  span.style.webkitUserSelect = 'auto'
+  span.style.userSelect = 'all'
+
+  // Add the <span> to the page
+  document.body.appendChild(span)
+
+  // Make a selection object representing the range of text selected by the user
+  const selection = window.getSelection()
+  const range = window.document.createRange()
+  if (selection) {
+    selection.removeAllRanges()
+    range.selectNode(span)
+    selection.addRange(range)
+
+    // Copy text to the clipboard
+    let success = false
+    try {
+      success = window.document.execCommand('copy')
+    } finally {
+      // Cleanup
+      selection.removeAllRanges()
+      window.document.body.removeChild(span)
+    }
+
+    if (!success) throw makeError()
+  }
+}
+
+async function clipboardCopy(text: string) {
+  try {
+    await copyClipboardApi(text)
+  } catch (err) {
+    // ...Otherwise, use document.execCommand() fallback
+    try {
+      await copyExecCommand(text)
+    } catch (err2) {
+      throw err2 || err || makeError()
+    }
+  }
+}

+ 101 - 0
src/components/layout/index.tsx

@@ -0,0 +1,101 @@
+import styled from 'styled-components';
+
+export const breakpoints = {
+  xs: 480,
+  sm: 576,
+  md: 768,
+  lg: 992,
+  xl: 1200,
+  xxl: 1600,
+};
+
+type RowProps = {
+  alignItems?: string;
+  flexDirection?: string;
+};
+export const Row = styled.div<RowProps>`
+  display: flex;
+  flex-wrap: wrap;
+  align-items: ${(props) => props.alignItems || 'flex-start'};
+  flex-direction: ${(props) => props.flexDirection || 'row'};
+`;
+
+interface ICol {
+  off: {
+    xs: number;
+    sm: number;
+    md: number;
+    lg: number;
+    xl: number;
+    xxl: number;
+  };
+  span: {
+    xs: number;
+    sm: number;
+    md: number;
+    lg: number;
+    xl: number;
+    xxl: number;
+  };
+  padding?: number;
+}
+const nCols = 24;
+export const Col = styled.div<ICol>`
+  margin-left: ${(props) => (props.off.xs / nCols) * 100}%;
+  width: ${(props) => (props.span.xs / nCols) * 100}%;
+  padding: 0 ${(props) => props.gutterWidth * 0.5}px;
+
+  @media screen and (min-width: ${breakpoints.sm}px) {
+    margin-left: ${(props) => (props.off.sm / nCols) * 100}%;
+    width: ${(props) => (props.span.sm / nCols) * 100}%;
+  }
+  @media screen and (min-width: ${breakpoints.md}px) {
+    margin-left: ${(props) => (props.off.md / nCols) * 100}%;
+    width: ${(props) => (props.span.md / nCols) * 100}%;
+  }
+  @media screen and (min-width: ${breakpoints.lg}px) {
+    margin-left: ${(props) => (props.off.lg / nCols) * 100}%;
+    width: ${(props) => (props.span.lg / nCols) * 100}%;
+  }
+  @media screen and (min-width: ${breakpoints.xl}px) {
+    margin-left: ${(props) => (props.off.xl / nCols) * 100}%;
+    width: ${(props) => (props.span.xl / nCols) * 100}%;
+  }
+  @media screen and (min-width: ${breakpoints.xxl}px) {
+    margin-left: ${(props) => (props.off.xxl / nCols) * 100}%;
+    width: ${(props) => (props.span.xxl / nCols) * 100}%;
+  }
+`;
+
+type SpaceProps = {
+  h: number;
+  xsHeight?: number;
+};
+export const Space = styled.div<SpaceProps>`
+  flex-shrink: 0;
+  display: ${(props) => (props.h === 0 ? 'none' : 'block')};
+  height: ${(props) => (props.h ? props.h : 10)}px;
+
+  @media screen and (max-width: ${breakpoints.sm}px) {
+    display: ${(props) => (props.xsHeight === 0 ? 'none' : 'block')};
+    height: ${(props) => (props.xsHeight ? props.xsHeight : props.h)}px;
+  }
+`;
+
+export const LeftCol = ({ children }) => (
+  <Col
+    off={{ xs: 1, sm: 2, md: 1, lg: 1, xl: 1, xxl: 1 }}
+    span={{ xs: 22, sm: 20, md: 13, lg: 13, xl: 13, xxl: 13 }}
+  >
+    {children}
+  </Col>
+);
+
+export const RightCol = ({ children }) => (
+  <Col
+    off={{ xs: 1, sm: 2, md: 2, lg: 2, xl: 2, xxl: 2 }}
+    span={{ xs: 22, sm: 20, md: 7, lg: 7, xl: 7, xxl: 7 }}
+  >
+    {children}
+  </Col>
+);

+ 35 - 0
src/components/store.tsx

@@ -0,0 +1,35 @@
+/* eslint no-unused-vars: "off" */
+
+import create from 'zustand';
+
+export type InputState = {
+  svgProps: any;
+  setSvgProps: ({ size, baseFrequency, numOctaves }) => void;
+  cssProps: any;
+  setCssProps: ({ gradientType, angle, color1, color2, contrast, brightness }) => void;
+};
+
+export const useInputStore = create<InputState>((set) => ({
+  svgProps: {
+    size: 250,
+    baseFrequency: 0.65,
+    numOctaves: 3,
+  },
+  setSvgProps: ({ size, baseFrequency, numOctaves }) =>
+    set({
+      svgProps: { size, baseFrequency, numOctaves },
+    }),
+
+  cssProps: {
+    gradientType: 'linear-gradient',
+    angle: 20,
+    color1: 'blue',
+    color2: 'transparent',
+    contrast: 170,
+    brightness: 1000,
+  },
+  setCssProps: ({ gradientType, angle, color1, color2, contrast, brightness }) =>
+    set({
+      cssProps: { gradientType, angle, color1, color2, contrast, brightness },
+    }),
+}));

+ 4 - 6
src/pages/_app.tsx

@@ -1,13 +1,11 @@
 import type { AppProps } from 'next/app';
 import React from 'react';
-import '~/styles/globals.css'
+import 'antd/dist/antd.dark.css';
+import 'highlight.js/styles/base16/outrun-dark.css';
+import '~/styles/globals.css';
 
 const App = ({ Component, pageProps }: AppProps) => {
-  return (
-    <>
-      <Component {...pageProps} />
-    </>
-  );
+  return <Component {...pageProps} />;
 };
 
 export default App;

+ 0 - 0
src/pages/document.tsx → src/pages/_document.tsx


+ 0 - 9
src/pages/index.js

@@ -1,9 +0,0 @@
-const IndexPage = () => {
-  return (
-    <div>
-      hello world
-    </div>
-  )
-}
-
-export default IndexPage;

+ 26 - 0
src/pages/index.tsx

@@ -0,0 +1,26 @@
+import React from 'react';
+import styled from 'styled-components';
+import { SvgSection, CssSection, ResultSection } from '~/components/CodeBlocks';
+import { Row, LeftCol, Space } from '~/components/layout';
+
+const IndexPage = () => {
+  return (
+    <Container>
+      <Row>
+        <LeftCol>
+          <Space h={30} />
+          <h1>Grainy Gradient playground</h1>
+        </LeftCol>
+      </Row>
+      <SvgSection />
+      <CssSection />
+      <ResultSection />
+    </Container>
+  );
+};
+
+export default IndexPage;
+
+const Container = styled.div`
+  height: 100vh;
+`;

+ 9 - 0
src/styles/globals.css

@@ -4,8 +4,17 @@ body {
   margin: 0;
   font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
     Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+  background-color: #1E1E1E;
+  color: white;
 }
 
 * {
   box-sizing: border-box;
 }
+
+pre {
+  font-size: 0.9em;
+  padding: 12px;
+  word-wrap: break-word;
+  margin: 0;
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff