@chem-po/react-web 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/package.json +22 -20
- package/src/components/auth/SignIn.tsx +43 -0
- package/src/components/auth/index.ts +1 -0
- package/src/components/box/CollapseHorizontal.tsx +18 -0
- package/src/components/box/ContentBox.tsx +17 -0
- package/src/components/box/ExpandOnMount.tsx +48 -0
- package/src/components/box/Expandable.tsx +96 -0
- package/src/components/box/FullSizeContainer.tsx +50 -0
- package/src/components/box/MobileFrame/index.tsx +145 -0
- package/src/components/box/MobileFrame/styles.css +35 -0
- package/src/components/box/index.ts +6 -0
- package/src/components/button/DeleteButton.tsx +178 -0
- package/src/components/button/Toggle.tsx +88 -0
- package/src/components/button/ViewButton.tsx +30 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/feed/FeedContentPane.tsx +111 -0
- package/src/components/feed/MediaFeed.tsx +200 -0
- package/src/components/feed/MediaFeedBackground.tsx +127 -0
- package/src/components/feed/MediaFeedRefresh.tsx +78 -0
- package/src/components/feed/MediaFeedSwipeUp.tsx +34 -0
- package/src/components/feed/constants.ts +11 -0
- package/src/components/feed/context.tsx +19 -0
- package/src/components/feed/hooks.ts +290 -0
- package/src/components/feed/index.ts +2 -0
- package/src/components/feed/types.ts +50 -0
- package/src/components/form/Condition.tsx +26 -0
- package/src/components/form/Field.tsx +39 -0
- package/src/components/form/Form.tsx +425 -0
- package/src/components/form/FormFooter.tsx +82 -0
- package/src/components/form/UploadProgress/index.tsx +38 -0
- package/src/components/form/UploadProgress/styles.css +23 -0
- package/src/components/form/index.ts +4 -0
- package/src/components/form/input/Editable.tsx +129 -0
- package/src/components/form/input/InputSlider.tsx +75 -0
- package/src/components/form/input/OptionalTag.tsx +33 -0
- package/src/components/form/input/StandaloneInput.tsx +41 -0
- package/src/components/form/input/boolean/index.tsx +53 -0
- package/src/components/form/input/color/index.tsx +126 -0
- package/src/components/form/input/date/index.tsx +122 -0
- package/src/components/form/input/datetime/index.tsx +93 -0
- package/src/components/form/input/file.tsx +379 -0
- package/src/components/form/input/hooks/index.ts +2 -0
- package/src/components/form/input/hooks/useInputImperativeHandle.ts +16 -0
- package/src/components/form/input/hooks/useInputStyle.ts +39 -0
- package/src/components/form/input/index.ts +2 -0
- package/src/components/form/input/input.css +44 -0
- package/src/components/form/input/input.tsx +130 -0
- package/src/components/form/input/multipleSelect/index.tsx +55 -0
- package/src/components/form/input/number/index.tsx +83 -0
- package/src/components/form/input/number/styles.css +8 -0
- package/src/components/form/input/select/index.tsx +80 -0
- package/src/components/form/input/socialMedia/index.tsx +158 -0
- package/src/components/form/input/text/index.tsx +72 -0
- package/src/components/form/input/text/textarea.tsx +44 -0
- package/src/components/form/input/time/index.tsx +33 -0
- package/src/components/form/input/type.ts +0 -0
- package/src/components/form/input/types.ts +4 -0
- package/src/components/form/view/file.tsx +45 -0
- package/src/components/form/view/index.tsx +61 -0
- package/src/components/form/view/multipleSelect.tsx +38 -0
- package/src/components/form/view/select.tsx +33 -0
- package/src/components/index.ts +14 -0
- package/src/components/list/Body/InfiniteScrollGridBody.tsx +177 -0
- package/src/components/list/Body/InfiniteScrollListBody.tsx +114 -0
- package/src/components/list/Body/ListBody.tsx +23 -0
- package/src/components/list/Body/PagedGridBody.tsx +104 -0
- package/src/components/list/Body/PagedListBody.tsx +92 -0
- package/src/components/list/Body/hooks.ts +84 -0
- package/src/components/list/DataList.tsx +32 -0
- package/src/components/list/ListContainer.tsx +20 -0
- package/src/components/list/ListContent.tsx +54 -0
- package/src/components/list/ListCreate.tsx +57 -0
- package/src/components/list/ListFilters.tsx +182 -0
- package/src/components/list/ListFooter.tsx +458 -0
- package/src/components/list/ListHeader.tsx +180 -0
- package/src/components/list/ListItem/ListCell.tsx +48 -0
- package/src/components/list/ListItem/ListRow.tsx +38 -0
- package/src/components/list/ListItemView.tsx +53 -0
- package/src/components/list/ListSort.tsx +84 -0
- package/src/components/list/NoItems.tsx +33 -0
- package/src/components/list/constants.ts +1 -0
- package/src/components/list/index.ts +4 -0
- package/src/components/list/types.ts +29 -0
- package/src/components/list/utils.ts +62 -0
- package/src/components/loading/CircularProgress.tsx +11 -0
- package/src/components/loading/Loading.tsx +160 -0
- package/src/components/loading/LoadingImage.tsx +123 -0
- package/src/components/loading/LoadingSwitch.tsx +78 -0
- package/src/components/loading/index.ts +4 -0
- package/src/components/media/PlayButton.tsx +94 -0
- package/src/components/media/index.ts +1 -0
- package/src/components/modal/DefaultModal.tsx +18 -0
- package/src/components/modal/DesktopModal.tsx +11 -0
- package/src/components/modal/ForceMobile.tsx +7 -0
- package/src/components/modal/MobileModal.tsx +89 -0
- package/src/components/modal/index.ts +3 -0
- package/src/components/modal/type.ts +7 -0
- package/src/components/nav/NavBar.tsx +101 -0
- package/src/components/nav/index.ts +1 -0
- package/src/components/overlay/ImageViewOverlay.tsx +88 -0
- package/src/components/overlay/MobileOverlay.tsx +22 -0
- package/src/components/overlay/index.ts +2 -0
- package/src/components/text/GradientText/index.tsx +16 -0
- package/src/components/text/GradientText/styles.css +5 -0
- package/src/components/text/NumberTicker.tsx +28 -0
- package/src/components/text/index.ts +1 -0
- package/src/components/theme/colorMode/DarkModeToggle.tsx +40 -0
- package/src/components/theme/colorMode/index.ts +1 -0
- package/src/components/theme/index.ts +1 -0
- package/src/components/view/ErrorView.tsx +13 -0
- package/src/components/view/RedirectView.tsx +42 -0
- package/src/components/view/index.ts +2 -0
- package/src/contexts/index.ts +1 -0
- package/src/contexts/theme.ts +316 -0
- package/src/custom.d.ts +4 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/useBorderColor.ts +4 -0
- package/src/store/index.ts +1 -0
- package/src/store/usePlayer.ts +75 -0
- package/src/store/useScreen.ts +22 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HStack,
|
|
3
|
+
Slider,
|
|
4
|
+
SliderFilledTrack,
|
|
5
|
+
SliderProps,
|
|
6
|
+
SliderThumb,
|
|
7
|
+
SliderTrack,
|
|
8
|
+
StackProps,
|
|
9
|
+
Text,
|
|
10
|
+
Tooltip,
|
|
11
|
+
} from '@chakra-ui/react'
|
|
12
|
+
import { Gradient, gradientToCssGradientProp } from '@chem-po/core'
|
|
13
|
+
import { GradientText } from '../../text/GradientText'
|
|
14
|
+
|
|
15
|
+
export const InputSlider = ({
|
|
16
|
+
label,
|
|
17
|
+
stackProps,
|
|
18
|
+
gradient,
|
|
19
|
+
value,
|
|
20
|
+
min,
|
|
21
|
+
max,
|
|
22
|
+
...props
|
|
23
|
+
}: {
|
|
24
|
+
label: string
|
|
25
|
+
stackProps?: StackProps
|
|
26
|
+
gradient?: Gradient
|
|
27
|
+
} & SliderProps) => {
|
|
28
|
+
const gradientCss = gradient ? gradientToCssGradientProp(gradient) : null
|
|
29
|
+
return (
|
|
30
|
+
<HStack
|
|
31
|
+
opacity={value !== undefined ? 1 : 0.5}
|
|
32
|
+
// filter={`grayscale(${value !== undefined ? 0 : 100}%)`}
|
|
33
|
+
_hover={{
|
|
34
|
+
opacity: 1,
|
|
35
|
+
// filter: 'grayscale(0%)',
|
|
36
|
+
}}
|
|
37
|
+
transition="all 500ms"
|
|
38
|
+
spacing={2}
|
|
39
|
+
width="100%"
|
|
40
|
+
align="center"
|
|
41
|
+
{...stackProps}>
|
|
42
|
+
<HStack w="100%" align="center">
|
|
43
|
+
{min !== undefined && gradientCss ? (
|
|
44
|
+
<GradientText fontSize="sm" color={gradientCss}>
|
|
45
|
+
{typeof min === 'number' ? min.toFixed((props?.step ?? 1) < 1 ? 2 : 0) : ''}
|
|
46
|
+
</GradientText>
|
|
47
|
+
) : null}
|
|
48
|
+
{min !== undefined && !gradientCss ? (
|
|
49
|
+
<Text>{typeof min === 'number' ? min.toFixed((props?.step ?? 1) < 1 ? 2 : 0) : ''}</Text>
|
|
50
|
+
) : null}
|
|
51
|
+
<Slider min={min} max={max} step={1} aria-label={label} flex={1} {...props}>
|
|
52
|
+
<SliderTrack bg="gray.400">
|
|
53
|
+
<SliderFilledTrack bg={gradientCss ?? 'blackAlpha.500'} />
|
|
54
|
+
</SliderTrack>
|
|
55
|
+
<Tooltip
|
|
56
|
+
bg="gray.100"
|
|
57
|
+
color="gray.500"
|
|
58
|
+
placement="top"
|
|
59
|
+
hasArrow
|
|
60
|
+
label={typeof value === 'number' ? value.toFixed(0) : ''}>
|
|
61
|
+
<SliderThumb width={2} height={4} />
|
|
62
|
+
</Tooltip>
|
|
63
|
+
</Slider>
|
|
64
|
+
{max !== undefined && gradientCss ? (
|
|
65
|
+
<GradientText fontSize="sm" color={gradientCss}>
|
|
66
|
+
{typeof max === 'number' ? max.toFixed((props?.step ?? 1) < 1 ? 2 : 0) : ''}
|
|
67
|
+
</GradientText>
|
|
68
|
+
) : null}
|
|
69
|
+
{max !== undefined && !gradientCss ? (
|
|
70
|
+
<Text>{typeof max === 'number' ? max.toFixed((props?.step ?? 1) < 1 ? 2 : 0) : ''}</Text>
|
|
71
|
+
) : null}
|
|
72
|
+
</HStack>
|
|
73
|
+
</HStack>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Flex, Text } from '@chakra-ui/react'
|
|
2
|
+
import { Field } from '@chem-po/react'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
export const OptionalTag = <T extends Field>({
|
|
6
|
+
value,
|
|
7
|
+
field,
|
|
8
|
+
}: {
|
|
9
|
+
field: T
|
|
10
|
+
value: T['defaultValue'] | undefined | null
|
|
11
|
+
}) => {
|
|
12
|
+
const top = useMemo(() => {
|
|
13
|
+
if (value === '' || value === undefined || value === null) return 1
|
|
14
|
+
switch (field._type) {
|
|
15
|
+
case 'text':
|
|
16
|
+
case 'number':
|
|
17
|
+
case 'select':
|
|
18
|
+
case 'date':
|
|
19
|
+
case 'time':
|
|
20
|
+
case 'datetime':
|
|
21
|
+
return -1
|
|
22
|
+
default:
|
|
23
|
+
return 1
|
|
24
|
+
}
|
|
25
|
+
}, [value, field])
|
|
26
|
+
return (
|
|
27
|
+
<Flex transition="top 300ms" top={top} position="absolute" right={3}>
|
|
28
|
+
<Text fontSize="xs" opacity={0.6}>
|
|
29
|
+
OPTIONAL
|
|
30
|
+
</Text>
|
|
31
|
+
</Flex>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InputRef } from '@chem-po/core'
|
|
2
|
+
import { Field, useStandaloneInput } from '@chem-po/react'
|
|
3
|
+
import { CSSProperties, ForwardedRef, forwardRef } from 'react'
|
|
4
|
+
import { Input } from './input'
|
|
5
|
+
|
|
6
|
+
const StandaloneInputBase = (
|
|
7
|
+
{
|
|
8
|
+
onChange,
|
|
9
|
+
value,
|
|
10
|
+
field,
|
|
11
|
+
onBlur,
|
|
12
|
+
inEditable,
|
|
13
|
+
onFocus,
|
|
14
|
+
style,
|
|
15
|
+
}: {
|
|
16
|
+
onChange: (v?: any) => void
|
|
17
|
+
value?: any
|
|
18
|
+
field: Field
|
|
19
|
+
onBlur?: () => void
|
|
20
|
+
onFocus?: () => void
|
|
21
|
+
theme?: 'basic' | 'detailed'
|
|
22
|
+
inEditable?: boolean
|
|
23
|
+
style?: CSSProperties
|
|
24
|
+
},
|
|
25
|
+
ref: ForwardedRef<InputRef>,
|
|
26
|
+
) => {
|
|
27
|
+
const { inputProps, meta } = useStandaloneInput(field, value, onChange, onFocus, onBlur)
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Input
|
|
31
|
+
ref={ref}
|
|
32
|
+
field={field}
|
|
33
|
+
inEditable={inEditable}
|
|
34
|
+
style={style}
|
|
35
|
+
input={inputProps}
|
|
36
|
+
meta={meta}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const StandaloneInput = forwardRef(StandaloneInputBase)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Checkbox, Flex, Switch, Text } from '@chakra-ui/react'
|
|
2
|
+
import { InputRef } from '@chem-po/core'
|
|
3
|
+
import { BooleanField } from '@chem-po/react'
|
|
4
|
+
import { ForwardedRef, forwardRef, useImperativeHandle } from 'react'
|
|
5
|
+
import { FieldProps } from '../types'
|
|
6
|
+
|
|
7
|
+
const BaseCheckboxComponent = (
|
|
8
|
+
{ field, input: { onChange, value, ...input } }: FieldProps<BooleanField>,
|
|
9
|
+
ref: ForwardedRef<InputRef>,
|
|
10
|
+
) => {
|
|
11
|
+
const { placeholder } = field
|
|
12
|
+
useImperativeHandle(ref, () => ({
|
|
13
|
+
focus: () => {},
|
|
14
|
+
blur: () => {},
|
|
15
|
+
}))
|
|
16
|
+
return (
|
|
17
|
+
<Checkbox isChecked={value} onChange={e => onChange(e.target.checked)} {...input}>
|
|
18
|
+
{placeholder}
|
|
19
|
+
</Checkbox>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CheckboxBooleanComponent = forwardRef<InputRef, FieldProps<BooleanField>>(
|
|
24
|
+
BaseCheckboxComponent,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const BaseSwitchComponent = (
|
|
28
|
+
{ field, input: { onChange, value, ...input } }: FieldProps<BooleanField>,
|
|
29
|
+
ref: ForwardedRef<InputRef>,
|
|
30
|
+
) => {
|
|
31
|
+
const { placeholder } = field
|
|
32
|
+
useImperativeHandle(ref, () => ({
|
|
33
|
+
focus: () => {},
|
|
34
|
+
blur: () => {},
|
|
35
|
+
}))
|
|
36
|
+
return (
|
|
37
|
+
<Flex gap={1} align="center">
|
|
38
|
+
<Switch isChecked={value} onChange={e => onChange(e.target.checked)} {...input} />
|
|
39
|
+
<Text fontWeight={600} opacity={value ? 0.9 : 0.6}>
|
|
40
|
+
{placeholder}
|
|
41
|
+
</Text>
|
|
42
|
+
</Flex>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SwitchBooleanComponent = forwardRef<InputRef, FieldProps<BooleanField>>(BaseSwitchComponent)
|
|
47
|
+
|
|
48
|
+
const BaseBooleanComponent = (props: FieldProps<BooleanField>, ref: ForwardedRef<InputRef>) => {
|
|
49
|
+
if (props.field.type === 'switch') return <SwitchBooleanComponent ref={ref} {...props} />
|
|
50
|
+
return <CheckboxBooleanComponent ref={ref} {...props} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const BooleanComponent = forwardRef<InputRef, FieldProps<BooleanField>>(BaseBooleanComponent)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Box, Center, HStack, SliderProps, StackProps, Text, VStack } from '@chakra-ui/react'
|
|
2
|
+
|
|
3
|
+
import { colorValueToHex, Gradient, gradients, InputRef } from '@chem-po/core'
|
|
4
|
+
import { ColorField } from '@chem-po/react'
|
|
5
|
+
import { forwardRef, useCallback, useMemo } from 'react'
|
|
6
|
+
import { useInputImperativeHandle } from '../hooks/useInputImperativeHandle'
|
|
7
|
+
import { InputSlider } from '../InputSlider'
|
|
8
|
+
import { FieldProps } from '../types'
|
|
9
|
+
|
|
10
|
+
const ColorComponentSlider = ({
|
|
11
|
+
label,
|
|
12
|
+
stackProps,
|
|
13
|
+
gradient,
|
|
14
|
+
value,
|
|
15
|
+
...props
|
|
16
|
+
}: {
|
|
17
|
+
label: string
|
|
18
|
+
stackProps?: StackProps
|
|
19
|
+
gradient: Gradient
|
|
20
|
+
} & SliderProps) => (
|
|
21
|
+
<InputSlider
|
|
22
|
+
label={label}
|
|
23
|
+
value={value}
|
|
24
|
+
min={0}
|
|
25
|
+
step={1}
|
|
26
|
+
max={255}
|
|
27
|
+
stackProps={{ width: '100%', ...stackProps }}
|
|
28
|
+
gradient={gradient}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
export const ColorComponent = forwardRef<InputRef, FieldProps<ColorField>>(
|
|
34
|
+
({ input: { onChange, value }, field, meta }, ref) => {
|
|
35
|
+
// my colors: {r: 0, g: 0, b: 0, a: 0}
|
|
36
|
+
// input colors: '#rrggbbaa'
|
|
37
|
+
const { withAlpha, defaultValue } = field
|
|
38
|
+
const { active } = meta || {}
|
|
39
|
+
|
|
40
|
+
useInputImperativeHandle(ref)
|
|
41
|
+
|
|
42
|
+
const handleChange = useCallback(
|
|
43
|
+
(key: 'r' | 'g' | 'b' | 'a', val: number) => {
|
|
44
|
+
if (withAlpha) {
|
|
45
|
+
onChange({
|
|
46
|
+
r: 0,
|
|
47
|
+
g: 0,
|
|
48
|
+
b: 0,
|
|
49
|
+
a: 1,
|
|
50
|
+
...value,
|
|
51
|
+
[key]: val,
|
|
52
|
+
})
|
|
53
|
+
} else {
|
|
54
|
+
onChange({
|
|
55
|
+
r: 0,
|
|
56
|
+
g: 0,
|
|
57
|
+
b: 0,
|
|
58
|
+
...value,
|
|
59
|
+
[key]: val,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[value, onChange, withAlpha],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const withDefault = useMemo(() => ({ ...defaultValue, ...value }), [defaultValue, value])
|
|
67
|
+
const asHex = useMemo(() => colorValueToHex(!!withAlpha, withDefault), [withDefault, withAlpha])
|
|
68
|
+
return (
|
|
69
|
+
<HStack w="100%">
|
|
70
|
+
<Box
|
|
71
|
+
boxShadow="inset 0 0 12px rgba(255,255,255,0.4), 0 0 4px rgba(0,0,0,0.4)"
|
|
72
|
+
p={2}
|
|
73
|
+
bg="blackAlpha.800"
|
|
74
|
+
borderRadius="full">
|
|
75
|
+
<Center
|
|
76
|
+
transition="background 300ms"
|
|
77
|
+
bg={asHex}
|
|
78
|
+
width="80px"
|
|
79
|
+
h="80px"
|
|
80
|
+
borderRadius="full">
|
|
81
|
+
<Text style={{ fontSize: '1.3rem' }}>{asHex}</Text>
|
|
82
|
+
</Center>
|
|
83
|
+
</Box>
|
|
84
|
+
<VStack
|
|
85
|
+
spacing={1}
|
|
86
|
+
flex={1}
|
|
87
|
+
px={2}
|
|
88
|
+
align="flex-start"
|
|
89
|
+
borderRadius={6}
|
|
90
|
+
transition="all 400ms"
|
|
91
|
+
boxShadow={`0 0 5px ${active ? '#ffffff' : 'transparent'}`}>
|
|
92
|
+
<ColorComponentSlider
|
|
93
|
+
label="R"
|
|
94
|
+
gradient={gradients.red}
|
|
95
|
+
defaultValue={defaultValue.r}
|
|
96
|
+
value={value?.r}
|
|
97
|
+
onChange={updatedRed => handleChange('r', updatedRed)}
|
|
98
|
+
/>
|
|
99
|
+
<ColorComponentSlider
|
|
100
|
+
label="G"
|
|
101
|
+
defaultValue={defaultValue.g}
|
|
102
|
+
gradient={gradients.green}
|
|
103
|
+
value={value?.g}
|
|
104
|
+
onChange={updatedGreen => handleChange('g', updatedGreen)}
|
|
105
|
+
/>
|
|
106
|
+
<ColorComponentSlider
|
|
107
|
+
label="B"
|
|
108
|
+
gradient={gradients.blue}
|
|
109
|
+
defaultValue={defaultValue.b}
|
|
110
|
+
value={value?.b}
|
|
111
|
+
onChange={updatedBlue => handleChange('b', updatedBlue)}
|
|
112
|
+
/>
|
|
113
|
+
{withAlpha ? (
|
|
114
|
+
<ColorComponentSlider
|
|
115
|
+
label="A"
|
|
116
|
+
gradient={gradients.midnight}
|
|
117
|
+
defaultValue={1}
|
|
118
|
+
value={value?.a}
|
|
119
|
+
onChange={updatedAlpha => handleChange('a', updatedAlpha)}
|
|
120
|
+
/>
|
|
121
|
+
) : null}
|
|
122
|
+
</VStack>
|
|
123
|
+
</HStack>
|
|
124
|
+
)
|
|
125
|
+
},
|
|
126
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Image,
|
|
4
|
+
Popover,
|
|
5
|
+
PopoverArrow,
|
|
6
|
+
PopoverContent,
|
|
7
|
+
PopoverTrigger,
|
|
8
|
+
Portal,
|
|
9
|
+
Text,
|
|
10
|
+
} from '@chakra-ui/react'
|
|
11
|
+
import { forwardRef, useImperativeHandle, useMemo } from 'react'
|
|
12
|
+
import { DayPicker } from 'react-day-picker'
|
|
13
|
+
// import 'react-day-picker/dist/style.css'
|
|
14
|
+
import { getDateString, InputRef } from '@chem-po/core'
|
|
15
|
+
import { DateField } from '@chem-po/react'
|
|
16
|
+
import { FieldProps } from '../types'
|
|
17
|
+
|
|
18
|
+
const padZeros = (n: number, digits: number) => {
|
|
19
|
+
let v = `${n}`
|
|
20
|
+
for (let i = v.length; i < digits; i += 1) {
|
|
21
|
+
v = `0${v}`
|
|
22
|
+
}
|
|
23
|
+
return v
|
|
24
|
+
}
|
|
25
|
+
export const parseDate = (date?: Date) =>
|
|
26
|
+
date
|
|
27
|
+
? `${padZeros(date.getFullYear(), 4)}-${padZeros(date.getMonth() + 1, 2)}-${padZeros(
|
|
28
|
+
date.getDate(),
|
|
29
|
+
2,
|
|
30
|
+
)}`
|
|
31
|
+
: undefined
|
|
32
|
+
export const DateInput = forwardRef<InputRef, FieldProps<DateField>>(
|
|
33
|
+
(props: FieldProps<DateField>, ref) => {
|
|
34
|
+
const {
|
|
35
|
+
input,
|
|
36
|
+
field: { placeholder, minDate, maxDate, optional },
|
|
37
|
+
meta: { active },
|
|
38
|
+
} = props
|
|
39
|
+
const { onChange, value, onFocus, onBlur } = input
|
|
40
|
+
const date = useMemo(() => (value ? new Date(`${value}T00:00:00.000`) : new Date()), [value])
|
|
41
|
+
const disabled = useMemo(() => {
|
|
42
|
+
const res: { from: Date; to: Date }[] = []
|
|
43
|
+
if (minDate !== undefined) {
|
|
44
|
+
res.push({
|
|
45
|
+
to: minDate === 'now' ? new Date() : new Date(minDate),
|
|
46
|
+
from: new Date(0),
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
if (maxDate !== undefined) {
|
|
50
|
+
res.push({
|
|
51
|
+
from: maxDate === 'now' ? new Date() : new Date(maxDate),
|
|
52
|
+
to: new Date(Infinity),
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
return res
|
|
56
|
+
}, [minDate, maxDate])
|
|
57
|
+
|
|
58
|
+
useImperativeHandle(ref, () => ({
|
|
59
|
+
focus: () => {},
|
|
60
|
+
blur: () => {},
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Popover
|
|
65
|
+
isOpen={active}
|
|
66
|
+
onOpen={onFocus}
|
|
67
|
+
onClose={onBlur}
|
|
68
|
+
closeDelay={100}
|
|
69
|
+
placement="bottom">
|
|
70
|
+
<PopoverTrigger>
|
|
71
|
+
<Button
|
|
72
|
+
w="100%"
|
|
73
|
+
fontFamily="Public Sans"
|
|
74
|
+
fontWeight="normal"
|
|
75
|
+
alignItems="center"
|
|
76
|
+
_hover={{ bg: '#efefef' }}
|
|
77
|
+
lineHeight={1}
|
|
78
|
+
variant="outline">
|
|
79
|
+
<Text fontSize="md" height="16px" align="left" flex={1}>
|
|
80
|
+
{value ? getDateString(value, 'short') : placeholder}
|
|
81
|
+
</Text>
|
|
82
|
+
<Image src="/svg/calendar.svg" height="20px" />
|
|
83
|
+
</Button>
|
|
84
|
+
</PopoverTrigger>
|
|
85
|
+
<Portal>
|
|
86
|
+
<PopoverContent zIndex={2} onFocus={onFocus}>
|
|
87
|
+
<PopoverArrow />
|
|
88
|
+
<DayPicker
|
|
89
|
+
required={!optional}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
styles={{
|
|
92
|
+
caption: {
|
|
93
|
+
fontFamily: 'Public Sans',
|
|
94
|
+
fontWeight: '400',
|
|
95
|
+
fontSize: '0.9rem',
|
|
96
|
+
color: '#555',
|
|
97
|
+
},
|
|
98
|
+
head: {
|
|
99
|
+
fontFamily: 'Public Sans',
|
|
100
|
+
},
|
|
101
|
+
nav: {
|
|
102
|
+
fontFamily: 'Public Sans',
|
|
103
|
+
},
|
|
104
|
+
}}
|
|
105
|
+
modifiersStyles={{
|
|
106
|
+
selected: {
|
|
107
|
+
background: '#454545',
|
|
108
|
+
},
|
|
109
|
+
}}
|
|
110
|
+
// id={name}
|
|
111
|
+
mode="single"
|
|
112
|
+
selected={date}
|
|
113
|
+
onSelect={(updated: Date | undefined) => onChange(parseDate(updated))}
|
|
114
|
+
/>
|
|
115
|
+
</PopoverContent>
|
|
116
|
+
</Portal>
|
|
117
|
+
</Popover>
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
DateInput.displayName = 'DateInput'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { HStack } from '@chakra-ui/react'
|
|
2
|
+
import { InputRef } from '@chem-po/core'
|
|
3
|
+
import { DateTimeField, FieldMeta } from '@chem-po/react'
|
|
4
|
+
import { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react'
|
|
5
|
+
import { DateInput, parseDate } from '../date'
|
|
6
|
+
import '../input.css'
|
|
7
|
+
import { TimeInput } from '../time'
|
|
8
|
+
import { FieldProps } from '../types'
|
|
9
|
+
|
|
10
|
+
const parseDateAndTime = (timestamp?: number | string) => {
|
|
11
|
+
if (!timestamp) return { date: undefined, time: undefined }
|
|
12
|
+
const d = new Date(timestamp)
|
|
13
|
+
return { date: parseDate(d), time: d.toTimeString().substring(0, 5) }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DateTimeInput = forwardRef<InputRef, FieldProps<DateTimeField>>(
|
|
17
|
+
({ input: { value, onBlur, onChange, onFocus } }, ref) => {
|
|
18
|
+
const [{ date, time }, set] = useState<{ date?: string; time?: string }>(
|
|
19
|
+
parseDateAndTime(value),
|
|
20
|
+
)
|
|
21
|
+
const [focusedElement, setFocused] = useState<'date' | 'time' | null>(null)
|
|
22
|
+
const handleChange = useCallback(
|
|
23
|
+
(type: 'date' | 'time', updated?: string) => {
|
|
24
|
+
if (type === 'date') {
|
|
25
|
+
set({ date: updated, time })
|
|
26
|
+
if (updated && time) onChange(new Date(`${updated}T${time}`).getTime())
|
|
27
|
+
} else {
|
|
28
|
+
set({ time: updated, date })
|
|
29
|
+
if (updated && date) {
|
|
30
|
+
onChange(new Date(`${date}T${updated}:00.000`).getTime())
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
[date, time, onChange],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
useImperativeHandle(ref, () => ({
|
|
38
|
+
focus: () => {},
|
|
39
|
+
blur: () => {},
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
const meta = useMemo<FieldMeta>(
|
|
43
|
+
() => ({
|
|
44
|
+
active: focusedElement === 'date',
|
|
45
|
+
error: undefined,
|
|
46
|
+
touched: false,
|
|
47
|
+
}),
|
|
48
|
+
[focusedElement],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<HStack w="100%">
|
|
53
|
+
<DateInput
|
|
54
|
+
field={{ _type: 'date', placeholder: 'Date' }}
|
|
55
|
+
input={{
|
|
56
|
+
// name: '',
|
|
57
|
+
value: date,
|
|
58
|
+
onChange: (dateString: string) => {
|
|
59
|
+
handleChange('date', dateString)
|
|
60
|
+
},
|
|
61
|
+
onBlur: () => {
|
|
62
|
+
setFocused(null)
|
|
63
|
+
onBlur()
|
|
64
|
+
},
|
|
65
|
+
onFocus: () => {
|
|
66
|
+
setFocused('date')
|
|
67
|
+
onFocus()
|
|
68
|
+
},
|
|
69
|
+
}}
|
|
70
|
+
meta={meta}
|
|
71
|
+
/>
|
|
72
|
+
<TimeInput
|
|
73
|
+
field={{ _type: 'time', placeholder: 'Time' }}
|
|
74
|
+
input={{
|
|
75
|
+
value: time,
|
|
76
|
+
onChange: e => handleChange('time', e.target.value),
|
|
77
|
+
onBlur: () => {
|
|
78
|
+
setFocused(null)
|
|
79
|
+
onBlur()
|
|
80
|
+
},
|
|
81
|
+
onFocus: () => {
|
|
82
|
+
setFocused('time')
|
|
83
|
+
onFocus()
|
|
84
|
+
},
|
|
85
|
+
}}
|
|
86
|
+
meta={meta}
|
|
87
|
+
/>
|
|
88
|
+
</HStack>
|
|
89
|
+
)
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
DateTimeInput.displayName = 'DateTimeInput'
|