Skip to content

Commit 1152024

Browse files
Theme generator - appColorPicker
1 parent fb78a4a commit 1152024

53 files changed

Lines changed: 806 additions & 453 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"@types/mustache": "^4.2.2",
6666
"@types/node": "^18.16.17",
6767
"@types/react": "^18.2.38",
68+
"@types/react-color": "^3.0.10",
6869
"@types/react-dom": "^18.2.17",
6970
"@types/react-helmet": "^6.1.9",
7071
"@types/react-table": "^7.7.18",
@@ -90,6 +91,7 @@
9091
"react-app-polyfill": "^3.0.0",
9192
"react-avatar": "^5.0.3",
9293
"react-bootstrap": "^2.9.1",
94+
"react-color": "^2.19.3",
9395
"react-dom": "^18.2.0",
9496
"react-dropzone": "^14.2.3",
9597
"react-helmet": "^6.1.0",

src/common/hooks/use-app-theme.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ export function useAppTheme(options: UseAppThemeType) {
5151

5252
const getThemeData = useCallback(
5353
(currentTheme: string) => {
54-
if (!isEmpty(appStore.configuration.previewTheme)) {
55-
return appStore.configuration.previewTheme
56-
}
54+
// if (!isEmpty(appStore.configuration.previewTheme)) {
55+
// return appStore.configuration.previewTheme
56+
// }
5757

5858
if (appTheme) {
5959
const index = appTheme.findIndex((i: any) => Object.keys(i)[0] === currentTheme)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { css } from '@emotion/react'
2+
import { ThemeType, get } from '../_theme'
3+
4+
export const colorPicker = (theme: ThemeType) => css`
5+
display: inline-flex;
6+
align-items: center;
7+
padding: 4px;
8+
background: ${get(theme, `ColorPicker.background`)};
9+
border-radius: 8px;
10+
cursor: pointer;
11+
border: 1px solid ${get(theme, `ColorPicker.borderColor`)};
12+
`
13+
14+
export const color = css`
15+
width: 40px;
16+
height: 16px;
17+
border-radius: 8px;
18+
`
19+
20+
export const hex = css`
21+
width: 115px;
22+
`
23+
24+
export const rgba = css`
25+
width: 180px;
26+
`
27+
28+
export const label = (theme: ThemeType) => css`
29+
font-size: 12px;
30+
display: block;
31+
padding: 0 8px;
32+
color: ${get(theme, `ColorPicker.label.color`)};
33+
`
34+
35+
export const floatingMenu = (theme: ThemeType) => css`
36+
z-index: 10;
37+
border: 1px solid ${get(theme, `ColorPicker.floatingMenu.borderColor`)};
38+
box-shadow: 0 30px 40px rgba(28, 52, 99, 0.1);
39+
border-radius: 8px;
40+
overflow: hidden;
41+
`
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
2+
import { createPortal } from 'react-dom'
3+
import { ChromePicker, ColorResult, RGBColor } from 'react-color'
4+
import { offset, shift, useFloating } from '@floating-ui/react'
5+
import { useTheme } from '@emotion/react'
6+
import isFunction from 'lodash/isFunction'
7+
8+
import { Props, defaultProps } from './ColorPicker.types'
9+
import * as styles from './ColorPicker.styles'
10+
import { get } from '../_theme'
11+
import { rgbToHex, hexToRgbA } from '../_utils/commonStyles'
12+
import { colors } from '../_utils/colors'
13+
14+
const ColorPicker: FC<Props> = (props) => {
15+
const { defaultColor: defaultColorProps, className, id, menuProps, portalTarget, onToggle, onColorChange } = { ...defaultProps, ...props }
16+
const defaultColor: RGBColor = useMemo(() => {
17+
if (typeof defaultColorProps === 'string') {
18+
if (defaultColorProps.length === 7) {
19+
// hex color
20+
return hexToRgbA(defaultColorProps, 1, true) as RGBColor
21+
} else {
22+
// rgba color
23+
const parsed = defaultColorProps.split('(')[1].split(')')[0].split(',')
24+
return {
25+
r: parseInt(parsed[0]),
26+
g: parseInt(parsed[1]),
27+
b: parseInt(parsed[2]),
28+
a: parseFloat(parsed[3]),
29+
}
30+
}
31+
} else if (defaultColorProps) {
32+
return defaultColorProps
33+
}
34+
35+
return hexToRgbA(colors.primary, 1, true) as RGBColor
36+
}, [defaultColorProps])
37+
38+
const [color, setColor] = useState<RGBColor>(defaultColor)
39+
const [open, setOpen] = useState(false)
40+
41+
useEffect(() => {
42+
setColor(defaultColor)
43+
}, [defaultColor])
44+
45+
const { x, y, refs, strategy } = useFloating({
46+
placement: menuProps?.placement || 'bottom-start',
47+
strategy: 'fixed',
48+
middleware: [shift(), offset(4)],
49+
})
50+
51+
const ref = useRef(null)
52+
const theme = useTheme()
53+
54+
const getValue = useCallback((c: RGBColor) => (c.a !== 1 ? `rgba(${c.r},${c.g},${c.b},${c.a})` : rgbToHex(c.r, c.g, c.b)), [])
55+
56+
const handleChange = useCallback(
57+
(color: ColorResult) => {
58+
setColor(color.rgb)
59+
isFunction(onColorChange) && onColorChange(getValue(color.rgb))
60+
},
61+
[getValue, onColorChange]
62+
)
63+
64+
useEffect(() => {
65+
function handleClickOutside(event: any) {
66+
// @ts-ignore
67+
if (ref?.current && !ref?.current?.contains(event.target)) {
68+
setOpen(false)
69+
isFunction(onToggle) && onToggle(false)
70+
}
71+
}
72+
if (open) {
73+
document.addEventListener('mousedown', handleClickOutside)
74+
return () => {
75+
document.removeEventListener('mousedown', handleClickOutside)
76+
}
77+
}
78+
}, [ref, open, onToggle])
79+
80+
const FloatingPanel = (
81+
<div
82+
css={styles.floatingMenu}
83+
ref={refs.setFloating}
84+
style={{
85+
position: strategy,
86+
top: y ?? 0,
87+
left: x ?? 0,
88+
width: 'max-content',
89+
}}
90+
>
91+
<ChromePicker
92+
color={color}
93+
onChange={handleChange}
94+
styles={{
95+
default: {
96+
body: {
97+
background: get(theme, `ColorPicker.background`),
98+
},
99+
},
100+
}}
101+
/>
102+
</div>
103+
)
104+
105+
return (
106+
<div className={className} id={id} ref={ref}>
107+
<div
108+
css={[styles.colorPicker, getValue(color).startsWith('#') ? styles.hex : styles.rgba]}
109+
onClick={() => setOpen((prev) => !prev)}
110+
ref={refs.setReference}
111+
>
112+
<div
113+
css={styles.color}
114+
style={{
115+
background: getValue(color),
116+
}}
117+
/>
118+
{color ? <span css={styles.label}>{getValue(color)}</span> : null}
119+
</div>
120+
{open && portalTarget && createPortal(FloatingPanel, portalTarget as Element)}
121+
{open && !portalTarget && FloatingPanel}
122+
</div>
123+
)
124+
}
125+
126+
ColorPicker.displayName = 'ColorPicker'
127+
ColorPicker.defaultProps = defaultProps
128+
129+
export default ColorPicker
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ReactNode, SyntheticEvent } from 'react'
2+
import { Placement } from '@floating-ui/react-dom-interactions'
3+
import { RGBColor } from 'react-color'
4+
5+
export type Props = {
6+
className?: string
7+
defaultColor?: RGBColor | string
8+
id?: string
9+
menuProps?: {
10+
placement?: Placement
11+
}
12+
onToggle?: (isOpen: boolean, event: SyntheticEvent<any>) => void
13+
onColorChange?: (color: string) => void
14+
portalTarget?: ReactNode | Element | null
15+
}
16+
17+
export const defaultProps = {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default } from './ColorPicker'
2+
export * from './ColorPicker'

src/components/Atomic/Editor/Editor.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-nocheck
2-
import React, { FC, forwardRef, MutableRefObject, useEffect, useImperativeHandle, useRef, useState } from 'react'
2+
import React, { forwardRef, MutableRefObject, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
33
import classNames from 'classnames'
44
import JSONEditor from 'jsoneditor'
55
import 'jsoneditor/dist/jsoneditor.css'
@@ -20,6 +20,7 @@ const Editor = forwardRef<EditorRefType, Props>((props, ref) => {
2020
i18n,
2121
json,
2222
mode,
23+
onBlur,
2324
onChange,
2425
onError,
2526
onResize,
@@ -139,6 +140,10 @@ const Editor = forwardRef<EditorRefType, Props>((props, ref) => {
139140
}
140141
}, [])
141142

143+
const handleBlur = useCallback(() => {
144+
isFunction(onBlur) && onBlur(jsonEditor?.current?.getText())
145+
}, [onBlur])
146+
142147
useImperativeHandle(ref, () => ({
143148
setValue: (value) => {
144149
if (typeof json === 'object') {
@@ -159,6 +164,7 @@ const Editor = forwardRef<EditorRefType, Props>((props, ref) => {
159164
resize: !!ResizeObserver,
160165
})}
161166
css={styles.editor}
167+
onBlur={handleBlur}
162168
ref={(ref) => handleContainerRef(ref)}
163169
style={{ ...style, width, height }}
164170
>

src/components/Atomic/Editor/Editor.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type Props = {
1616
height?: string
1717
json: string | [] | object
1818
mode?: EditorModeType
19+
onBlur?: (value: any) => void
1920
onChange?: (json: any) => void
2021
onError?: (error: any) => void
2122
onResize?: (width: number, height: number, callback: () => void) => void
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Auto-generated file created by svgr-cli source svg-template.js
2+
// Do not edit directly
3+
import * as React from 'react'
4+
import type { SVGProps } from 'react'
5+
import { Ref, forwardRef, memo } from 'react'
6+
const SvgIconCertificate = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
7+
<svg xmlns='http://www.w3.org/2000/svg' width={props.width || 16} height={props.height || 16} fill='none' viewBox='0 0 16 16' ref={ref} {...props}>
8+
<path
9+
fill='#81868C'
10+
d='M7.495 1.47a.967.967 0 0 1 1.01 0L9 1.776a.973.973 0 0 0 .558.141l.591-.034c.367-.02.711.167.875.474l.267.497c.088.167.233.3.408.383l.536.253a.853.853 0 0 1 .505.82l-.036.555a.824.824 0 0 0 .15.523l.328.464a.815.815 0 0 1 0 .947l-.328.467a.829.829 0 0 0-.15.523l.036.555a.853.853 0 0 1-.505.82l-.53.25a.895.895 0 0 0-.409.383l-.27.502a.929.929 0 0 1-.874.474l-.591-.034a.969.969 0 0 0-.559.14l-.494.308a.967.967 0 0 1-1.01 0L7 10.88a.974.974 0 0 0-.558-.14l-.591.033a.929.929 0 0 1-.875-.474l-.267-.497a.895.895 0 0 0-.408-.383l-.536-.253a.853.853 0 0 1-.505-.82l.036-.554a.824.824 0 0 0-.15-.524l-.325-.466a.815.815 0 0 1 0-.948l.325-.463a.828.828 0 0 0 .15-.524l-.036-.554a.853.853 0 0 1 .505-.82l.53-.25a.912.912 0 0 0 .412-.389l.266-.497a.929.929 0 0 1 .875-.474l.591.034c.198.01.395-.04.559-.14l.497-.305m2.727 4.857a2.02 2.02 0 0 0-.651-1.473A2.299 2.299 0 0 0 8 4.244c-.59 0-1.154.22-1.57.61-.417.39-.651.92-.651 1.473s.234 1.083.65 1.473c.417.39.982.61 1.571.61.59 0 1.154-.22 1.57-.61.417-.39.652-.92.652-1.473m-7.517 6.505 1.196-2.67a.021.021 0 0 1 .011.011l.267.498c.325.604 1 .97 1.722.932l.591-.034c.006 0 .014 0 .02.005l.494.308c.142.085.291.153.447.2l-1.044 2.325a.438.438 0 0 1-.37.253.452.452 0 0 1-.41-.188l-.895-1.283-1.557.216a.469.469 0 0 1-.417-.156.394.394 0 0 1-.058-.417h.003m6.886 1.573-1.044-2.323c.156-.047.305-.112.447-.2l.494-.308a.042.042 0 0 1 .02-.005l.591.034c.722.039 1.397-.328 1.722-.932l.267-.498a.021.021 0 0 1 .01-.01l1.2 2.669a.4.4 0 0 1-.058.417.453.453 0 0 1-.417.156l-1.557-.216-.895 1.28a.451.451 0 0 1-.41.188.443.443 0 0 1-.37-.252'
11+
/>
12+
</svg>
13+
)
14+
const ForwardRef = forwardRef(SvgIconCertificate)
15+
const Memo = memo(ForwardRef)
16+
export default Memo

0 commit comments

Comments
 (0)