about summary refs log tree commit diff
path: root/src/components/forms/DateField
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/forms/DateField')
-rw-r--r--src/components/forms/DateField/index.android.tsx108
-rw-r--r--src/components/forms/DateField/index.tsx56
-rw-r--r--src/components/forms/DateField/index.web.tsx64
-rw-r--r--src/components/forms/DateField/types.ts7
-rw-r--r--src/components/forms/DateField/utils.ts16
5 files changed, 251 insertions, 0 deletions
diff --git a/src/components/forms/DateField/index.android.tsx b/src/components/forms/DateField/index.android.tsx
new file mode 100644
index 000000000..83fa285f5
--- /dev/null
+++ b/src/components/forms/DateField/index.android.tsx
@@ -0,0 +1,108 @@
+import React from 'react'
+import {View, Pressable} from 'react-native'
+import DateTimePicker, {
+  BaseProps as DateTimePickerProps,
+} from '@react-native-community/datetimepicker'
+
+import {useTheme, atoms} from '#/alf'
+import {Text} from '#/components/Typography'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+import * as TextField from '#/components/forms/TextField'
+import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
+
+import {DateFieldProps} from '#/components/forms/DateField/types'
+import {
+  localizeDate,
+  toSimpleDateString,
+} from '#/components/forms/DateField/utils'
+
+export * as utils from '#/components/forms/DateField/utils'
+export const Label = TextField.Label
+
+export function DateField({
+  value,
+  onChangeDate,
+  label,
+  isInvalid,
+  testID,
+}: DateFieldProps) {
+  const t = useTheme()
+  const [open, setOpen] = React.useState(false)
+  const {
+    state: pressed,
+    onIn: onPressIn,
+    onOut: onPressOut,
+  } = useInteractionState()
+  const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+  const {chromeFocus, chromeError, chromeErrorHover} =
+    TextField.useSharedInputStyles()
+
+  const onChangeInternal = React.useCallback<
+    Required<DateTimePickerProps>['onChange']
+  >(
+    (_event, date) => {
+      setOpen(false)
+
+      if (date) {
+        const formatted = toSimpleDateString(date)
+        onChangeDate(formatted)
+      }
+    },
+    [onChangeDate, setOpen],
+  )
+
+  return (
+    <View style={[atoms.relative, atoms.w_full]}>
+      <Pressable
+        aria-label={label}
+        accessibilityLabel={label}
+        accessibilityHint={undefined}
+        onPress={() => setOpen(true)}
+        onPressIn={onPressIn}
+        onPressOut={onPressOut}
+        onFocus={onFocus}
+        onBlur={onBlur}
+        style={[
+          {
+            paddingTop: 16,
+            paddingBottom: 16,
+            borderColor: 'transparent',
+            borderWidth: 2,
+          },
+          atoms.flex_row,
+          atoms.flex_1,
+          atoms.w_full,
+          atoms.px_lg,
+          atoms.rounded_sm,
+          t.atoms.bg_contrast_50,
+          focused || pressed ? chromeFocus : {},
+          isInvalid ? chromeError : {},
+          isInvalid && (focused || pressed) ? chromeErrorHover : {},
+        ]}>
+        <TextField.Icon icon={CalendarDays} />
+
+        <Text
+          style={[atoms.text_md, atoms.pl_xs, t.atoms.text, {paddingTop: 3}]}>
+          {localizeDate(value)}
+        </Text>
+      </Pressable>
+
+      {open && (
+        <DateTimePicker
+          aria-label={label}
+          accessibilityLabel={label}
+          accessibilityHint={undefined}
+          testID={`${testID}-datepicker`}
+          mode="date"
+          timeZoneName={'Etc/UTC'}
+          display="spinner"
+          // @ts-ignore applies in iOS only -prf
+          themeVariant={t.name === 'dark' ? 'dark' : 'light'}
+          value={new Date(value)}
+          onChange={onChangeInternal}
+        />
+      )}
+    </View>
+  )
+}
diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx
new file mode 100644
index 000000000..c359a9d46
--- /dev/null
+++ b/src/components/forms/DateField/index.tsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import {View} from 'react-native'
+import DateTimePicker, {
+  DateTimePickerEvent,
+} from '@react-native-community/datetimepicker'
+
+import {useTheme, atoms} from '#/alf'
+import * as TextField from '#/components/forms/TextField'
+import {toSimpleDateString} from '#/components/forms/DateField/utils'
+import {DateFieldProps} from '#/components/forms/DateField/types'
+
+export * as utils from '#/components/forms/DateField/utils'
+export const Label = TextField.Label
+
+/**
+ * Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date
+ * changes in the same format.
+ *
+ * For dates of unknown format, convert with the
+ * `utils.toSimpleDateString(Date)` export of this file.
+ */
+export function DateField({
+  value,
+  onChangeDate,
+  testID,
+  label,
+}: DateFieldProps) {
+  const t = useTheme()
+
+  const onChangeInternal = React.useCallback(
+    (event: DateTimePickerEvent, date: Date | undefined) => {
+      if (date) {
+        const formatted = toSimpleDateString(date)
+        onChangeDate(formatted)
+      }
+    },
+    [onChangeDate],
+  )
+
+  return (
+    <View style={[atoms.relative, atoms.w_full]}>
+      <DateTimePicker
+        aria-label={label}
+        accessibilityLabel={label}
+        accessibilityHint={undefined}
+        testID={`${testID}-datepicker`}
+        mode="date"
+        timeZoneName={'Etc/UTC'}
+        display="spinner"
+        themeVariant={t.name === 'dark' ? 'dark' : 'light'}
+        value={new Date(value)}
+        onChange={onChangeInternal}
+      />
+    </View>
+  )
+}
diff --git a/src/components/forms/DateField/index.web.tsx b/src/components/forms/DateField/index.web.tsx
new file mode 100644
index 000000000..32f38a5d1
--- /dev/null
+++ b/src/components/forms/DateField/index.web.tsx
@@ -0,0 +1,64 @@
+import React from 'react'
+import {TextInput, TextInputProps, StyleSheet} from 'react-native'
+// @ts-ignore
+import {unstable_createElement} from 'react-native-web'
+
+import * as TextField from '#/components/forms/TextField'
+import {toSimpleDateString} from '#/components/forms/DateField/utils'
+import {DateFieldProps} from '#/components/forms/DateField/types'
+
+export * as utils from '#/components/forms/DateField/utils'
+export const Label = TextField.Label
+
+const InputBase = React.forwardRef<HTMLInputElement, TextInputProps>(
+  ({style, ...props}, ref) => {
+    return unstable_createElement('input', {
+      ...props,
+      ref,
+      type: 'date',
+      style: [
+        StyleSheet.flatten(style),
+        {
+          background: 'transparent',
+          border: 0,
+        },
+      ],
+    })
+  },
+)
+
+InputBase.displayName = 'InputBase'
+
+const Input = TextField.createInput(InputBase as unknown as typeof TextInput)
+
+export function DateField({
+  value,
+  onChangeDate,
+  label,
+  isInvalid,
+  testID,
+}: DateFieldProps) {
+  const handleOnChange = React.useCallback(
+    (e: any) => {
+      const date = e.target.valueAsDate || e.target.value
+
+      if (date) {
+        const formatted = toSimpleDateString(date)
+        onChangeDate(formatted)
+      }
+    },
+    [onChangeDate],
+  )
+
+  return (
+    <TextField.Root isInvalid={isInvalid}>
+      <Input
+        value={value}
+        label={label}
+        onChange={handleOnChange}
+        onChangeText={() => {}}
+        testID={testID}
+      />
+    </TextField.Root>
+  )
+}
diff --git a/src/components/forms/DateField/types.ts b/src/components/forms/DateField/types.ts
new file mode 100644
index 000000000..129f5672d
--- /dev/null
+++ b/src/components/forms/DateField/types.ts
@@ -0,0 +1,7 @@
+export type DateFieldProps = {
+  value: string
+  onChangeDate: (date: string) => void
+  label: string
+  isInvalid?: boolean
+  testID?: string
+}
diff --git a/src/components/forms/DateField/utils.ts b/src/components/forms/DateField/utils.ts
new file mode 100644
index 000000000..c787272fe
--- /dev/null
+++ b/src/components/forms/DateField/utils.ts
@@ -0,0 +1,16 @@
+import {getLocales} from 'expo-localization'
+
+const LOCALE = getLocales()[0]
+
+// we need the date in the form yyyy-MM-dd to pass to the input
+export function toSimpleDateString(date: Date | string): string {
+  const _date = typeof date === 'string' ? new Date(date) : date
+  return _date.toISOString().split('T')[0]
+}
+
+export function localizeDate(date: Date | string): string {
+  const _date = typeof date === 'string' ? new Date(date) : date
+  return new Intl.DateTimeFormat(LOCALE.languageTag, {
+    timeZone: 'UTC',
+  }).format(_date)
+}