Last active
June 3, 2023 04:25
-
-
Save lxchurbakov/b47087342fe16cc5d2b1ea82ef241178 to your computer and use it in GitHub Desktop.
minimal-ui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import styled, { css } from 'styled-components'; | |
type PropsOf<T> = T extends React.FC<infer P> ? P : never; | |
export type BaseProps = { | |
p?: string; | |
pt?: string; | |
pl?: string; | |
pr?: string; | |
pb?: string; | |
m?: string; | |
mt?: string; | |
mr?: string; | |
mb?: string; | |
ml?: string; | |
w?: string; | |
h?: string; | |
mw?: string; | |
mh?: string; | |
}; | |
export const Base = styled.div<BaseProps>` | |
padding: ${props => props.p}; | |
padding-top: ${props => props.pt}; | |
padding-right: ${props => props.pr}; | |
padding-left: ${props => props.pl}; | |
padding-bottom: ${props => props.pb}; | |
margin: ${props => props.m}; | |
margin-top: ${props => props.mt}; | |
margin-right: ${props => props.mr}; | |
margin-left: ${props => props.ml}; | |
margin-bottom: ${props => props.mb}; | |
width: ${props => props.w}; | |
height: ${props => props.h}; | |
max-width: ${props => props.mw}; | |
max-height: ${props => props.mh}; | |
box-sizing: border-box; | |
`; | |
export const Card = styled(Base)<{ color?: string }>` | |
background: ${props => props.color || '#f5f5f5'}; | |
border-radius: 4px; | |
`; | |
export const Flex = styled(Base)<{ | |
direction?: 'row' | 'column'; | |
align?: 'center' | 'flex-start' | 'flex-end'; | |
justify?: 'center' | 'flex-start' | 'flex-end' | 'space-around' | 'space-between'; | |
wrap?: boolean; | |
gap?: number; | |
}>` | |
display: flex; | |
flex-direction: ${props => props.dir || 'row'}; | |
align-items: ${props => props.align || 'center'}; | |
justify-content: ${props => props.justify || 'center'}; | |
flex-wrap: ${props => props.wrap ? 'wrap' : 'nowrap'}; | |
gap: ${props => props.gap || 0}px; | |
`; | |
export const Text = styled(Base)<{ size: number, weight: number, color: string }>` | |
font-family: Inter; | |
font-size: ${props => props.size}px; | |
font-weight: ${props => props.weight}; | |
color: ${props => props.color}; | |
line-height: 1.6; | |
text-align: ${props => props.align}; | |
`; | |
export const Image = styled.img<BaseProps>` | |
padding: ${props => props.p}; | |
padding-top: ${props => props.pt}; | |
padding-right: ${props => props.pr}; | |
padding-left: ${props => props.pl}; | |
padding-bottom: ${props => props.pb}; | |
margin: ${props => props.m}; | |
margin-top: ${props => props.mt}; | |
margin-right: ${props => props.mr}; | |
margin-left: ${props => props.ml}; | |
margin-bottom: ${props => props.mb}; | |
width: ${props => props.w}; | |
height: ${props => props.h}; | |
max-width: ${props => props.mw}; | |
max-height: ${props => props.mh}; | |
object-fit: cover; | |
box-sizing: border-box; | |
`; | |
export const Clickable = styled(Card)<{ color?: string, border?: string, radius?: string }>` | |
background: ${props => props.color || 'none'}; | |
cursor: pointer; | |
user-select: none; | |
display: inline-block; | |
border: ${props => props.border || 'none'}; | |
border-radius: ${props => props.radius || '0px'}; | |
&:active { | |
transform: translateY(1px); | |
} | |
`; | |
export const Container = ({ children, ...props }: { children: any } & PropsOf<typeof Flex>) => { | |
return ( | |
<Flex {...props}> | |
<Base p="100px 20px" w="100%" mw="1200px"> | |
{children} | |
</Base> | |
</Flex> | |
) | |
}; | |
export const Disabled = styled(Base)<{ disabled: boolean }>` | |
${props => props.disabled && css` | |
opacity: .5; | |
pointer-events: none; | |
`}; | |
`; | |
export const Table = styled(Base)` | |
border-radius: 8px; | |
overflow: hidden; | |
`; | |
export const Row = styled(Flex).attrs({ | |
align: 'center', | |
justify: 'flex-start', | |
w: '100%' | |
})` | |
&:nth-child(2n+1) { | |
background: #E4EAF5; | |
} | |
`; | |
export const Item = styled(Flex)` | |
padding: 8px 16px; | |
`; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import styled, { css } from 'styled-components'; | |
import { PropsOf } from '/src/libs/utils'; | |
import { ArrowDownShort } from '@styled-icons/bootstrap/ArrowDownShort'; | |
import { CloseOutline } from '@styled-icons/evaicons-outline/CloseOutline'; | |
import { BaseProps, Base, Flex, Card, Text } from './atoms'; | |
import { colors } from '/src/libs/theme'; | |
type StyledProps = { className?: string, style?: React.CSSProperties }; | |
type CursorOption = 'pointer' | 'text'; | |
const InputWrap = styled(Flex)<{ cursor: CursorOption }>` | |
background: ${colors.blueLight}; | |
padding: 12px 16px; | |
cursor: ${props => props.cursor}; | |
border-radius: 8px; | |
`; | |
const InputCore = styled.input<{ hasPrefix: boolean, cursor: CursorOption }>` | |
background: none; | |
border: none; | |
font-family: Inter; | |
font-size: 16px; | |
font-weight: 400; | |
width: 100%; | |
box-sizing: border-box; | |
cursor: ${props => props.cursor}; | |
&:focus { | |
outline: none; | |
} | |
`; | |
export const StringInput = ({ value, onChange, placeholder, prefix, suffix, cursor, onFocus, onBlur, ...props }: { | |
value: string; onChange: (value: string) => void; placeholder?: string; | |
prefix?: React.ReactNode; suffix?: React.ReactNode; cursor?: CursorOption; | |
onFocus?: () => void; onBlur?: () => void; | |
} & StyledProps & BaseProps) => { | |
const inputRef = React.useRef<HTMLInputElement>(null); | |
const handler = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value || ''), [onChange]); | |
const focus = React.useCallback(() => inputRef.current?.focus(), []); | |
return ( | |
<InputWrap onClick={focus} cursor={cursor || 'text'} align="flex-start" {...props}> | |
{prefix && <Base mr="12px">{prefix}</Base>} | |
<InputCore | |
ref={inputRef} | |
hasPrefix={!!prefix} | |
placeholder={placeholder} | |
value={value} | |
onChange={handler} | |
cursor={cursor || 'text'} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
/> | |
{suffix && <Base ml="12px">{suffix}</Base>} | |
</InputWrap> | |
); | |
}; | |
const DropdownBase= styled(Base)` | |
position: relative; | |
`; | |
const DropdownCard = styled(Card)` | |
position: absolute; | |
top: calc(100% + 4px); | |
left: 0; | |
width: 100%; | |
max-height: 300px; | |
overflow-y: scroll; | |
`; | |
const DropdownOption = styled(Base)` | |
padding: 12px; | |
cursor: pointer; | |
&:hover { | |
background: ${colors.blueLight}; | |
} | |
`; | |
export const OptionsInput = <T extends string>({ value, onChange, options, placeholder, prefix, ...props }: { | |
value: T | null, options: { value: T, label: string }[], onChange: (value: T | null) => void, placeholder?: string, | |
} & Pick<PropsOf<typeof StringInput>, 'prefix'> & StyledProps & BaseProps) => { | |
const label = React.useMemo(() => options.find(($) => $.value === value)?.label || '', [options, value]); | |
const [isOpen, setIsOpen] = React.useState(false); | |
const open = React.useCallback(() => setIsOpen(true), [setIsOpen]); | |
const close = React.useCallback(() => setTimeout(() => setIsOpen(false), 100), [setIsOpen]); | |
return ( | |
<DropdownBase {...props}> | |
<StringInput | |
placeholder={placeholder} | |
prefix={prefix} | |
value={label} | |
onChange={() => {}} | |
cursor="pointer" | |
suffix={value === null ? ( | |
<ArrowDownShort style={{ transform: `rotate(${isOpen ? 180 : 0}deg)`, marginRight: '-4px' }} width={24} color="#333" /> | |
) : ( | |
<CloseOutline width={24} color="#333" onClick={() => onChange(null)} /> | |
)} | |
onFocus={open} | |
onBlur={close} | |
/> | |
{isOpen && ( | |
<DropdownCard> | |
{options.map((option) => ( | |
<DropdownOption key={option.value} onClick={() => onChange(option.value)}> | |
<Text size={16} weight={400} color="#333">{option.label}</Text> | |
</DropdownOption> | |
))} | |
</DropdownCard> | |
)} | |
</DropdownBase> | |
); | |
}; | |
const Checkbox = styled.div` | |
position: relative; | |
background: #232426; | |
border: 2px solid #4C4D4F; | |
width: 20px; | |
height: 20px; | |
border-radius: 4px; | |
${props => props.value && css` | |
background: ${colors.blue}; | |
border-color: ${colors.blue}; | |
&::after { | |
display: block; | |
position: absolute; | |
top: 2px; | |
content: " "; | |
width:16px; | |
height: 8px; | |
border-top: 3px solid white; | |
border-right: 3px solid white; | |
transform: rotate(135deg); | |
} | |
`}; | |
`; | |
export const BooleanInput = ({ value, onChange, ...props }: { value: boolean, onChange: ($: boolean) => void } & BaseProps) => { | |
return ( | |
<Clickable color="none" onClick={() => onChange(!value)} {...props}> | |
<Checkbox value={value} /> | |
</Clickable> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
import React from 'react'; | |
export const useEventListener = (eventName, handler, element = window) => { | |
React.useEffect(() => { | |
element.addEventListener(eventName, handler); | |
return () => element.removeEventListener(eventName, handler); | |
}, [eventName, handler, element]); | |
}; | |
export const getWindowWidth = () => { | |
return window.innerWidth; | |
}; | |
export const useWindowWidth = () => { | |
const [size, setSize] = React.useState(getWindowWidth()); | |
const listener = React.useMemo(() => _.debounce(() => setSize(getWindowWidth()), 100), [setSize]); | |
useEventListener('resize', listener, window); | |
return size; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment