@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.
Files changed (123) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.js +2 -2
  3. package/package.json +22 -20
  4. package/src/components/auth/SignIn.tsx +43 -0
  5. package/src/components/auth/index.ts +1 -0
  6. package/src/components/box/CollapseHorizontal.tsx +18 -0
  7. package/src/components/box/ContentBox.tsx +17 -0
  8. package/src/components/box/ExpandOnMount.tsx +48 -0
  9. package/src/components/box/Expandable.tsx +96 -0
  10. package/src/components/box/FullSizeContainer.tsx +50 -0
  11. package/src/components/box/MobileFrame/index.tsx +145 -0
  12. package/src/components/box/MobileFrame/styles.css +35 -0
  13. package/src/components/box/index.ts +6 -0
  14. package/src/components/button/DeleteButton.tsx +178 -0
  15. package/src/components/button/Toggle.tsx +88 -0
  16. package/src/components/button/ViewButton.tsx +30 -0
  17. package/src/components/button/index.ts +3 -0
  18. package/src/components/feed/FeedContentPane.tsx +111 -0
  19. package/src/components/feed/MediaFeed.tsx +200 -0
  20. package/src/components/feed/MediaFeedBackground.tsx +127 -0
  21. package/src/components/feed/MediaFeedRefresh.tsx +78 -0
  22. package/src/components/feed/MediaFeedSwipeUp.tsx +34 -0
  23. package/src/components/feed/constants.ts +11 -0
  24. package/src/components/feed/context.tsx +19 -0
  25. package/src/components/feed/hooks.ts +290 -0
  26. package/src/components/feed/index.ts +2 -0
  27. package/src/components/feed/types.ts +50 -0
  28. package/src/components/form/Condition.tsx +26 -0
  29. package/src/components/form/Field.tsx +39 -0
  30. package/src/components/form/Form.tsx +425 -0
  31. package/src/components/form/FormFooter.tsx +82 -0
  32. package/src/components/form/UploadProgress/index.tsx +38 -0
  33. package/src/components/form/UploadProgress/styles.css +23 -0
  34. package/src/components/form/index.ts +4 -0
  35. package/src/components/form/input/Editable.tsx +129 -0
  36. package/src/components/form/input/InputSlider.tsx +75 -0
  37. package/src/components/form/input/OptionalTag.tsx +33 -0
  38. package/src/components/form/input/StandaloneInput.tsx +41 -0
  39. package/src/components/form/input/boolean/index.tsx +53 -0
  40. package/src/components/form/input/color/index.tsx +126 -0
  41. package/src/components/form/input/date/index.tsx +122 -0
  42. package/src/components/form/input/datetime/index.tsx +93 -0
  43. package/src/components/form/input/file.tsx +379 -0
  44. package/src/components/form/input/hooks/index.ts +2 -0
  45. package/src/components/form/input/hooks/useInputImperativeHandle.ts +16 -0
  46. package/src/components/form/input/hooks/useInputStyle.ts +39 -0
  47. package/src/components/form/input/index.ts +2 -0
  48. package/src/components/form/input/input.css +44 -0
  49. package/src/components/form/input/input.tsx +130 -0
  50. package/src/components/form/input/multipleSelect/index.tsx +55 -0
  51. package/src/components/form/input/number/index.tsx +83 -0
  52. package/src/components/form/input/number/styles.css +8 -0
  53. package/src/components/form/input/select/index.tsx +80 -0
  54. package/src/components/form/input/socialMedia/index.tsx +158 -0
  55. package/src/components/form/input/text/index.tsx +72 -0
  56. package/src/components/form/input/text/textarea.tsx +44 -0
  57. package/src/components/form/input/time/index.tsx +33 -0
  58. package/src/components/form/input/type.ts +0 -0
  59. package/src/components/form/input/types.ts +4 -0
  60. package/src/components/form/view/file.tsx +45 -0
  61. package/src/components/form/view/index.tsx +61 -0
  62. package/src/components/form/view/multipleSelect.tsx +38 -0
  63. package/src/components/form/view/select.tsx +33 -0
  64. package/src/components/index.ts +14 -0
  65. package/src/components/list/Body/InfiniteScrollGridBody.tsx +177 -0
  66. package/src/components/list/Body/InfiniteScrollListBody.tsx +114 -0
  67. package/src/components/list/Body/ListBody.tsx +23 -0
  68. package/src/components/list/Body/PagedGridBody.tsx +104 -0
  69. package/src/components/list/Body/PagedListBody.tsx +92 -0
  70. package/src/components/list/Body/hooks.ts +84 -0
  71. package/src/components/list/DataList.tsx +32 -0
  72. package/src/components/list/ListContainer.tsx +20 -0
  73. package/src/components/list/ListContent.tsx +54 -0
  74. package/src/components/list/ListCreate.tsx +57 -0
  75. package/src/components/list/ListFilters.tsx +182 -0
  76. package/src/components/list/ListFooter.tsx +458 -0
  77. package/src/components/list/ListHeader.tsx +180 -0
  78. package/src/components/list/ListItem/ListCell.tsx +48 -0
  79. package/src/components/list/ListItem/ListRow.tsx +38 -0
  80. package/src/components/list/ListItemView.tsx +53 -0
  81. package/src/components/list/ListSort.tsx +84 -0
  82. package/src/components/list/NoItems.tsx +33 -0
  83. package/src/components/list/constants.ts +1 -0
  84. package/src/components/list/index.ts +4 -0
  85. package/src/components/list/types.ts +29 -0
  86. package/src/components/list/utils.ts +62 -0
  87. package/src/components/loading/CircularProgress.tsx +11 -0
  88. package/src/components/loading/Loading.tsx +160 -0
  89. package/src/components/loading/LoadingImage.tsx +123 -0
  90. package/src/components/loading/LoadingSwitch.tsx +78 -0
  91. package/src/components/loading/index.ts +4 -0
  92. package/src/components/media/PlayButton.tsx +94 -0
  93. package/src/components/media/index.ts +1 -0
  94. package/src/components/modal/DefaultModal.tsx +18 -0
  95. package/src/components/modal/DesktopModal.tsx +11 -0
  96. package/src/components/modal/ForceMobile.tsx +7 -0
  97. package/src/components/modal/MobileModal.tsx +89 -0
  98. package/src/components/modal/index.ts +3 -0
  99. package/src/components/modal/type.ts +7 -0
  100. package/src/components/nav/NavBar.tsx +101 -0
  101. package/src/components/nav/index.ts +1 -0
  102. package/src/components/overlay/ImageViewOverlay.tsx +88 -0
  103. package/src/components/overlay/MobileOverlay.tsx +22 -0
  104. package/src/components/overlay/index.ts +2 -0
  105. package/src/components/text/GradientText/index.tsx +16 -0
  106. package/src/components/text/GradientText/styles.css +5 -0
  107. package/src/components/text/NumberTicker.tsx +28 -0
  108. package/src/components/text/index.ts +1 -0
  109. package/src/components/theme/colorMode/DarkModeToggle.tsx +40 -0
  110. package/src/components/theme/colorMode/index.ts +1 -0
  111. package/src/components/theme/index.ts +1 -0
  112. package/src/components/view/ErrorView.tsx +13 -0
  113. package/src/components/view/RedirectView.tsx +42 -0
  114. package/src/components/view/index.ts +2 -0
  115. package/src/contexts/index.ts +1 -0
  116. package/src/contexts/theme.ts +316 -0
  117. package/src/custom.d.ts +4 -0
  118. package/src/hooks/index.ts +1 -0
  119. package/src/hooks/ui/index.ts +1 -0
  120. package/src/hooks/ui/useBorderColor.ts +4 -0
  121. package/src/store/index.ts +1 -0
  122. package/src/store/usePlayer.ts +75 -0
  123. 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'