This commit is contained in:
Sanster
2022-04-12 20:58:57 +08:00
parent e570e85e64
commit aa411c7524
25 changed files with 769 additions and 22 deletions

View File

@@ -1,6 +1,6 @@
import { XIcon } from '@heroicons/react/outline'
import React, { ReactNode, useRef } from 'react'
import { useClickAway, useKey } from 'react-use'
import { useClickAway, useKey, useKeyPress, useKeyPressEvent } from 'react-use'
import Button from './Button'
export interface ModalProps {
@@ -19,8 +19,8 @@ export default function Modal(props: ModalProps) {
onClose?.()
})
useKey('Escape', onClose, {
event: 'keydown',
useKeyPressEvent('Escape', e => {
onClose?.()
})
return (
@@ -30,7 +30,7 @@ export default function Modal(props: ModalProps) {
>
<div ref={ref} className={`modal ${className}`}>
<div className="modal-header">
<h3>{title}</h3>
<h2>{title}</h2>
<Button icon={<XIcon />} onClick={onClose} />
</div>
{children}

View File

@@ -0,0 +1,12 @@
.number-input {
all: unset;
flex: 1 0 auto;
border-radius: 0.5rem;
padding: 0.2rem 0.8rem;
line-height: 1;
outline: 1px solid var(--border-color);
&:focus-visible {
outline: 1px solid var(--yellow-accent);
}
}

View File

@@ -0,0 +1,31 @@
import React, { FormEvent, InputHTMLAttributes } from 'react'
interface NumberInputProps extends InputHTMLAttributes<HTMLInputElement> {
value: string
onValue?: (val: string) => void
}
const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
(props: NumberInputProps, forwardedRef) => {
const { value, onValue, ...itemProps } = props
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
const target = evt.target as HTMLInputElement
const val = target.value.replace(/\D/g, '')
onValue?.(val)
}
return (
<input
value={value}
onInput={handleOnInput}
className="number-input"
{...itemProps}
ref={forwardedRef}
type="text"
/>
)
}
)
export default NumberInput

View File

@@ -0,0 +1,74 @@
@use '../../styles/Mixins' as *;
.selector {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.selector-main {
@include accented-display(var(white));
width: 100%;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
outline: none;
gap: 8px;
padding: 0.2rem 0.8rem;
border: 1px solid var(--border-color);
color: var(--options-text-color);
svg {
width: 1rem;
height: 1rem;
margin-top: 0.25rem;
}
}
.selector-options {
@include accented-display(var(--btn-primary-bg));
width: 100%;
padding: 0;
display: grid;
justify-self: center;
position: absolute;
cursor: pointer;
top: 3rem;
color: var(--options-text-color);
background-color: var(--page-bg);
border: 1px solid var(--border-color);
border-radius: 0.6rem;
@include mobile {
bottom: 11.5rem;
}
.selector-option {
display: flex;
align-items: center;
user-select: none;
padding: 0.5rem 0.8rem;
&:first-of-type {
border-top-right-radius: 0.5rem;
border-top-left-radius: 0.5rem;
}
&:last-of-type {
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}
&:hover {
background-color: var(--yellow-accent);
color: var(--btn-text-hover-color);
}
}
}

View File

@@ -0,0 +1,87 @@
import React, { MutableRefObject, useCallback, useRef, useState } from 'react'
import { useClickAway, useKeyPressEvent } from 'react-use'
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/outline'
type SelectorChevronDirection = 'up' | 'down'
type SelectorProps = {
minWidth?: number
chevronDirection?: SelectorChevronDirection
options: string[]
onChange: (value: string) => void
}
const selectorDefaultProps = {
minWidth: 128,
chevronDirection: 'down',
}
function Selector(props: SelectorProps) {
const { minWidth, chevronDirection, options, onChange } = props
const [showOptions, setShowOptions] = useState<boolean>(false)
const [index, setIndex] = useState<number>(0)
const selectorRef = useRef<HTMLDivElement | null>(null)
const showOptionsHandler = () => {
// console.log(selectorRef.current?.focus)
// selectorRef?.current?.focus()
setShowOptions(currentShowOptionsState => !currentShowOptionsState)
}
useClickAway(selectorRef, () => {
setShowOptions(false)
})
// TODO: how to prevent Modal close?
// useKeyPressEvent('Escape', (e: KeyboardEvent) => {
// if (showOptions === true) {
// console.log(`selector ${e}`)
// e.preventDefault()
// e.stopPropagation()
// setShowOptions(false)
// }
// })
const onOptionClick = (e: any, newIndex: number) => {
const currentRes = e.target.textContent.split('x')
onChange(currentRes[0])
setShowOptions(false)
setIndex(newIndex)
}
return (
<div className="selector" ref={selectorRef} style={{ minWidth }}>
<div
className="selector-main"
role="button"
onClick={showOptionsHandler}
aria-hidden="true"
>
<p>{options[index]}</p>
<div className="selector-icon">
{chevronDirection === 'up' ? <ChevronUpIcon /> : <ChevronDownIcon />}
</div>
</div>
{showOptions && (
<div className="selector-options">
{options.map((val, _index) => (
<div
className="selector-option"
role="button"
tabIndex={0}
key={val}
onClick={e => onOptionClick(e, _index)}
aria-hidden="true"
>
{val}
</div>
))}
</div>
)}
</div>
)
}
Selector.defaultProps = selectorDefaultProps
export default Selector

View File

@@ -0,0 +1,36 @@
.switch-root {
all: 'unset';
width: 42px;
height: 25px;
background-color: var(--switch-root-background-color);
border-radius: 9999px;
border: none;
position: relative;
transition: background-color 100ms;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
&:focus-visible {
outline: none;
}
}
.switch-root[data-state='checked'] {
background-color: var(--yellow-accent);
}
.switch-thumb {
display: block;
width: 17px;
height: 17px;
background-color: var(--switch-thumb-color);
border-radius: 9999px;
transition: transform 100ms;
transform: translateX(4px);
will-change: transform;
}
.switch-thumb[data-state='checked'] {
transform: translateX(21px);
background-color: var(--switch-thumb-checked-color);
outline: 1px solid rgb(100, 100, 120, 0.5);
}

View File

@@ -0,0 +1,34 @@
import React from 'react'
import * as SwitchPrimitive from '@radix-ui/react-switch'
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitive.Root>,
React.ComponentProps<typeof SwitchPrimitive.Root>
>((props, forwardedRef) => {
const { className, ...itemProps } = props
return (
<SwitchPrimitive.Root
{...itemProps}
ref={forwardedRef}
className={`switch-root ${className}`}
/>
)
})
const SwitchThumb = React.forwardRef<
React.ElementRef<typeof SwitchPrimitive.Thumb>,
React.ComponentProps<typeof SwitchPrimitive.Thumb>
>((props, forwardedRef) => {
const { className, ...itemProps } = props
return (
<SwitchPrimitive.Thumb
{...itemProps}
ref={forwardedRef}
className={`switch-thumb ${className}`}
/>
)
})
export { Switch, SwitchThumb }