;
17 | }
18 |
19 | Checkboard.defaultProps = {
20 | size: 8,
21 | white: 'transparent',
22 | grey: 'rgba(0,0,0,.08)',
23 | renderers: {},
24 | }
25 |
26 | export default Checkboard
27 |
--------------------------------------------------------------------------------
/components/react-color/components/common/ColorWrap.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PureComponent } from 'react'
2 | import debounce from 'lodash/debounce'
3 | import * as color from '../../helpers/color'
4 |
5 | export const ColorWrap = (Picker) => {
6 | class ColorPicker extends (PureComponent || Component) {
7 | constructor(props) {
8 | super()
9 |
10 | this.state = {
11 | ...color.toState(props.color, 0),
12 | }
13 |
14 | this.debounce = debounce((fn, data, event) => {
15 | fn(data, event)
16 | }, 100)
17 | }
18 |
19 | static getDerivedStateFromProps(nextProps, state) {
20 | return {
21 | ...color.toState(nextProps.color, state.oldHue),
22 | }
23 | }
24 |
25 | handleChange = (data, event) => {
26 | const isValidColor = color.simpleCheckForValidColor(data)
27 | if (isValidColor) {
28 | const colors = color.toState(data, data.h || this.state.oldHue)
29 | this.setState(colors)
30 | this.props.onChangeComplete && this.debounce(this.props.onChangeComplete, colors, event)
31 | this.props.onChange && this.props.onChange(colors, event)
32 | }
33 | }
34 |
35 | handleSwatchHover = (data, event) => {
36 | const isValidColor = color.simpleCheckForValidColor(data)
37 | if (isValidColor) {
38 | const colors = color.toState(data, data.h || this.state.oldHue)
39 | this.props.onSwatchHover && this.props.onSwatchHover(colors, event)
40 | }
41 | }
42 |
43 | render() {
44 | const optionalEvents = {}
45 | if (this.props.onSwatchHover) {
46 | optionalEvents.onSwatchHover = this.handleSwatchHover
47 | }
48 |
49 | return (
50 |
56 | )
57 | }
58 | }
59 |
60 | ColorPicker.propTypes = {
61 | ...Picker.propTypes,
62 | }
63 |
64 | ColorPicker.defaultProps = {
65 | ...Picker.defaultProps,
66 | color: {
67 | h: 250,
68 | s: 0.50,
69 | l: 0.20,
70 | a: 1,
71 | },
72 | }
73 |
74 | return ColorPicker
75 | }
76 |
77 | export default ColorWrap
78 |
--------------------------------------------------------------------------------
/components/react-color/components/common/EditableInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PureComponent } from 'react'
2 | import reactCSS from 'reactcss'
3 |
4 | const DEFAULT_ARROW_OFFSET = 1
5 |
6 | const UP_KEY_CODE = 38
7 | const DOWN_KEY_CODE = 40
8 | const VALID_KEY_CODES = [
9 | UP_KEY_CODE,
10 | DOWN_KEY_CODE
11 | ]
12 | const isValidKeyCode = keyCode => VALID_KEY_CODES.indexOf(keyCode) > -1
13 | const getNumberValue = value => Number(String(value).replace(/%/g, ''))
14 |
15 | let idCounter = 1
16 |
17 | export class EditableInput extends (PureComponent || Component) {
18 | constructor(props) {
19 | super()
20 |
21 | this.state = {
22 | value: String(props.value).toUpperCase(),
23 | blurValue: String(props.value).toUpperCase(),
24 | }
25 |
26 | this.inputId = `rc-editable-input-${idCounter++}`
27 | }
28 |
29 | componentDidUpdate(prevProps, prevState) {
30 | if (
31 | this.props.value !== this.state.value &&
32 | (prevProps.value !== this.props.value || prevState.value !== this.state.value)
33 | ) {
34 | if (this.input === document.activeElement) {
35 | this.setState({ blurValue: String(this.props.value).toUpperCase() })
36 | } else {
37 | this.setState({ value: String(this.props.value).toUpperCase(), blurValue: !this.state.blurValue && String(this.props.value).toUpperCase() })
38 | }
39 | }
40 | }
41 |
42 | componentWillUnmount() {
43 | this.unbindEventListeners()
44 | }
45 |
46 | getValueObjectWithLabel(value) {
47 | return {
48 | [this.props.label]: value
49 | }
50 | }
51 |
52 | handleBlur = () => {
53 | if (this.state.blurValue) {
54 | this.setState({ value: this.state.blurValue, blurValue: null })
55 | }
56 | }
57 |
58 | handleChange = (e) => {
59 | this.setUpdatedValue(e.target.value, e)
60 | }
61 |
62 | getArrowOffset() {
63 | return this.props.arrowOffset || DEFAULT_ARROW_OFFSET
64 | }
65 |
66 | handleKeyDown = (e) => {
67 | // In case `e.target.value` is a percentage remove the `%` character
68 | // and update accordingly with a percentage
69 | // https://github.com/casesandberg/react-color/issues/383
70 | const value = getNumberValue(e.target.value)
71 | if (!isNaN(value) && isValidKeyCode(e.keyCode)) {
72 | const offset = this.getArrowOffset()
73 | const updatedValue = e.keyCode === UP_KEY_CODE ? value + offset : value - offset
74 |
75 | this.setUpdatedValue(updatedValue, e)
76 | }
77 | }
78 |
79 | setUpdatedValue(value, e) {
80 | const onChangeValue = this.props.label ? this.getValueObjectWithLabel(value) : value
81 | this.props.onChange && this.props.onChange(onChangeValue, e)
82 |
83 | this.setState({ value })
84 | }
85 |
86 | handleDrag = (e) => {
87 | if (this.props.dragLabel) {
88 | const newValue = Math.round(this.props.value + e.movementX)
89 | if (newValue >= 0 && newValue <= this.props.dragMax) {
90 | this.props.onChange && this.props.onChange(this.getValueObjectWithLabel(newValue), e)
91 | }
92 | }
93 | }
94 |
95 | handleMouseDown = (e) => {
96 | if (this.props.dragLabel) {
97 | e.preventDefault()
98 | this.handleDrag(e)
99 | window.addEventListener('mousemove', this.handleDrag)
100 | window.addEventListener('mouseup', this.handleMouseUp)
101 | }
102 | }
103 |
104 | handleMouseUp = () => {
105 | this.unbindEventListeners()
106 | }
107 |
108 | unbindEventListeners = () => {
109 | window.removeEventListener('mousemove', this.handleDrag)
110 | window.removeEventListener('mouseup', this.handleMouseUp)
111 | }
112 |
113 | render() {
114 | const styles = reactCSS({
115 | 'default': {
116 | wrap: {
117 | position: 'relative',
118 | },
119 | },
120 | 'user-override': {
121 | wrap: this.props.style && this.props.style.wrap ? this.props.style.wrap : {},
122 | input: this.props.style && this.props.style.input ? this.props.style.input : {},
123 | label: this.props.style && this.props.style.label ? this.props.style.label : {},
124 | },
125 | 'dragLabel-true': {
126 | label: {
127 | cursor: 'ew-resize',
128 | },
129 | },
130 | }, {
131 | 'user-override': true,
132 | }, this.props)
133 |
134 | return (
135 |
136 | this.input = input }
140 | value={ this.state.value }
141 | onKeyDown={ this.handleKeyDown }
142 | onChange={ this.handleChange }
143 | onBlur={ this.handleBlur }
144 | placeholder={ this.props.placeholder }
145 | spellCheck="false"
146 | />
147 | { this.props.label && !this.props.hideLabel ? (
148 |
153 | { this.props.label }
154 |
155 | ) : null }
156 |
157 | )
158 | }
159 | }
160 |
161 | export default EditableInput
162 |
--------------------------------------------------------------------------------
/components/react-color/components/common/Hue.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PureComponent } from 'react'
2 | import reactCSS from 'reactcss'
3 | import * as hue from '../../helpers/hue'
4 |
5 | export class Hue extends (PureComponent || Component) {
6 | componentWillUnmount() {
7 | this.unbindEventListeners()
8 | }
9 |
10 | handleChange = (e) => {
11 | const change = hue.calculateChange(e, this.props.direction, this.props.hsl, this.container)
12 | change && typeof this.props.onChange === 'function' && this.props.onChange(change, e)
13 | }
14 |
15 | handleMouseDown = (e) => {
16 | this.handleChange(e)
17 | window.addEventListener('mousemove', this.handleChange)
18 | window.addEventListener('mouseup', this.handleMouseUp)
19 | }
20 |
21 | handleMouseUp = () => {
22 | this.unbindEventListeners()
23 | }
24 |
25 | unbindEventListeners() {
26 | window.removeEventListener('mousemove', this.handleChange)
27 | window.removeEventListener('mouseup', this.handleMouseUp)
28 | }
29 |
30 | render() {
31 | const { direction = 'horizontal' } = this.props
32 |
33 | const styles = reactCSS({
34 | 'default': {
35 | hue: {
36 | absolute: '0px 0px 0px 0px',
37 | borderRadius: this.props.radius,
38 | boxShadow: this.props.shadow,
39 | },
40 | container: {
41 | padding: '0 2px',
42 | position: 'relative',
43 | height: '100%',
44 | borderRadius: this.props.radius,
45 | },
46 | pointer: {
47 | position: 'absolute',
48 | left: `${ (this.props.hsl.h * 100) / 360 }%`,
49 | },
50 | slider: {
51 | marginTop: '1px',
52 | width: '4px',
53 | borderRadius: '1px',
54 | height: '8px',
55 | boxShadow: '0 0 2px rgba(0, 0, 0, .6)',
56 | background: '#fff',
57 | transform: 'translateX(-2px)',
58 | },
59 | },
60 | 'vertical': {
61 | pointer: {
62 | left: '0px',
63 | top: `${ -((this.props.hsl.h * 100) / 360) + 100 }%`,
64 | },
65 | },
66 | }, { vertical: direction === 'vertical' })
67 |
68 | return (
69 |
70 |
this.container = container }
74 | onMouseDown={ this.handleMouseDown }
75 | onTouchMove={ this.handleChange }
76 | onTouchStart={ this.handleChange }
77 | >
78 |
93 |
94 | { this.props.pointer ? (
95 |
96 | ) : (
97 |
98 | ) }
99 |
100 |
101 |
102 | )
103 | }
104 | }
105 |
106 | export default Hue
107 |
--------------------------------------------------------------------------------
/components/react-color/components/common/Raised.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import reactCSS from 'reactcss'
4 | import merge from 'lodash/merge'
5 |
6 | export const Raised = ({ zDepth, radius, background, children,
7 | styles: passedStyles = {} }) => {
8 | const styles = reactCSS(merge({
9 | 'default': {
10 | wrap: {
11 | position: 'relative',
12 | display: 'inline-block',
13 | },
14 | content: {
15 | position: 'relative',
16 | },
17 | bg: {
18 | absolute: '0px 0px 0px 0px',
19 | boxShadow: `0 ${ zDepth }px ${ zDepth * 4 }px rgba(0,0,0,.24)`,
20 | borderRadius: radius,
21 | background,
22 | },
23 | },
24 | 'zDepth-0': {
25 | bg: {
26 | boxShadow: 'none',
27 | },
28 | },
29 |
30 | 'zDepth-1': {
31 | bg: {
32 | boxShadow: '0 2px 10px rgba(0,0,0,.12), 0 2px 5px rgba(0,0,0,.16)',
33 | },
34 | },
35 | 'zDepth-2': {
36 | bg: {
37 | boxShadow: '0 6px 20px rgba(0,0,0,.19), 0 8px 17px rgba(0,0,0,.2)',
38 | },
39 | },
40 | 'zDepth-3': {
41 | bg: {
42 | boxShadow: '0 17px 50px rgba(0,0,0,.19), 0 12px 15px rgba(0,0,0,.24)',
43 | },
44 | },
45 | 'zDepth-4': {
46 | bg: {
47 | boxShadow: '0 25px 55px rgba(0,0,0,.21), 0 16px 28px rgba(0,0,0,.22)',
48 | },
49 | },
50 | 'zDepth-5': {
51 | bg: {
52 | boxShadow: '0 40px 77px rgba(0,0,0,.22), 0 27px 24px rgba(0,0,0,.2)',
53 | },
54 | },
55 | 'square': {
56 | bg: {
57 | borderRadius: '0',
58 | },
59 | },
60 | 'circle': {
61 | bg: {
62 | borderRadius: '50%',
63 | },
64 | },
65 | }, passedStyles), { 'zDepth-1': zDepth === 1 })
66 |
67 | return (
68 |
69 |
70 |
71 | { children }
72 |
73 |
74 | )
75 | }
76 |
77 | Raised.propTypes = {
78 | background: PropTypes.string,
79 | zDepth: PropTypes.oneOf([0, 1, 2, 3, 4, 5]),
80 | radius: PropTypes.number,
81 | styles: PropTypes.object,
82 | }
83 |
84 | Raised.defaultProps = {
85 | background: '#fff',
86 | zDepth: 1,
87 | radius: 2,
88 | styles: {},
89 | }
90 |
91 | export default Raised
92 |
--------------------------------------------------------------------------------
/components/react-color/components/common/Saturation.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PureComponent } from 'react'
2 | import reactCSS from 'reactcss'
3 | import throttle from 'lodash/throttle'
4 | import * as saturation from '../../helpers/saturation'
5 |
6 | export class Saturation extends (PureComponent || Component) {
7 | constructor(props) {
8 | super(props)
9 |
10 | this.throttle = throttle((fn, data, e) => {
11 | fn(data, e)
12 | }, 50)
13 | }
14 |
15 | componentWillUnmount() {
16 | this.throttle.cancel()
17 | this.unbindEventListeners()
18 | }
19 |
20 | getContainerRenderWindow() {
21 | const { container } = this
22 | let renderWindow = window
23 | while (!renderWindow.document.contains(container) && renderWindow.parent !== renderWindow) {
24 | renderWindow = renderWindow.parent
25 | }
26 | return renderWindow
27 | }
28 |
29 | handleChange = (e) => {
30 | typeof this.props.onChange === 'function' && this.throttle(
31 | this.props.onChange,
32 | saturation.calculateChange(e, this.props.hsl, this.container),
33 | e,
34 | )
35 | }
36 |
37 | handleMouseDown = (e) => {
38 | this.handleChange(e)
39 | const renderWindow = this.getContainerRenderWindow()
40 | renderWindow.addEventListener('mousemove', this.handleChange)
41 | renderWindow.addEventListener('mouseup', this.handleMouseUp)
42 | }
43 |
44 | handleMouseUp = () => {
45 | this.unbindEventListeners()
46 | }
47 |
48 | unbindEventListeners() {
49 | const renderWindow = this.getContainerRenderWindow()
50 | renderWindow.removeEventListener('mousemove', this.handleChange)
51 | renderWindow.removeEventListener('mouseup', this.handleMouseUp)
52 | }
53 |
54 | render() {
55 | const { color, white, black, pointer, circle } = this.props.style || {}
56 | const styles = reactCSS({
57 | 'default': {
58 | color: {
59 | absolute: '0px 0px 0px 0px',
60 | background: `hsl(${ this.props.hsl.h },100%, 50%)`,
61 | borderRadius: this.props.radius,
62 | },
63 | white: {
64 | absolute: '0px 0px 0px 0px',
65 | borderRadius: this.props.radius,
66 | },
67 | black: {
68 | absolute: '0px 0px 0px 0px',
69 | boxShadow: this.props.shadow,
70 | borderRadius: this.props.radius,
71 | },
72 | pointer: {
73 | position: 'absolute',
74 | top: `${ -(this.props.hsv.v * 100) + 100 }%`,
75 | left: `${ this.props.hsv.s * 100 }%`,
76 | cursor: 'default',
77 | },
78 | circle: {
79 | width: '4px',
80 | height: '4px',
81 | boxShadow: `0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0,0,0,.3),
82 | 0 0 1px 2px rgba(0,0,0,.4)`,
83 | borderRadius: '50%',
84 | cursor: 'hand',
85 | transform: 'translate(-2px, -2px)',
86 | },
87 | },
88 | 'custom': {
89 | color,
90 | white,
91 | black,
92 | pointer,
93 | circle,
94 | },
95 | }, { 'custom': !!this.props.style })
96 |
97 | return (
98 |
this.container = container }
101 | onMouseDown={ this.handleMouseDown }
102 | onTouchMove={ this.handleChange }
103 | onTouchStart={ this.handleChange }
104 | >
105 |
115 |
116 |
117 |
118 | { this.props.pointer ? (
119 |
120 | ) : (
121 |
122 | ) }
123 |
124 |
125 |
126 | )
127 | }
128 | }
129 |
130 | export default Saturation
131 |
--------------------------------------------------------------------------------
/components/react-color/components/common/Swatch.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import reactCSS from 'reactcss'
3 | import { handleFocus } from '../../helpers/interaction'
4 |
5 | import Checkboard from './Checkboard'
6 |
7 | const ENTER = 13
8 |
9 | export const Swatch = ({ color, style, onClick = () => {}, onHover, title = color,
10 | children, focus, focusStyle = {} }) => {
11 | const transparent = color === 'transparent'
12 | const styles = reactCSS({
13 | default: {
14 | swatch: {
15 | background: color,
16 | height: '100%',
17 | width: '100%',
18 | cursor: 'pointer',
19 | position: 'relative',
20 | outline: 'none',
21 | ...style,
22 | ...(focus ? focusStyle : {}),
23 | },
24 | },
25 | })
26 |
27 | const handleClick = e => onClick(color, e)
28 | const handleKeyDown = e => e.keyCode === ENTER && onClick(color, e)
29 | const handleHover = e => onHover(color, e)
30 |
31 | const optionalEvents = {}
32 | if (onHover) {
33 | optionalEvents.onMouseOver = handleHover
34 | }
35 |
36 | return (
37 |
45 | { children }
46 | { transparent && (
47 |
51 | ) }
52 |
53 | )
54 | }
55 |
56 | export default handleFocus(Swatch)
57 |
--------------------------------------------------------------------------------
/components/react-color/components/common/__snapshots__/spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Alpha renders correctly 1`] = `
4 |
131 | `;
132 |
133 | exports[`Checkboard renders children correctly 1`] = `
134 |
156 | Click
157 |
158 | `;
159 |
160 | exports[`Checkboard renders correctly 1`] = `
161 |
183 | `;
184 |
185 | exports[`EditableInput renders correctly 1`] = `
186 |
193 |
203 |
208 | Hex
209 |
210 |
211 | `;
212 |
213 | exports[`Hue renders correctly 1`] = `
214 |
306 | `;
307 |
308 | exports[`Saturation renders correctly 1`] = `
309 |
422 | `;
423 |
424 | exports[`Swatch renders correctly 1`] = `
425 |
429 |
446 |
447 | `;
448 |
449 | exports[`Swatch renders custom title correctly 1`] = `
450 |
454 |
470 |
471 | `;
472 |
473 | exports[`Swatch renders with an onMouseOver handler correctly 1`] = `
474 |
478 |
495 |
496 | `;
497 |
--------------------------------------------------------------------------------
/components/react-color/components/common/index.js:
--------------------------------------------------------------------------------
1 | export { default as Alpha } from './Alpha'
2 | export { default as Checkboard } from './Checkboard'
3 | export { default as EditableInput } from './EditableInput'
4 | export { default as Hue } from './Hue'
5 | export { default as Raised } from './Raised'
6 | export { default as Saturation } from './Saturation'
7 | export { default as ColorWrap } from './ColorWrap'
8 | export { default as Swatch } from './Swatch'
9 |
--------------------------------------------------------------------------------
/components/react-color/components/common/spec.js:
--------------------------------------------------------------------------------
1 | /* global test, jest, expect */
2 |
3 | import React from 'react'
4 | import renderer from 'react-test-renderer'
5 | import { red } from '../../helpers/color'
6 | // import canvas from 'canvas'
7 |
8 | import Alpha from './Alpha'
9 | import Checkboard from './Checkboard'
10 | import EditableInput from './EditableInput'
11 | import Hue from './Hue'
12 | import Saturation from './Saturation'
13 | import Swatch from './Swatch'
14 |
15 | test('Alpha renders correctly', () => {
16 | const tree = renderer.create(
17 |
,
18 | ).toJSON()
19 | expect(tree).toMatchSnapshot()
20 | })
21 |
22 | // test('Alpha renders on server correctly', () => {
23 | // const tree = renderer.create(
24 | //
25 | // ).toJSON()
26 | // expect(tree).toMatchSnapshot()
27 | // })
28 |
29 | test('Checkboard renders correctly', () => {
30 | const tree = renderer.create(
31 |
,
32 | ).toJSON()
33 | expect(tree).toMatchSnapshot()
34 | })
35 |
36 | test('Checkboard renders children correctly', () => {
37 | const tree = renderer.create(
38 |
Click ,
39 | ).toJSON()
40 | expect(tree).toMatchSnapshot()
41 | })
42 |
43 | // test('Checkboard renders on server correctly', () => {
44 | // const tree = renderer.create(
45 | //
46 | // ).toJSON()
47 | // expect(tree).toMatchSnapshot()
48 | // })
49 |
50 | test('EditableInput renders correctly', () => {
51 | const tree = renderer.create(
52 |
,
53 | ).toJSON()
54 | expect(tree).toMatchSnapshot()
55 | })
56 |
57 | test('Hue renders correctly', () => {
58 | const tree = renderer.create(
59 |
,
60 | ).toJSON()
61 | expect(tree).toMatchSnapshot()
62 | })
63 |
64 | test('Saturation renders correctly', () => {
65 | const tree = renderer.create(
66 |
,
67 | ).toJSON()
68 | expect(tree).toMatchSnapshot()
69 | })
70 |
71 | test('Swatch renders correctly', () => {
72 | const tree = renderer.create(
73 |
,
74 | ).toJSON()
75 | expect(tree).toMatchSnapshot()
76 | })
77 |
78 | test('Swatch renders custom title correctly', () => {
79 | const tree = renderer.create(
80 |
,
81 | ).toJSON()
82 | expect(tree).toMatchSnapshot()
83 | })
84 |
85 | test('Swatch renders with an onMouseOver handler correctly', () => {
86 | const tree = renderer.create(
87 |
{} } />,
88 | ).toJSON()
89 | expect(tree).toMatchSnapshot()
90 | })
91 |
--------------------------------------------------------------------------------
/components/react-color/components/slider/Slider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import reactCSS from 'reactcss';
4 | import merge from 'lodash/merge';
5 |
6 | import { ColorWrap, Hue } from '../common';
7 | import SliderSwatches from './SliderSwatches';
8 | import SliderPointer from './SliderPointer';
9 |
10 | export const Slider = ({
11 | hsl,
12 | onChange,
13 | pointer,
14 | styles: passedStyles = {},
15 | className = '',
16 | }) => {
17 | const styles = reactCSS(
18 | merge(
19 | {
20 | default: {
21 | hue: {
22 | height: '12px',
23 | position: 'relative',
24 | },
25 | Hue: {
26 | radius: '2px',
27 | },
28 | },
29 | },
30 | passedStyles
31 | )
32 | );
33 |
34 | return (
35 |
40 | );
41 | };
42 |
43 | Slider.propTypes = {
44 | styles: PropTypes.object,
45 | };
46 | Slider.defaultProps = {
47 | pointer: SliderPointer,
48 | styles: {},
49 | };
50 |
51 | export default ColorWrap(Slider);
52 |
--------------------------------------------------------------------------------
/components/react-color/components/slider/SliderPointer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import reactCSS from 'reactcss'
3 |
4 | export const SliderPointer = () => {
5 | const styles = reactCSS({
6 | 'default': {
7 | picker: {
8 | width: '14px',
9 | height: '14px',
10 | borderRadius: '6px',
11 | transform: 'translate(-7px, -1px)',
12 | backgroundColor: 'rgb(248, 248, 248)',
13 | boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.37)',
14 | },
15 | },
16 | })
17 |
18 | return (
19 |
20 | )
21 | }
22 |
23 | export default SliderPointer
24 |
--------------------------------------------------------------------------------
/components/react-color/components/slider/SliderSwatch.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import reactCSS from 'reactcss'
3 |
4 | export const SliderSwatch = ({ hsl, offset, onClick = () => {}, active, first, last }) => {
5 | const styles = reactCSS({
6 | 'default': {
7 | swatch: {
8 | height: '12px',
9 | background: `hsl(${ hsl.h }, 50%, ${ (offset * 100) }%)`,
10 | cursor: 'pointer',
11 | },
12 | },
13 | 'first': {
14 | swatch: {
15 | borderRadius: '2px 0 0 2px',
16 | },
17 | },
18 | 'last': {
19 | swatch: {
20 | borderRadius: '0 2px 2px 0',
21 | },
22 | },
23 | 'active': {
24 | swatch: {
25 | transform: 'scaleY(1.8)',
26 | borderRadius: '3.6px/2px',
27 | },
28 | },
29 | }, { active, first, last })
30 |
31 | const handleClick = e => onClick({
32 | h: hsl.h,
33 | s: 0.5,
34 | l: offset,
35 | source: 'hsl',
36 | }, e)
37 |
38 | return (
39 |
40 | )
41 | }
42 |
43 | export default SliderSwatch
44 |
--------------------------------------------------------------------------------
/components/react-color/components/slider/SliderSwatches.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import reactCSS from 'reactcss'
3 |
4 | import SliderSwatch from './SliderSwatch'
5 |
6 | export const SliderSwatches = ({ onClick, hsl }) => {
7 | const styles = reactCSS({
8 | 'default': {
9 | swatches: {
10 | marginTop: '20px',
11 | },
12 | swatch: {
13 | boxSizing: 'border-box',
14 | width: '20%',
15 | paddingRight: '1px',
16 | float: 'left',
17 | },
18 | clear: {
19 | clear: 'both',
20 | },
21 | },
22 | })
23 |
24 | // Acceptible difference in floating point equality
25 | const epsilon = 0.1
26 |
27 | return (
28 |
29 |
30 |
38 |
39 |
40 |
47 |
48 |
49 |
56 |
57 |
58 |
65 |
66 |
67 |
75 |
76 |
77 |
78 | )
79 | }
80 |
81 | export default SliderSwatches
82 |
--------------------------------------------------------------------------------
/components/react-color/components/slider/__snapshots__/spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Slider renders correctly 1`] = `
4 |
8 |
108 |
111 |
118 |
144 |
165 |
186 |
207 |
238 |
245 |
246 |
247 |
248 | `;
249 |
250 | exports[`SliderPointer renders correctly 1`] = `
251 |
275 | `;
276 |
277 | exports[`SliderSwatch renders correctly 1`] = `
278 |
288 | `;
289 |
290 | exports[`SliderSwatches renders correctly 1`] = `
291 |
298 |
324 |
345 |
366 |
387 |
413 |
420 |
421 | `;
422 |
--------------------------------------------------------------------------------
/components/react-color/components/slider/spec.js:
--------------------------------------------------------------------------------
1 | /* global test, expect */
2 |
3 | import React from 'react'
4 | import renderer from 'react-test-renderer'
5 | import { red } from '../../helpers/color'
6 |
7 | import Slider from './Slider'
8 | import SliderPointer from './SliderPointer'
9 | import SliderSwatch from './SliderSwatch'
10 | import SliderSwatches from './SliderSwatches'
11 |
12 | test('Slider renders correctly', () => {
13 | const tree = renderer.create(
14 | ,
15 | ).toJSON()
16 | expect(tree).toMatchSnapshot()
17 | })
18 |
19 | test('Slider renders custom styles correctly', () => {
20 | const tree = renderer.create(
21 | ,
22 | ).toJSON()
23 | expect(tree.props.style.boxShadow).toBe('none')
24 | })
25 |
26 |
27 | test('SliderPointer renders correctly', () => {
28 | const tree = renderer.create(
29 | ,
30 | ).toJSON()
31 | expect(tree).toMatchSnapshot()
32 | })
33 |
34 | test('SliderSwatch renders correctly', () => {
35 | const tree = renderer.create(
36 | ,
37 | ).toJSON()
38 | expect(tree).toMatchSnapshot()
39 | })
40 |
41 | test('SliderSwatches renders correctly', () => {
42 | const tree = renderer.create(
43 | ,
44 | ).toJSON()
45 | expect(tree).toMatchSnapshot()
46 | })
47 |
--------------------------------------------------------------------------------
/components/react-color/components/twitter/Twitter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import reactCSS from 'reactcss';
4 | import map from 'lodash/map';
5 | import merge from 'lodash/merge';
6 | import * as color from '../../helpers/color';
7 |
8 | import { ColorWrap, EditableInput, Swatch } from '../common';
9 |
10 | export const Twitter = ({
11 | onChange,
12 | onSwatchHover,
13 | hex,
14 | colors,
15 | width,
16 | triangle,
17 | styles: passedStyles = {},
18 | className = '',
19 | }) => {
20 | const styles = reactCSS(
21 | merge(
22 | {
23 | default: {
24 | card: {
25 | width,
26 | background: '#fff',
27 | border: '0 solid rgba(0,0,0,0.25)',
28 | // boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
29 | borderRadius: '4px',
30 | position: 'relative',
31 | },
32 | body: {
33 | padding: '0px',
34 | },
35 | label: {
36 | fontSize: '18px',
37 | color: '#fff',
38 | },
39 | triangle: {
40 | width: '0px',
41 | height: '0px',
42 | borderStyle: 'solid',
43 | borderWidth: '0 9px 10px 9px',
44 | borderColor: 'transparent transparent #fff transparent',
45 | position: 'absolute',
46 | },
47 | triangleShadow: {
48 | width: '0px',
49 | height: '0px',
50 | borderStyle: 'solid',
51 | borderWidth: '0 9px 10px 9px',
52 | borderColor: 'transparent transparent rgba(0,0,0,.1) transparent',
53 | position: 'absolute',
54 | },
55 | hash: {
56 | background: '#F0F0F0',
57 | height: '26px',
58 | width: '26px',
59 | borderRadius: '4px 0 0 4px',
60 | float: 'left',
61 | color: '#98A1A4',
62 | display: 'flex',
63 | alignItems: 'center',
64 | justifyContent: 'center',
65 | },
66 | input: {
67 | width: '88px',
68 | fontSize: '14px',
69 | color: '#666',
70 | border: '0px',
71 | outline: 'none',
72 | height: '26px',
73 | boxShadow: 'inset 0 0 0 1px #F0F0F0',
74 | boxSizing: 'content-box',
75 | borderRadius: '0 4px 4px 0',
76 | float: 'left',
77 | paddingLeft: '8px',
78 | },
79 | swatch: {
80 | width: '26px',
81 | height: '26px',
82 | float: 'left',
83 | borderRadius: '4px',
84 | margin: '0 6px 6px 0',
85 | },
86 | clear: {
87 | clear: 'both',
88 | },
89 | },
90 | 'hide-triangle': {
91 | triangle: {
92 | display: 'none',
93 | },
94 | triangleShadow: {
95 | display: 'none',
96 | },
97 | },
98 | 'top-left-triangle': {
99 | triangle: {
100 | top: '-10px',
101 | left: '12px',
102 | },
103 | triangleShadow: {
104 | top: '-11px',
105 | left: '12px',
106 | },
107 | },
108 | 'top-right-triangle': {
109 | triangle: {
110 | top: '-10px',
111 | right: '12px',
112 | },
113 | triangleShadow: {
114 | top: '-11px',
115 | right: '12px',
116 | },
117 | },
118 | },
119 | passedStyles
120 | ),
121 | {
122 | 'hide-triangle': triangle === 'hide',
123 | 'top-left-triangle': triangle === 'top-left',
124 | 'top-right-triangle': triangle === 'top-right',
125 | }
126 | );
127 |
128 | const handleChange = (hexcode, e) => {
129 | color.isValidHex(hexcode) &&
130 | onChange(
131 | {
132 | hex: hexcode,
133 | source: 'hex',
134 | },
135 | e
136 | );
137 | };
138 |
139 | return (
140 |
141 |
142 |
143 |
144 |
145 | {map(colors, (c, i) => {
146 | return (
147 |
158 | );
159 | })}
160 |
#
161 |
167 |
168 |
169 |
170 | );
171 | };
172 |
173 | Twitter.propTypes = {
174 | width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
175 | triangle: PropTypes.oneOf(['hide', 'top-left', 'top-right']),
176 | colors: PropTypes.arrayOf(PropTypes.string),
177 | styles: PropTypes.object,
178 | };
179 |
180 | Twitter.defaultProps = {
181 | width: 224,
182 | colors: [
183 | '#FF6900',
184 | '#FCB900',
185 | '#7BDCB5',
186 | '#00D084',
187 | '#8ED1FC',
188 | '#0693E3',
189 | '#ABB8C3',
190 | '#EB144C',
191 | '#F78DA7',
192 | '#9900EF',
193 | ],
194 | triangle: 'top-left',
195 | styles: {},
196 | };
197 |
198 | export default ColorWrap(Twitter);
199 |
--------------------------------------------------------------------------------
/components/react-color/components/twitter/__snapshots__/spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Twitter \`triangle="hide"\` 1`] = `
4 |
25 |
38 |
51 |
58 |
62 |
85 |
86 |
90 |
113 |
114 |
118 |
141 |
142 |
146 |
169 |
170 |
174 |
197 |
198 |
202 |
225 |
226 |
230 |
253 |
254 |
258 |
281 |
282 |
286 |
309 |
310 |
314 |
337 |
338 |
358 | #
359 |
360 |
367 |
399 |
400 |
407 |
408 |
409 | `;
410 |
411 | exports[`Twitter \`triangle="top-right"\` 1`] = `
412 |
433 |
447 |
461 |
468 |
472 |
495 |
496 |
500 |
523 |
524 |
528 |
551 |
552 |
556 |
579 |
580 |
584 |
607 |
608 |
612 |
635 |
636 |
640 |
663 |
664 |
668 |
691 |
692 |
696 |
719 |
720 |
724 |
747 |
748 |
768 | #
769 |
770 |
777 |
809 |
810 |
817 |
818 |
819 | `;
820 |
821 | exports[`Twitter renders correctly 1`] = `
822 |
843 |
857 |
871 |
878 |
882 |
905 |
906 |
910 |
933 |
934 |
938 |
961 |
962 |
966 |
989 |
990 |
994 |
1017 |
1018 |
1022 |
1045 |
1046 |
1050 |
1073 |
1074 |
1078 |
1101 |
1102 |
1106 |
1129 |
1130 |
1134 |
1157 |
1158 |
1178 | #
1179 |
1180 |
1187 |
1219 |
1220 |
1227 |
1228 |
1229 | `;
1230 |
--------------------------------------------------------------------------------
/components/react-color/components/twitter/spec.js:
--------------------------------------------------------------------------------
1 | /* global test, jest, expect */
2 |
3 | import React from 'react'
4 | import renderer from 'react-test-renderer'
5 | import { mount } from 'enzyme'
6 | import * as color from '../../helpers/color'
7 |
8 | import Twitter from './Twitter'
9 | import { Swatch } from '../common'
10 |
11 | test('Twitter renders correctly', () => {
12 | const tree = renderer.create(
13 | ,
14 | ).toJSON()
15 | expect(tree).toMatchSnapshot()
16 | })
17 |
18 | test('Material renders custom styles correctly', () => {
19 | const tree = renderer.create(
20 | ,
21 | ).toJSON()
22 | expect(tree.props.style.boxShadow).toBe('0 0 10px red')
23 | })
24 |
25 | test('Twitter `triangle="hide"`', () => {
26 | const tree = renderer.create(
27 | ,
28 | ).toJSON()
29 | expect(tree).toMatchSnapshot()
30 | })
31 |
32 | test('Twitter `triangle="top-right"`', () => {
33 | const tree = renderer.create(
34 | ,
35 | ).toJSON()
36 | expect(tree).toMatchSnapshot()
37 | })
38 |
39 | test('Twitter onChange events correctly', () => {
40 | const changeSpy = jest.fn((data) => {
41 | expect(color.simpleCheckForValidColor(data)).toBeTruthy()
42 | })
43 | const tree = mount(
44 | ,
45 | )
46 | expect(changeSpy).toHaveBeenCalledTimes(0)
47 | const swatches = tree.find(Swatch)
48 | swatches.at(0).childAt(0).simulate('click')
49 |
50 | expect(changeSpy).toHaveBeenCalled()
51 | })
52 |
53 | test('Twitter with onSwatchHover events correctly', () => {
54 | const hoverSpy = jest.fn((data) => {
55 | expect(color.simpleCheckForValidColor(data)).toBeTruthy()
56 | })
57 | const tree = mount(
58 | ,
59 | )
60 | expect(hoverSpy).toHaveBeenCalledTimes(0)
61 | const swatches = tree.find(Swatch)
62 | swatches.at(0).childAt(0).simulate('mouseOver')
63 |
64 | expect(hoverSpy).toHaveBeenCalled()
65 | })
66 |
--------------------------------------------------------------------------------
/components/react-color/components/twitter/story.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { storiesOf } from '@storybook/react'
3 | import { renderWithKnobs } from '../../../.storybook/report'
4 | import SyncColorField from '../../../.storybook/SyncColorField'
5 |
6 | import Twitter from './Twitter'
7 |
8 | storiesOf('Pickers', module)
9 | .add('TwitterPicker', () => (
10 |
11 | { renderWithKnobs(Twitter, {}, null, {
12 | width: { range: true, min: 140, max: 500, step: 1 },
13 | }) }
14 |
15 | ))
16 |
--------------------------------------------------------------------------------
/components/react-color/helpers/alpha.js:
--------------------------------------------------------------------------------
1 | export const calculateChange = (e, hsl, direction, initialA, container) => {
2 | const containerWidth = container.clientWidth
3 | const containerHeight = container.clientHeight
4 | const x = typeof e.pageX === 'number' ? e.pageX : e.touches[0].pageX
5 | const y = typeof e.pageY === 'number' ? e.pageY : e.touches[0].pageY
6 | const left = x - (container.getBoundingClientRect().left + window.pageXOffset)
7 | const top = y - (container.getBoundingClientRect().top + window.pageYOffset)
8 |
9 | if (direction === 'vertical') {
10 | let a
11 | if (top < 0) {
12 | a = 0
13 | } else if (top > containerHeight) {
14 | a = 1
15 | } else {
16 | a = Math.round((top * 100) / containerHeight) / 100
17 | }
18 |
19 | if (hsl.a !== a) {
20 | return {
21 | h: hsl.h,
22 | s: hsl.s,
23 | l: hsl.l,
24 | a,
25 | source: 'rgb',
26 | }
27 | }
28 | } else {
29 | let a
30 | if (left < 0) {
31 | a = 0
32 | } else if (left > containerWidth) {
33 | a = 1
34 | } else {
35 | a = Math.round((left * 100) / containerWidth) / 100
36 | }
37 |
38 | if (initialA !== a) {
39 | return {
40 | h: hsl.h,
41 | s: hsl.s,
42 | l: hsl.l,
43 | a,
44 | source: 'rgb',
45 | }
46 | }
47 | }
48 | return null
49 | }
50 |
--------------------------------------------------------------------------------
/components/react-color/helpers/checkboard.js:
--------------------------------------------------------------------------------
1 | const checkboardCache = {}
2 |
3 | export const render = (c1, c2, size, serverCanvas) => {
4 | if (typeof document === 'undefined' && !serverCanvas) {
5 | return null
6 | }
7 | const canvas = serverCanvas ? new serverCanvas() : document.createElement('canvas')
8 | canvas.width = size * 2
9 | canvas.height = size * 2
10 | const ctx = canvas.getContext('2d')
11 | if (!ctx) {
12 | return null
13 | } // If no context can be found, return early.
14 | ctx.fillStyle = c1
15 | ctx.fillRect(0, 0, canvas.width, canvas.height)
16 | ctx.fillStyle = c2
17 | ctx.fillRect(0, 0, size, size)
18 | ctx.translate(size, size)
19 | ctx.fillRect(0, 0, size, size)
20 | return canvas.toDataURL()
21 | }
22 |
23 | export const get = (c1, c2, size, serverCanvas) => {
24 | const key = `${ c1 }-${ c2 }-${ size }${ serverCanvas ? '-server' : '' }`
25 |
26 | if (checkboardCache[key]) {
27 | return checkboardCache[key]
28 | }
29 |
30 | const checkboard = render(c1, c2, size, serverCanvas)
31 | checkboardCache[key] = checkboard
32 | return checkboard
33 | }
34 |
--------------------------------------------------------------------------------
/components/react-color/helpers/color.js:
--------------------------------------------------------------------------------
1 | import each from 'lodash/each'
2 | import tinycolor from 'tinycolor2'
3 |
4 | export const simpleCheckForValidColor = (data) => {
5 | const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v']
6 | let checked = 0
7 | let passed = 0
8 | each(keysToCheck, (letter) => {
9 | if (data[letter]) {
10 | checked += 1
11 | if (!isNaN(data[letter])) {
12 | passed += 1
13 | }
14 | if (letter === 's' || letter === 'l') {
15 | const percentPatt = /^\d+%$/
16 | if (percentPatt.test(data[letter])) {
17 | passed += 1
18 | }
19 | }
20 | }
21 | })
22 | return (checked === passed) ? data : false
23 | }
24 |
25 | export const toState = (data, oldHue) => {
26 | const color = data.hex ? tinycolor(data.hex) : tinycolor(data)
27 | const hsl = color.toHsl()
28 | const hsv = color.toHsv()
29 | const rgb = color.toRgb()
30 | const hex = color.toHex()
31 | if (hsl.s === 0) {
32 | hsl.h = oldHue || 0
33 | hsv.h = oldHue || 0
34 | }
35 | const transparent = hex === '000000' && rgb.a === 0
36 |
37 | return {
38 | hsl,
39 | hex: transparent ? 'transparent' : `#${ hex }`,
40 | rgb,
41 | hsv,
42 | oldHue: data.h || oldHue || hsl.h,
43 | source: data.source,
44 | }
45 | }
46 |
47 | export const isValidHex = (hex) => {
48 | if (hex === 'transparent') {
49 | return true
50 | }
51 | // disable hex4 and hex8
52 | const lh = (String(hex).charAt(0) === '#') ? 1 : 0
53 | return hex.length !== (4 + lh) && hex.length < (7 + lh) && tinycolor(hex).isValid()
54 | }
55 |
56 | export const getContrastingColor = (data) => {
57 | if (!data) {
58 | return '#fff'
59 | }
60 | const col = toState(data)
61 | if (col.hex === 'transparent') {
62 | return 'rgba(0,0,0,0.4)'
63 | }
64 | const yiq = ((col.rgb.r * 299) + (col.rgb.g * 587) + (col.rgb.b * 114)) / 1000
65 | return (yiq >= 128) ? '#000' : '#fff'
66 | }
67 |
68 | export const red = {
69 | hsl: { a: 1, h: 0, l: 0.5, s: 1 },
70 | hex: '#ff0000',
71 | rgb: { r: 255, g: 0, b: 0, a: 1 },
72 | hsv: { h: 0, s: 1, v: 1, a: 1 },
73 | }
74 |
75 | export const isvalidColorString = (string, type) => {
76 | const stringWithoutDegree = string.replace('°', '')
77 | return tinycolor(`${ type } (${ stringWithoutDegree })`)._ok
78 | }
79 |
--------------------------------------------------------------------------------
/components/react-color/helpers/hue.js:
--------------------------------------------------------------------------------
1 | export const calculateChange = (e, direction, hsl, container) => {
2 | const containerWidth = container.clientWidth
3 | const containerHeight = container.clientHeight
4 | const x = typeof e.pageX === 'number' ? e.pageX : e.touches[0].pageX
5 | const y = typeof e.pageY === 'number' ? e.pageY : e.touches[0].pageY
6 | const left = x - (container.getBoundingClientRect().left + window.pageXOffset)
7 | const top = y - (container.getBoundingClientRect().top + window.pageYOffset)
8 |
9 | if (direction === 'vertical') {
10 | let h
11 | if (top < 0) {
12 | h = 359
13 | } else if (top > containerHeight) {
14 | h = 0
15 | } else {
16 | const percent = -((top * 100) / containerHeight) + 100
17 | h = ((360 * percent) / 100)
18 | }
19 |
20 | if (hsl.h !== h) {
21 | return {
22 | h,
23 | s: hsl.s,
24 | l: hsl.l,
25 | a: hsl.a,
26 | source: 'hsl',
27 | }
28 | }
29 | } else {
30 | let h
31 | if (left < 0) {
32 | h = 0
33 | } else if (left > containerWidth) {
34 | h = 359
35 | } else {
36 | const percent = (left * 100) / containerWidth
37 | h = ((360 * percent) / 100)
38 | }
39 |
40 | if (hsl.h !== h) {
41 | return {
42 | h,
43 | s: hsl.s,
44 | l: hsl.l,
45 | a: hsl.a,
46 | source: 'hsl',
47 | }
48 | }
49 | }
50 | return null
51 | }
52 |
--------------------------------------------------------------------------------
/components/react-color/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './checkboard'
2 | export * from './color'
3 |
--------------------------------------------------------------------------------
/components/react-color/helpers/interaction.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-invalid-this */
2 | import React from 'react'
3 |
4 | export const handleFocus = (Component, Span = 'span') =>
5 | class Focus extends React.Component {
6 | state = { focus: false }
7 | handleFocus = () => this.setState({ focus: true })
8 | handleBlur = () => this.setState({ focus: false })
9 |
10 | render() {
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/components/react-color/helpers/saturation.js:
--------------------------------------------------------------------------------
1 | export const calculateChange = (e, hsl, container) => {
2 | const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect()
3 | const x = typeof e.pageX === 'number' ? e.pageX : e.touches[0].pageX
4 | const y = typeof e.pageY === 'number' ? e.pageY : e.touches[0].pageY
5 | let left = x - (container.getBoundingClientRect().left + window.pageXOffset)
6 | let top = y - (container.getBoundingClientRect().top + window.pageYOffset)
7 |
8 | if (left < 0) {
9 | left = 0
10 | } else if (left > containerWidth) {
11 | left = containerWidth
12 | }
13 |
14 | if (top < 0) {
15 | top = 0
16 | } else if (top > containerHeight) {
17 | top = containerHeight
18 | }
19 |
20 | const saturation = left / containerWidth
21 | const bright = 1 - (top / containerHeight)
22 |
23 | return {
24 | h: hsl.h,
25 | s: saturation,
26 | v: bright,
27 | a: hsl.a,
28 | source: 'hsv',
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/components/react-color/helpers/spec.js:
--------------------------------------------------------------------------------
1 | /* global test, expect, describe */
2 |
3 | import * as color from './color'
4 |
5 | describe('helpers/color', () => {
6 | describe('simpleCheckForValidColor', () => {
7 | test('throws on null', () => {
8 | const data = null
9 | expect(() => color.simpleCheckForValidColor(data)).toThrowError(TypeError)
10 | })
11 |
12 | test('throws on undefined', () => {
13 | const data = undefined
14 | expect(() => color.simpleCheckForValidColor(data)).toThrowError(TypeError)
15 | })
16 |
17 | test('no-op on number', () => {
18 | const data = 255
19 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
20 | })
21 |
22 | test('no-op on NaN', () => {
23 | const data = NaN
24 | expect(isNaN(color.simpleCheckForValidColor(data))).toBeTruthy()
25 | })
26 |
27 | test('no-op on string', () => {
28 | const data = 'ffffff'
29 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
30 | })
31 |
32 | test('no-op on array', () => {
33 | const data = []
34 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
35 | })
36 |
37 | test('no-op on rgb objects with numeric keys', () => {
38 | const data = { r: 0, g: 0, b: 0 }
39 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
40 | })
41 |
42 | test('no-op on an object with an r g b a h s v key mapped to a NaN value', () => {
43 | const data = { r: NaN }
44 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
45 | })
46 |
47 | test('no-op on hsl "s" percentage', () => {
48 | const data = { s: '15%' }
49 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
50 | })
51 |
52 | test('no-op on hsl "l" percentage', () => {
53 | const data = { l: '100%' }
54 | expect(color.simpleCheckForValidColor(data)).toEqual(data)
55 | })
56 |
57 | test('should return false for invalid percentage', () => {
58 | const data = { l: '100%2' }
59 | expect(color.simpleCheckForValidColor(data)).toBe(false)
60 | })
61 | })
62 |
63 | describe('toState', () => {
64 | test('returns an object giving a color in all formats', () => {
65 | expect(color.toState('red')).toEqual({
66 | hsl: { a: 1, h: 0, l: 0.5, s: 1 },
67 | hex: '#ff0000',
68 | rgb: { r: 255, g: 0, b: 0, a: 1 },
69 | hsv: { h: 0, s: 1, v: 1, a: 1 },
70 | oldHue: 0,
71 | source: undefined,
72 | })
73 | })
74 |
75 | test('gives hex color with leading hash', () => {
76 | expect(color.toState('blue').hex).toEqual('#0000ff')
77 | })
78 |
79 | test('doesn\'t mutate hsl color object', () => {
80 | const originalData = { h: 0, s: 0, l: 0, a: 1 }
81 | const data = Object.assign({}, originalData)
82 | color.toState(data)
83 | expect(data).toEqual(originalData)
84 | })
85 |
86 | test('doesn\'t mutate hsv color object', () => {
87 | const originalData = { h: 0, s: 0, v: 0, a: 1 }
88 | const data = Object.assign({}, originalData)
89 | color.toState(data)
90 | expect(data).toEqual(originalData)
91 | })
92 | })
93 |
94 | describe('isValidHex', () => {
95 | test('allows strings of length 3 or 6', () => {
96 | expect(color.isValidHex('f')).toBeFalsy()
97 | expect(color.isValidHex('ff')).toBeFalsy()
98 | expect(color.isValidHex('fff')).toBeTruthy()
99 | expect(color.isValidHex('ffff')).toBeFalsy()
100 | expect(color.isValidHex('fffff')).toBeFalsy()
101 | expect(color.isValidHex('ffffff')).toBeTruthy()
102 | expect(color.isValidHex('fffffff')).toBeFalsy()
103 | expect(color.isValidHex('ffffffff')).toBeFalsy()
104 | expect(color.isValidHex('fffffffff')).toBeFalsy()
105 | expect(color.isValidHex('ffffffffff')).toBeFalsy()
106 | expect(color.isValidHex('fffffffffff')).toBeFalsy()
107 | expect(color.isValidHex('ffffffffffff')).toBeFalsy()
108 | })
109 |
110 | test('allows strings without leading hash', () => {
111 | // Check a sample of possible colors - doing all takes too long.
112 | for (let i = 0; i <= 0xffffff; i += 0x010101) {
113 | const hex = (`000000${ i.toString(16) }`).slice(-6)
114 | expect(color.isValidHex(hex)).toBeTruthy()
115 | }
116 | })
117 |
118 | test('allows strings with leading hash', () => {
119 | // Check a sample of possible colors - doing all takes too long.
120 | for (let i = 0; i <= 0xffffff; i += 0x010101) {
121 | const hex = (`000000${ i.toString(16) }`).slice(-6)
122 | expect(color.isValidHex(`#${ hex }`)).toBeTruthy()
123 | }
124 | })
125 |
126 | test('is case-insensitive', () => {
127 | expect(color.isValidHex('ffffff')).toBeTruthy()
128 | expect(color.isValidHex('FfFffF')).toBeTruthy()
129 | expect(color.isValidHex('FFFFFF')).toBeTruthy()
130 | })
131 |
132 | test('allow transparent color', () => {
133 | expect(color.isValidHex('transparent')).toBeTruthy()
134 | })
135 |
136 | test('does not allow non-hex characters', () => {
137 | expect(color.isValidHex('gggggg')).toBeFalsy()
138 | })
139 |
140 | test('does not allow numbers', () => {
141 | expect(color.isValidHex(0xffffff)).toBeFalsy()
142 | })
143 | })
144 |
145 | describe('getContrastingColor', () => {
146 | test('returns a light color for a giving dark color', () => {
147 | expect(color.getContrastingColor('red')).toEqual('#fff')
148 | })
149 |
150 | test('returns a dark color for a giving light color', () => {
151 | expect(color.getContrastingColor('white')).toEqual('#000')
152 | })
153 |
154 | test('returns a predefined color for Transparent', () => {
155 | expect(color.getContrastingColor('transparent')).toEqual('rgba(0,0,0,0.4)')
156 | })
157 |
158 | test('returns a light color as default for undefined', () => {
159 | expect(color.getContrastingColor(undefined)).toEqual('#fff')
160 | })
161 | })
162 | })
163 |
164 |
165 | describe('validColorString', () => {
166 | test('checks for valid RGB string', () => {
167 | expect(color.isvalidColorString('23, 32, 3', 'rgb')).toBeTruthy()
168 | expect(color.isvalidColorString('290, 302, 3', 'rgb')).toBeTruthy()
169 | expect(color.isvalidColorString('23', 'rgb')).toBeFalsy()
170 | expect(color.isvalidColorString('230, 32', 'rgb')).toBeFalsy()
171 | })
172 |
173 | test('checks for valid HSL string', () => {
174 | expect(color.isvalidColorString('200, 12, 93', 'hsl')).toBeTruthy()
175 | expect(color.isvalidColorString('200, 12%, 93%', 'hsl')).toBeTruthy()
176 | expect(color.isvalidColorString('200, 120, 93%', 'hsl')).toBeTruthy()
177 | expect(color.isvalidColorString('335°, 64%, 99%', 'hsl')).toBeTruthy()
178 | expect(color.isvalidColorString('100', 'hsl')).toBeFalsy()
179 | expect(color.isvalidColorString('20, 32', 'hsl')).toBeFalsy()
180 | })
181 |
182 |
183 | test('checks for valid HSV string', () => {
184 | expect(color.isvalidColorString('200, 12, 93', 'hsv')).toBeTruthy()
185 | expect(color.isvalidColorString('200, 120, 93%', 'hsv')).toBeTruthy()
186 | expect(color.isvalidColorString('200°, 6%, 100%', 'hsv')).toBeTruthy()
187 | expect(color.isvalidColorString('1', 'hsv')).toBeFalsy()
188 | expect(color.isvalidColorString('20, 32', 'hsv')).toBeFalsy()
189 | expect(color.isvalidColorString('200°, ee3, 100%', 'hsv')).toBeFalsy()
190 | })
191 | })
192 |
--------------------------------------------------------------------------------
/components/react-color/index.js:
--------------------------------------------------------------------------------
1 | export { default as SliderPicker } from './components/slider/Slider';
2 | export { default as TwitterPicker } from './components/twitter/Twitter';
3 |
4 | export { default as CustomPicker } from './components/common/ColorWrap';
5 | export { default as Alpha } from './components/common/Alpha';
6 | export { default as Checkboard } from './components/common/Checkboard';
7 | export { default as EditableInput } from './components/common/EditableInput';
8 | export { default as Hue } from './components/common/Hue';
9 | export { default as Raised } from './components/common/Raised';
10 | export { default as Saturation } from './components/common/Saturation';
11 | export { default as Swatch } from './components/common/Swatch';
12 |
--------------------------------------------------------------------------------
/components/supabase/Auth.js:
--------------------------------------------------------------------------------
1 | import {
2 | LightningBoltIcon,
3 | PencilAltIcon,
4 | RefreshIcon,
5 | } from '@heroicons/react/solid';
6 | import cn from 'classnames';
7 | import { supabase } from 'lib/supabaseClient';
8 | import { useState } from 'react';
9 |
10 | export default function Auth({ text }) {
11 | const [isExpanded, setIsExpanded] = useState(false);
12 | const [loading, setLoading] = useState(false);
13 | const [email, setEmail] = useState('');
14 |
15 | const handleLogin = async (email) => {
16 | try {
17 | setLoading(true);
18 | const { error } = await supabase.auth.signIn(
19 | { email },
20 | { redirectTo: 'https://ogsupa.com/projects' }
21 | );
22 | if (error) throw error;
23 | alert('Check your email for the login link!');
24 | } catch (error) {
25 | alert(error.error_description || error.message);
26 | } finally {
27 | setLoading(false);
28 | }
29 | };
30 |
31 | return (
32 |
33 |
34 |
setIsExpanded(true)}
38 | >
39 |
40 | {text}
41 |
42 |
43 |
44 |
84 |
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "."
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/lib/a11yDark.js:
--------------------------------------------------------------------------------
1 | var _default = {
2 | 'code[class*="language-"]': {
3 | color: '#f8f8f2',
4 | background: 'none',
5 | fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
6 | textAlign: 'left',
7 | whiteSpace: 'pre',
8 | wordSpacing: 'normal',
9 | wordBreak: 'normal',
10 | wordWrap: 'normal',
11 | lineHeight: '1.5',
12 | MozTabSize: '4',
13 | OTabSize: '4',
14 | tabSize: '4',
15 | WebkitHyphens: 'none',
16 | MozHyphens: 'none',
17 | msHyphens: 'none',
18 | hyphens: 'none',
19 | },
20 | 'pre[class*="language-"]': {
21 | color: '#f8f8f2',
22 | background: '#2b2b2b',
23 | fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
24 | textAlign: 'left',
25 | whiteSpace: 'pre',
26 | wordSpacing: 'normal',
27 | wordBreak: 'normal',
28 | wordWrap: 'normal',
29 | lineHeight: '1.5',
30 | MozTabSize: '4',
31 | OTabSize: '4',
32 | tabSize: '4',
33 | WebkitHyphens: 'none',
34 | MozHyphens: 'none',
35 | msHyphens: 'none',
36 | hyphens: 'none',
37 | padding: '1em',
38 | margin: '0.5em 0',
39 | overflow: 'auto',
40 | borderRadius: '0.3em',
41 | },
42 | ':not(pre) > code[class*="language-"]': {
43 | background: '#2b2b2b',
44 | padding: '0.1em',
45 | borderRadius: '0.3em',
46 | whiteSpace: 'normal',
47 | },
48 | comment: {
49 | color: '#d4d0ab',
50 | },
51 | prolog: {
52 | color: '#d4d0ab',
53 | },
54 | doctype: {
55 | color: '#d4d0ab',
56 | },
57 | cdata: {
58 | color: '#d4d0ab',
59 | },
60 | punctuation: {
61 | color: '#fefefe',
62 | },
63 | property: {
64 | color: '#f3b0a0',
65 | },
66 | tag: {
67 | color: '#f3b0a0',
68 | },
69 | constant: {
70 | color: '#f3b0a0',
71 | },
72 | symbol: {
73 | color: '#f3b0a0',
74 | },
75 | deleted: {
76 | color: '#f3b0a0',
77 | },
78 | boolean: {
79 | color: '#00e0e0',
80 | },
81 | number: {
82 | color: '#00e0e0',
83 | },
84 | selector: {
85 | color: '#e6b3e4',
86 | },
87 | 'attr-name': {
88 | color: '#e6b3e4',
89 | },
90 | string: {
91 | color: '#e6b3e4',
92 | },
93 | char: {
94 | color: '#e6b3e4',
95 | },
96 | builtin: {
97 | color: '#e6b3e4',
98 | },
99 | inserted: {
100 | color: '#e6b3e4',
101 | },
102 | operator: {
103 | color: '#00e0e0',
104 | },
105 | entity: {
106 | color: '#00e0e0',
107 | cursor: 'help',
108 | },
109 | url: {
110 | color: '#00e0e0',
111 | },
112 | '.language-css .token.string': {
113 | color: '#00e0e0',
114 | },
115 | '.style .token.string': {
116 | color: '#00e0e0',
117 | },
118 | variable: {
119 | color: '#00e0e0',
120 | },
121 | atrule: {
122 | color: '#ffe228',
123 | },
124 | 'attr-value': {
125 | color: '#ffe228',
126 | },
127 | function: {
128 | color: '#ffe228',
129 | },
130 | keyword: {
131 | color: '#00e0e0',
132 | },
133 | regex: {
134 | color: '#ffe228',
135 | },
136 | important: {
137 | color: '#ffe228',
138 | fontWeight: 'bold',
139 | },
140 | bold: {
141 | fontWeight: 'bold',
142 | },
143 | italic: {
144 | fontStyle: 'italic',
145 | },
146 | };
147 | export default _default;
148 |
--------------------------------------------------------------------------------
/lib/helpers.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useReducer, useState } from 'react';
2 | import { supabase } from './supabaseClient';
3 | import randomWords from 'random-words';
4 |
5 | export function useHasMounted() {
6 | const [hasMounted, setHasMounted] = useState(false);
7 | useEffect(() => {
8 | setHasMounted(true);
9 | }, []);
10 | return hasMounted;
11 | }
12 |
13 | export function useDebounce(initialValue = '', delay) {
14 | const [actualValue, setActualValue] = useState(initialValue);
15 | const [debounceValue, setDebounceValue] = useState(initialValue);
16 | useEffect(() => {
17 | const debounceId = setTimeout(() => setDebounceValue(actualValue), delay);
18 | return () => clearTimeout(debounceId);
19 | }, [actualValue, delay]);
20 | return [debounceValue, setActualValue];
21 | }
22 |
23 | export function useForceUpdate() {
24 | return useReducer(() => ({}), {})[1];
25 | }
26 |
27 | export async function createEmptyProject() {
28 | try {
29 | const user = supabase.auth.user();
30 |
31 | console.log(randomWords({ exactly: 2, wordsPerString: 1 }).join('-'));
32 | const { data, error, status } = await supabase.from('projects').insert([
33 | {
34 | user_id: user.id,
35 | name: randomWords({ exactly: 2, wordsPerString: 1 }).join('-'),
36 | },
37 | ]);
38 |
39 | if (error && status !== 406) {
40 | throw error;
41 | }
42 |
43 | if (Array.isArray(data) && data.length > 0) return data[0];
44 | return data;
45 | } catch (error) {
46 | console.error(error.message);
47 | }
48 | }
49 |
50 | export async function getProject(projectId) {
51 | try {
52 | let { data, error, status } = await supabase
53 | .from('projects')
54 | .select()
55 | .eq('id', projectId)
56 | .single();
57 |
58 | if (error && status !== 406) {
59 | throw error;
60 | }
61 |
62 | return data;
63 | } catch (error) {
64 | console.error(error.message);
65 | }
66 | }
67 |
68 | export async function getProjects() {
69 | try {
70 | let { data, error, status } = await supabase.from('projects').select();
71 |
72 | if (error && status !== 406) {
73 | throw error;
74 | }
75 |
76 | console.log(data);
77 | return data;
78 | } catch (error) {
79 | console.error(error.message);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/supabaseClient.js:
--------------------------------------------------------------------------------
1 | import { createClient } from '@supabase/supabase-js';
2 |
3 | const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
4 | const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
5 |
6 | export const supabase = createClient(supabaseUrl, supabaseAnonKey);
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const { withPlausibleProxy } = require('next-plausible');
2 | module.exports = withPlausibleProxy()({});
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@headlessui/react": "^1.4.1",
10 | "@heroicons/react": "^1.0.4",
11 | "@supabase/supabase-js": "^1.24.0",
12 | "@tailwindcss/aspect-ratio": "^0.2.2",
13 | "@tailwindcss/forms": "^0.3.4",
14 | "@tailwindcss/line-clamp": "^0.2.2",
15 | "chrome-aws-lambda": "^10.1.0",
16 | "classnames": "^2.3.1",
17 | "next": "latest",
18 | "next-plausible": "^2.1.3",
19 | "puppeteer-core": "^10.4.0",
20 | "random-words": "^1.1.1",
21 | "react": "^17.0.2",
22 | "react-color": "^2.19.3",
23 | "react-dom": "^17.0.2",
24 | "react-hot-toast": "^2.1.1",
25 | "react-syntax-highlighter": "^15.4.4",
26 | "styled-components": "^5.3.1"
27 | },
28 | "devDependencies": {
29 | "@types/puppeteer": "^5.4.4",
30 | "@types/puppeteer-core": "^5.4.0",
31 | "@types/react": "^17.0.27",
32 | "autoprefixer": "^10.2.6",
33 | "postcss": "^8.3.9",
34 | "postcss-100vh-fix": "^1.0.2",
35 | "tailwindcss": "^2.2.4",
36 | "typescript": "^4.4.3"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import MainLayout from 'components/layouts/MainLayout';
2 | import PlausibleProvider from 'next-plausible';
3 | import '../styles/index.css';
4 |
5 | function defaultGetLayout(page) {
6 | return {page} ;
7 | }
8 |
9 | function MyApp({ Component, pageProps }) {
10 | const getLayout = Component.getLayout || defaultGetLayout;
11 |
12 | return getLayout(
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default MyApp;
20 |
--------------------------------------------------------------------------------
/pages/api/_lib/chromium.js:
--------------------------------------------------------------------------------
1 | import core from 'puppeteer-core';
2 | import { getOptions } from './options';
3 | let _page;
4 |
5 | async function getPage(isDev) {
6 | if (_page) {
7 | return _page;
8 | }
9 | const options = await getOptions(isDev);
10 | const browser = await core.launch(options);
11 | _page = await browser.newPage();
12 | return _page;
13 | }
14 |
15 | export async function getScreenshot(url, type, isDev) {
16 | const page = await getPage(isDev);
17 | await page.setViewport({ width: 2048, height: 1024, deviceScaleFactor: 2 });
18 | await page.goto(url, {
19 | waitUntil: 'networkidle0',
20 | });
21 | const element = await page.$('#preview');
22 | return await element.screenshot({ type });
23 | }
24 |
--------------------------------------------------------------------------------
/pages/api/_lib/options.js:
--------------------------------------------------------------------------------
1 | import chrome from 'chrome-aws-lambda';
2 | const exePath =
3 | process.platform === 'win32'
4 | ? 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
5 | : process.platform === 'linux'
6 | ? '/usr/bin/google-chrome'
7 | : '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
8 |
9 | export async function getOptions(isDev) {
10 | let options;
11 | if (isDev) {
12 | options = {
13 | args: [],
14 | executablePath: exePath,
15 | headless: true,
16 | };
17 | } else {
18 | options = {
19 | args: chrome.args,
20 | executablePath: await chrome.executablePath,
21 | headless: chrome.headless,
22 | };
23 | }
24 | return options;
25 | }
26 |
--------------------------------------------------------------------------------
/pages/api/_lib/parser.js:
--------------------------------------------------------------------------------
1 | import { IncomingMessage } from 'http';
2 | import { parse } from 'url';
3 | import { ParsedRequest, Theme } from './types';
4 |
5 | export function parseRequest(req) {
6 | const { pathname, query } = parse(req.url || '/', true);
7 | return query || {};
8 |
9 | // if (Array.isArray(fontSize)) {
10 | // throw new Error('Expected a single fontSize');
11 | // }
12 | // if (Array.isArray(theme)) {
13 | // throw new Error('Expected a single theme');
14 | // }
15 |
16 | // const arr = (pathname || '/').slice(1).split('.');
17 | // let extension = '';
18 | // let text = '';
19 | // if (arr.length === 0) {
20 | // text = '';
21 | // } else if (arr.length === 1) {
22 | // text = arr[0];
23 | // } else {
24 | // extension = arr.pop() as string;
25 | // text = arr.join('.');
26 | // }
27 |
28 | // const parsedRequest: ParsedRequest = {
29 | // fileType: extension === 'jpeg' ? extension : 'png',
30 | // text: decodeURIComponent(text),
31 | // theme: theme === 'dark' ? 'dark' : 'light',
32 | // md: md === '1' || md === 'true',
33 | // fontSize: fontSize || '96px',
34 | // images: getArray(images),
35 | // widths: getArray(widths),
36 | // heights: getArray(heights),
37 | // };
38 | // parsedRequest.images = getDefaultImages(
39 | // parsedRequest.images,
40 | // parsedRequest.theme
41 | // );
42 | // return parsedRequest;
43 | }
44 |
45 | // function getArray(stringOrArray: string[] | string | undefined): string[] {
46 | // if (typeof stringOrArray === 'undefined') {
47 | // return [];
48 | // } else if (Array.isArray(stringOrArray)) {
49 | // return stringOrArray;
50 | // } else {
51 | // return [stringOrArray];
52 | // }
53 | // }
54 |
55 | // function getDefaultImages(images: string[], theme: Theme): string[] {
56 | // const defaultImage =
57 | // theme === 'light'
58 | // ? 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-black.svg'
59 | // : 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-white.svg';
60 |
61 | // if (!images || !images[0]) {
62 | // return [defaultImage];
63 | // }
64 | // if (
65 | // !images[0].startsWith('https://assets.vercel.com/') &&
66 | // !images[0].startsWith('https://assets.zeit.co/')
67 | // ) {
68 | // images[0] = defaultImage;
69 | // }
70 | // return images;
71 | // }
72 |
--------------------------------------------------------------------------------
/pages/api/_lib/sanitizer.js:
--------------------------------------------------------------------------------
1 | const entityMap = {
2 | '&': '&',
3 | '<': '<',
4 | '>': '>',
5 | '"': '"',
6 | "'": ''',
7 | '/': '/',
8 | };
9 |
10 | export function sanitizeHtml(html) {
11 | return String(html).replace(/[&<>"'\/]/g, (key) => entityMap[key]);
12 | }
13 |
--------------------------------------------------------------------------------
/pages/api/_lib/types.js:
--------------------------------------------------------------------------------
1 | // export type FileType = 'png' | 'jpeg';
2 | // export type Theme = 'light' | 'dark';
3 |
4 | // export interface ParsedRequest {
5 | // fileType: FileType;
6 | // text: string;
7 | // theme: Theme;
8 | // md: boolean;
9 | // fontSize: string;
10 | // images: string[];
11 | // widths: string[];
12 | // heights: string[];
13 | // }
14 |
--------------------------------------------------------------------------------
/pages/api/v1.js:
--------------------------------------------------------------------------------
1 | import { parse } from 'url';
2 | import { getScreenshot } from './_lib/chromium';
3 |
4 | const IS_DEV = !process.env.AWS_REGION;
5 | const IS_HTML_DEBUG = process.env.OG_HTML_DEBUG === '1';
6 |
7 | const HOST = 'https://ogsupa.com/';
8 |
9 | export default async function handler(req, res) {
10 | try {
11 | console.log('HTTP ' + req.url);
12 | const url = parse(req.url || '/', true);
13 | const queryString = new URLSearchParams(url.query).toString();
14 |
15 | const fileType = url.query.fileType || 'png';
16 |
17 | const file = await getScreenshot(
18 | `${HOST}/preview?${queryString}`,
19 | fileType,
20 | IS_DEV
21 | );
22 | res.statusCode = 200;
23 | res.setHeader('Content-Type', `image/${fileType}`);
24 | // res.setHeader(
25 | // 'Cache-Control',
26 | // `public, immutable, no-transform, s-maxage=31536000, max-age=31536000`
27 | // );
28 | res.end(file);
29 | } catch (e) {
30 | res.statusCode = 500;
31 | res.setHeader('Content-Type', 'text/html');
32 | res.end('Internal Error Sorry, there was a problem
');
33 | console.error(e);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import { StarIcon } from '@heroicons/react/solid';
2 | import Sparkles from 'components/Sparkles';
3 | import Start from 'components/Start';
4 | import { supabase } from 'lib/supabaseClient';
5 | import { useEffect, useState } from 'react';
6 |
7 | export default function IndexPage() {
8 | const [session, setSession] = useState(null);
9 |
10 | useEffect(() => {
11 | setSession(supabase.auth.session());
12 |
13 | supabase.auth.onAuthStateChange((event, session) => {
14 | console.log('onAuthStateChange', event);
15 | setSession(session);
16 | });
17 | }, []);
18 |
19 | return (
20 | <>
21 |
22 | Generate og:images for free!
23 |
24 |
25 |
32 |
33 | ⚡️
34 |
35 |
36 | 🎉
37 | Empty link previews are a thing of the past!
38 |
39 |
40 | 🎨
41 | Generate customizable og:images in realtime
42 |
43 |
44 | 🚀
45 | Save many different project styles for editing
46 |
47 |
48 |
49 |
50 |
51 |
56 |
57 |
58 |
59 | meet og:supa
60 |
61 |
62 | og:supa is an open-source og:image generator! All you have to do is
63 | add our{' '}
64 |
65 | {' '}
66 |
{' '}
67 | tag to your HTML{' '}
68 |
69 | {''}
70 |
{' '}
71 | and you're good to go!
72 |
73 |
74 |
75 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | ℹ️
93 |
94 |
95 | og:images are
96 | used to show preview thumbnails in social media. They‘re very
97 | important, if you want to{' '}
98 |
99 | stand out
100 | {' '}
101 | in a sea of feeds. Too often we see an empty link preview and lose
102 | interest for it‘s content. Sadly, our brains have been trained to
103 | spot color in hoards of text. So, grab a
104 | color that pops
105 | for a good start!
106 |
107 |
108 |
109 |
110 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | >
122 | );
123 | }
124 |
--------------------------------------------------------------------------------
/pages/preview.js:
--------------------------------------------------------------------------------
1 | import OGPreview from 'components/OGPreview';
2 | import { useRouter } from 'next/dist/client/router';
3 | import Head from 'next/head';
4 | import HeadFonts from '../components/HeadFonts';
5 |
6 | const PreviewPage = () => {
7 | const router = useRouter();
8 |
9 | // todo: query to short props
10 |
11 | return (
12 | <>
13 |
14 |
15 |
16 |
17 | >
18 | );
19 | };
20 |
21 | PreviewPage.getLayout = (page) => page;
22 |
23 | export default PreviewPage;
24 |
--------------------------------------------------------------------------------
/pages/projects/[projectId].js:
--------------------------------------------------------------------------------
1 | import Editor from 'components/Editor';
2 | import ProjectSwitcher from 'components/ProjectSwitcher';
3 | import { getProjects, useForceUpdate } from 'lib/helpers';
4 | import { useRouter } from 'next/dist/client/router';
5 | import Head from 'next/head';
6 | import { useEffect, useState } from 'react';
7 |
8 | const ProjectPage = () => {
9 | const router = useRouter();
10 | const refreshProjectPage = useForceUpdate();
11 | const [projects, setProjects] = useState(null);
12 |
13 | const { projectId } = router.query;
14 |
15 | useEffect(async () => setProjects(await getProjects()), [projectId]);
16 |
17 | if (!projects) return null;
18 |
19 | const selectedProject = projects.find((p) => p.id == projectId);
20 |
21 | return (
22 | <>
23 |
24 |
28 |
32 |
36 |
37 | {projects && (
38 | <>
39 |
44 |
45 | >
46 | )}
47 | >
48 | );
49 | };
50 |
51 | export default ProjectPage;
52 |
--------------------------------------------------------------------------------
/pages/projects/index.js:
--------------------------------------------------------------------------------
1 | import Loading from 'components/Loading';
2 | import { createEmptyProject, getProjects, useHasMounted } from 'lib/helpers';
3 | import { supabase } from 'lib/supabaseClient';
4 | import { useRouter } from 'next/dist/client/router';
5 | import { useEffect, useState } from 'react';
6 |
7 | const ProjectPage = () => {
8 | const router = useRouter();
9 | const hasMounted = useHasMounted();
10 | const [session, setSession] = useState(null);
11 |
12 | useEffect(() => {
13 | setSession(supabase.auth.session());
14 |
15 | supabase.auth.onAuthStateChange((event, session) => {
16 | console.log('onAuthStateChange', event);
17 | setSession(session);
18 | });
19 | }, []);
20 |
21 | useEffect(async () => {
22 | if (!session) return;
23 |
24 | const projects = await getProjects();
25 | if (projects?.length > 0) {
26 | router.push(`/projects/${projects[0].id}`);
27 | return;
28 | }
29 | const newProject = await createEmptyProject();
30 | router.push(`/projects/${newProject.id}`);
31 | }, [session]);
32 |
33 | if (!hasMounted) return null;
34 |
35 | return ;
36 | };
37 |
38 | export default ProjectPage;
39 |
--------------------------------------------------------------------------------
/pages/projects/new.js:
--------------------------------------------------------------------------------
1 | import Loading from 'components/Loading';
2 | import { createEmptyProject, useHasMounted } from 'lib/helpers';
3 | import { useRouter } from 'next/dist/client/router';
4 | import { useEffect } from 'react';
5 |
6 | const NewProjectPage = () => {
7 | const router = useRouter();
8 | const hasMounted = useHasMounted();
9 |
10 | useEffect(async () => {
11 | const newProject = await createEmptyProject();
12 | router.push(`/projects/${newProject.id}`);
13 | }, []);
14 |
15 | if (!hasMounted) return null;
16 |
17 | return ;
18 | };
19 |
20 | export default NewProjectPage;
21 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | 'postcss-100vh-fix': {},
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/favicon.ico
--------------------------------------------------------------------------------
/public/gd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/gd.png
--------------------------------------------------------------------------------
/public/geometry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/geometry.png
--------------------------------------------------------------------------------
/public/geometry_@2X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/geometry_@2X.png
--------------------------------------------------------------------------------
/public/geot@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/geot@2x.png
--------------------------------------------------------------------------------
/public/gl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/gl.png
--------------------------------------------------------------------------------
/public/gp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/gp.png
--------------------------------------------------------------------------------
/public/light_toast-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/light_toast-50.png
--------------------------------------------------------------------------------
/public/light_toast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/light_toast.png
--------------------------------------------------------------------------------
/public/light_toast_@2X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/light_toast_@2X.png
--------------------------------------------------------------------------------
/public/no-og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/no-og.png
--------------------------------------------------------------------------------
/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/og.png
--------------------------------------------------------------------------------
/public/ogimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogimage.png
--------------------------------------------------------------------------------
/public/ogsupa-MI-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MI-shadow.png
--------------------------------------------------------------------------------
/public/ogsupa-MI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MI.png
--------------------------------------------------------------------------------
/public/ogsupa-MII-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MII-shadow.png
--------------------------------------------------------------------------------
/public/ogsupa-MIII-shadow-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MIII-shadow-alt.png
--------------------------------------------------------------------------------
/public/ogsupa-MIII-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MIII-shadow.png
--------------------------------------------------------------------------------
/public/ogsupa-MIV-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MIV-shadow.png
--------------------------------------------------------------------------------
/public/ogsupa-MV-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MV-shadow.png
--------------------------------------------------------------------------------
/public/ogsupa-MV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/ogsupa-MV.png
--------------------------------------------------------------------------------
/public/playstation-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/janniks/ogsupa/93cc0f76298214555cf42f218dd9ca1e62d0b6e4/public/playstation-pattern.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/styles/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | /* Write your own custom base styles here */
4 |
5 | /* Start purging... */
6 | @tailwind components;
7 | /* Stop purging. */
8 |
9 | html,
10 | body {
11 | /* @apply bg-gray-500 dark:bg-gray-900; */
12 | }
13 |
14 | /* Write your own custom component styles here */
15 | .btn-blue {
16 | @apply px-4 py-2 font-bold text-white bg-blue-500 rounded;
17 | }
18 |
19 | /* Start purging... */
20 | @tailwind utilities;
21 | /* Stop purging. */
22 |
23 | /* Your own custom utilities */
24 | .font-sans {
25 | font-family: 'Inter', ui-sans-serif, system-ui, -apple-system,
26 | BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
27 | sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
28 | 'Noto Color Emoji';
29 | }
30 | .font-serif {
31 | font-family: Georgia, Cambria, 'Times New Roman', Times, serif;
32 | }
33 | .font-mono {
34 | font-family: 'IBM Plex Mono', 'Noto Sans Mono', ui-monospace, SFMono-Regular,
35 | Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
36 | }
37 | .font-title {
38 | font-size: 1.25em;
39 | line-height: 1.25em;
40 | padding-top: 0.375em;
41 | margin-bottom: 0.375em;
42 | font-weight: bold;
43 | }
44 | .font-title.font-serif {
45 | transform: scaleY(0.95);
46 | transform-origin: 0 0;
47 | line-height: 1.25em;
48 | margin-bottom: 0.1em;
49 | }
50 | .font-title.font-sans {
51 | line-height: 1.075em;
52 | padding-bottom: 0.13em;
53 | margin-bottom: 0.28em;
54 | letter-spacing: 0.02em;
55 | }
56 | .font-title.font-mono {
57 | font-size: 1.15em;
58 | line-height: 1.175em;
59 | padding-top: 0.45em;
60 | margin-bottom: 0.4em;
61 | letter-spacing: -0.04em;
62 | }
63 | .font-description {
64 | font-size: 0.75em;
65 | line-height: 1.25em;
66 | }
67 | .font-meta {
68 | font-size: 0.5em;
69 | line-height: 1.25em;
70 | }
71 |
72 | .patterns {
73 | /* background-image: url('/geometry.png'); */
74 | /* background-image: url('/geot@2x.png'); */
75 | /* background-image: url('/gl.png'); */
76 | background-image: url('/gp.png');
77 | /* background-size: 48rem; */
78 |
79 | /* background-image: url('/playstation-pattern.png'); */
80 | /* background-size: 32rem; */
81 |
82 | /* background-image: url('/light_toast-50.png'); */
83 | /* background-size: 32rem; */
84 | }
85 |
86 | div.background {
87 | position: fixed;
88 | top: 0;
89 | left: 0;
90 | min-height: 100vh;
91 | width: 100%;
92 | background: repeating-linear-gradient(
93 | -55deg,
94 | #fff6f4,
95 | #fff6f4 10px,
96 | #fff2ee 10px,
97 | #fff2ee 20px
98 | );
99 | }
100 |
101 | .box {
102 | box-shadow: 0 0 0 0 white, 0 2px 2px rgba(0, 0, 0, 0.05),
103 | 0 0 10px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.15),
104 | 0 6px 10px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.15),
105 | 0 20px 20px rgba(0, 0, 0, 0.2), 0 40px 40px rgba(0, 0, 0, 0.15);
106 | }
107 |
108 | .box-sm {
109 | box-shadow: 0 0 0 0 white, 0 2px 2px rgba(0, 0, 0, 0.05),
110 | 0 0 10px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.15),
111 | 0 6px 10px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.15);
112 | }
113 |
114 | .box-pink {
115 | box-shadow: 0 0 0 0 white, 0 2px 2px rgba(55, 0, 55, 0.05),
116 | 0 0 10px rgba(55, 0, 55, 0.01), 0 2px 6px rgba(55, 0, 55, 0.1),
117 | 0 6px 10px rgba(55, 0, 55, 0.01), 0 10px 10px rgba(55, 0, 55, 0.1),
118 | 0 20px 15px rgba(55, 0, 55, 0.1), 0 40px 30px rgba(55, 0, 55, 0.05);
119 | }
120 |
121 | .box-pink-sm {
122 | box-shadow: 0 0 0 0 white, 0 2px 2px rgba(55, 0, 55, 0.05),
123 | 0 0 10px rgba(55, 0, 55, 0.01), 0 2px 6px rgba(55, 0, 55, 0.1),
124 | 0 6px 10px rgba(55, 0, 55, 0.01), 0 10px 20px rgba(55, 0, 55, 0.05);
125 | }
126 |
127 | .og {
128 | border: 8px solid black;
129 | box-shadow: -8px 0 0 0 #7801fd, -16px 0 0 0 #f804ef, -24px 0 0 0 #f3b0a0;
130 | }
131 | .og-parent {
132 | box-shadow: 0 0 0 8px white;
133 | }
134 |
135 | .input {
136 | border: 2px solid #f3b0a0;
137 | }
138 | .input:focus {
139 | border: 2px solid #7801fd;
140 | }
141 |
142 | .underline-heart {
143 | background-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/78779/heart.png');
144 | background-position: 2px 16px;
145 | background-size: 15px 9px;
146 | background-repeat: repeat-x;
147 | padding-bottom: 20px;
148 | color: #e62000;
149 | }
150 |
151 | body {
152 | /* Always show scrollbar, not super nice, but better than layout shift on load */
153 | overflow-y: scroll;
154 | }
155 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
4 | // darkMode: 'class',
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [
12 | require('@tailwindcss/forms'),
13 | require('@tailwindcss/aspect-ratio'),
14 | require('@tailwindcss/line-clamp'),
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------