From 26703f34d998aa7cdf6c6c988fc0220ff73fe2e6 Mon Sep 17 00:00:00 2001 From: Jiri Kolarik Date: Fri, 4 Feb 2022 14:33:03 +0100 Subject: [PATCH] feat(components): first commit --- .github/workflows/release.yml | 27 + .gitignore | 25 + .legacy/avatar/avatar.tsx | 187 + .legacy/calendar/calendar.tsx | 150 + .legacy/calendar/day.tsx | 390 + .legacy/calendar/month.tsx | 132 + .legacy/calendar/nav-button.tsx | 32 + .legacy/calendar/provider.tsx | 61 + .legacy/card.tsx | 49 + .legacy/currency/currency-selector.tsx | 35 + .legacy/currency/currency.tsx | 30 + .legacy/currency/price.tsx | 1 + .legacy/date-format.tsx | 18 + .legacy/form/date-picker-field-form.tsx | 29 + .legacy/form/date-picker-field.tsx | 74 + .legacy/form/editor-field-form.tsx | 23 + .legacy/form/editor-field.tsx | 67 + .legacy/form/editor-field/cs.ts | 60 + .legacy/form/image-field-form.tsx | 100 + .legacy/form/image-uploader-field-form.tsx | 137 + .legacy/form/input-field-form.tsx | 67 + .legacy/form/input-field.tsx | 54 + .legacy/form/input.tsx | 51 + .legacy/form/location-field-form.tsx | 44 + .legacy/form/location-field.tsx | 561 + .legacy/form/nick-field-form.tsx | 18 + .legacy/form/pincode-field.tsx | 41 + .legacy/form/private-file-uploader-field.tsx | 134 + .legacy/form/radio-field-form.tsx | 36 + .legacy/form/select-field-form.tsx | 166 + .legacy/form/select-field.tsx | 63 + .legacy/form/select2-field-form.tsx | 31 + .legacy/form/select2-field.tsx | 99 + .legacy/form/slider-field-form.tsx | 30 + .legacy/form/slider-field.tsx | 80 + .legacy/form/star-rating-field-form.tsx | 25 + .legacy/form/textarea-field-form.tsx | 32 + .legacy/form/value-range-field-form.tsx | 30 + .legacy/form/value-range-field.tsx | 80 + .legacy/form/week-day-field-form.tsx | 58 + .legacy/lang-selector.tsx | 45 + .legacy/link-button.tsx | 90 + .legacy/link.tsx | 45 + .legacy/list-item/list-item.tsx | 97 + .legacy/list.tsx | 7 + .legacy/loader/logo.tsx | 25 + .legacy/maps/annotations.tsx | 45 + .legacy/maps/circles.tsx | 32 + .legacy/maps/icons/index.tsx | 55 + .legacy/maps/index.tsx | 57 + .legacy/maps/mapkit-provider.tsx | 131 + .legacy/markdown-editor.tsx | 34 + .legacy/modal.tsx | 70 + .legacy/nav-button.tsx | 24 + .legacy/not-found.tsx | 27 + .legacy/payment/bankwire.tsx | 184 + .legacy/seo.tsx | 105 + .legacy/sign-pad.tsx | 111 + .legacy/slider/nav-buttons.tsx | 44 + .legacy/star-rating.tsx | 51 + .legacy/styles/app.css | 48 + .legacy/sub-menu.tsx | 77 + .legacy/terms-and-conditions.tsx | 12 + .legacy/textarea.tsx | 49 + .legacy/utils/currency.tsx | 67 + .legacy/utils/date-fns.ts | 6 + .legacy/utils/date.ts | 11 + .legacy/utils/error-boundary.tsx | 27 + .legacy/utils/geocoder.ts | 146 + .legacy/utils/gtag.ts | 9 + .legacy/utils/image.ts | 29 + .legacy/utils/language.tsx | 56 + .legacy/utils/maps.ts | 31 + .legacy/utils/use-cashify.ts | 128 + .legacy/zendesk.tsx | 66 + .npmrc | 1 + .storybook/main.js | 13 + .storybook/preview.js | 35 + .storybook/themes.js | 23 + README.md | 1 + package-lock.json | 70277 +++++++++++++++++ package.json | 96 + rollup.config.js | 46 + src/button-group/button-group.stories.tsx | 64 + src/button-group/button-group.tsx | 74 + src/button-group/index.ts | 2 + src/button/button-form.tsx | 16 + src/button/button.stories.tsx | 46 + src/button/button.test.tsx | 9 + src/button/button.tsx | 90 + src/button/index.ts | 4 + src/checkbox/checkbox-field-form.tsx | 35 + src/checkbox/checkbox-field.tsx | 24 + src/checkbox/checkbox.stories.tsx | 17 + src/checkbox/checkbox.tsx | 42 + src/checkbox/index.ts | 6 + src/field/field.stories.tsx | 17 + src/field/field.tsx | 83 + src/field/index.tsx | 2 + src/index.ts | 6 + src/setupTests.ts | 5 + src/themes/campervan.ts | 14 + src/themes/index.ts | 2 + src/themes/light.ts | 4 + src/utils/css.ts | 75 + tsconfig.json | 24 + 106 files changed, 76521 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .legacy/avatar/avatar.tsx create mode 100644 .legacy/calendar/calendar.tsx create mode 100644 .legacy/calendar/day.tsx create mode 100644 .legacy/calendar/month.tsx create mode 100644 .legacy/calendar/nav-button.tsx create mode 100644 .legacy/calendar/provider.tsx create mode 100644 .legacy/card.tsx create mode 100644 .legacy/currency/currency-selector.tsx create mode 100644 .legacy/currency/currency.tsx create mode 100644 .legacy/currency/price.tsx create mode 100644 .legacy/date-format.tsx create mode 100644 .legacy/form/date-picker-field-form.tsx create mode 100644 .legacy/form/date-picker-field.tsx create mode 100644 .legacy/form/editor-field-form.tsx create mode 100644 .legacy/form/editor-field.tsx create mode 100644 .legacy/form/editor-field/cs.ts create mode 100644 .legacy/form/image-field-form.tsx create mode 100644 .legacy/form/image-uploader-field-form.tsx create mode 100644 .legacy/form/input-field-form.tsx create mode 100644 .legacy/form/input-field.tsx create mode 100644 .legacy/form/input.tsx create mode 100644 .legacy/form/location-field-form.tsx create mode 100644 .legacy/form/location-field.tsx create mode 100644 .legacy/form/nick-field-form.tsx create mode 100644 .legacy/form/pincode-field.tsx create mode 100644 .legacy/form/private-file-uploader-field.tsx create mode 100644 .legacy/form/radio-field-form.tsx create mode 100644 .legacy/form/select-field-form.tsx create mode 100644 .legacy/form/select-field.tsx create mode 100644 .legacy/form/select2-field-form.tsx create mode 100644 .legacy/form/select2-field.tsx create mode 100644 .legacy/form/slider-field-form.tsx create mode 100644 .legacy/form/slider-field.tsx create mode 100644 .legacy/form/star-rating-field-form.tsx create mode 100644 .legacy/form/textarea-field-form.tsx create mode 100644 .legacy/form/value-range-field-form.tsx create mode 100644 .legacy/form/value-range-field.tsx create mode 100644 .legacy/form/week-day-field-form.tsx create mode 100644 .legacy/lang-selector.tsx create mode 100644 .legacy/link-button.tsx create mode 100644 .legacy/link.tsx create mode 100644 .legacy/list-item/list-item.tsx create mode 100644 .legacy/list.tsx create mode 100644 .legacy/loader/logo.tsx create mode 100644 .legacy/maps/annotations.tsx create mode 100644 .legacy/maps/circles.tsx create mode 100644 .legacy/maps/icons/index.tsx create mode 100644 .legacy/maps/index.tsx create mode 100644 .legacy/maps/mapkit-provider.tsx create mode 100644 .legacy/markdown-editor.tsx create mode 100644 .legacy/modal.tsx create mode 100644 .legacy/nav-button.tsx create mode 100644 .legacy/not-found.tsx create mode 100644 .legacy/payment/bankwire.tsx create mode 100644 .legacy/seo.tsx create mode 100644 .legacy/sign-pad.tsx create mode 100644 .legacy/slider/nav-buttons.tsx create mode 100644 .legacy/star-rating.tsx create mode 100644 .legacy/styles/app.css create mode 100644 .legacy/sub-menu.tsx create mode 100644 .legacy/terms-and-conditions.tsx create mode 100644 .legacy/textarea.tsx create mode 100644 .legacy/utils/currency.tsx create mode 100644 .legacy/utils/date-fns.ts create mode 100644 .legacy/utils/date.ts create mode 100644 .legacy/utils/error-boundary.tsx create mode 100644 .legacy/utils/geocoder.ts create mode 100644 .legacy/utils/gtag.ts create mode 100644 .legacy/utils/image.ts create mode 100644 .legacy/utils/language.tsx create mode 100644 .legacy/utils/maps.ts create mode 100644 .legacy/utils/use-cashify.ts create mode 100644 .legacy/zendesk.tsx create mode 100644 .npmrc create mode 100644 .storybook/main.js create mode 100644 .storybook/preview.js create mode 100644 .storybook/themes.js create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/button-group/button-group.stories.tsx create mode 100644 src/button-group/button-group.tsx create mode 100644 src/button-group/index.ts create mode 100644 src/button/button-form.tsx create mode 100644 src/button/button.stories.tsx create mode 100644 src/button/button.test.tsx create mode 100644 src/button/button.tsx create mode 100644 src/button/index.ts create mode 100644 src/checkbox/checkbox-field-form.tsx create mode 100644 src/checkbox/checkbox-field.tsx create mode 100644 src/checkbox/checkbox.stories.tsx create mode 100644 src/checkbox/checkbox.tsx create mode 100644 src/checkbox/index.ts create mode 100644 src/field/field.stories.tsx create mode 100644 src/field/field.tsx create mode 100644 src/field/index.tsx create mode 100644 src/index.ts create mode 100644 src/setupTests.ts create mode 100644 src/themes/campervan.ts create mode 100644 src/themes/index.ts create mode 100644 src/themes/light.ts create mode 100644 src/utils/css.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a3e889e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Release +on: + push: + branches: + - master +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "lts/*" + - name: Install dependencies + run: npm ci + - name: Test + run: npm run build + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb6a4a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build +/dist +/**/dist + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/.legacy/avatar/avatar.tsx b/.legacy/avatar/avatar.tsx new file mode 100644 index 0000000..0fb2459 --- /dev/null +++ b/.legacy/avatar/avatar.tsx @@ -0,0 +1,187 @@ +import { captureEvent } from "@sentry/react"; +import { Slider } from "baseui/slider"; +import { useSnackbar } from "baseui/snackbar"; +import { useField, useFormikContext } from "formik"; +import { useRef, useState } from "react"; +import AvatarEditor from "react-avatar-editor"; +import Dropzone from "react-dropzone"; +import * as Icons from "react-feather"; +import { useTranslation } from "react-i18next"; +import { useStyletron } from "styletron-react"; +import { borderRadius } from ".././utils/css"; +import Button, { ButtonAppearance } from "../button/button"; +import { upload } from "../form/image-uploader-field-form"; + +const { REACT_APP_API: API = "" } = process.env; +const prefix = process.env.NODE_ENV !== "production" ? "_dev/" : ""; + +const imageUploadUrl = (scope: string) => + `${API}/upload/image?scope=${prefix}${scope}`; + +export default function AvatarField(props: { name: string }) { + const [submitting, setSubmitting] = useState(false); + const [editMode, setEditMode] = useState(false); + const [img, setImg] = useState(); + const { submitForm } = useFormikContext(); + const editor = useRef(null); + const [{ value }, , { setValue }] = useField(props.name); + const [css] = useStyletron(); + const [scale, setScale] = useState([1]); + const [t] = useTranslation(); + const { enqueue } = useSnackbar(); + + const onSend = async () => { + try { + if (editor.current) { + setSubmitting(true); + const payload = new FormData(); + + const canvas = editor.current.getImage().toDataURL(); + const response = await fetch(canvas); + const blob = await response.blob(); + + payload.append("file", blob); + + const { + data: { url }, + } = await upload(imageUploadUrl("account"), payload); + + if (url) { + setValue(url); + setEditMode(false); + submitForm(); + } else { + throw "URL not specified"; + } + } + } catch (error: any) { + setSubmitting(false); + enqueue({ + startEnhancer: () => , + message: t("exp.save.error"), + }); + captureEvent(error); + } + }; + + if (editMode) { + return ( +
+
+ + + scaleValue && setScale(scaleValue) + } + /> +
+ + +
+
+
+ ); + } + + return ( +
+ { + setImg(item); + setEditMode(true); + }} + > + {({ getRootProps, getInputProps }) => ( +
+ + +
+ {t("avatar.click-or-drop-to-upload")} +
+
+ )} +
+
+ ); +} diff --git a/.legacy/calendar/calendar.tsx b/.legacy/calendar/calendar.tsx new file mode 100644 index 0000000..53539fd --- /dev/null +++ b/.legacy/calendar/calendar.tsx @@ -0,0 +1,150 @@ +import { + END_DATE, + FocusedInput, + OnDatesChangeProps, + START_DATE, + useDatepicker, +} from "@datepicker-react/hooks"; +import { endOfMonth, startOfMonth } from "date-fns/esm"; +import React, { ReactNode, useEffect, useState } from "react"; +import { StyleObject, useStyletron } from "styletron-react"; +import CalendarMonth from "./month"; +import CalendarContext, { DayOverrides } from "./provider"; + +interface Interval { + start?: Date; + end?: Date; +} + +export interface CalendarProps { + date: Interval; + onChange?: ( + { start, end }: Interval, + focusedInput?: typeof START_DATE | typeof END_DATE | null + ) => void; + onVisibleMonthsRangeChange?: ({ start, end }: Interval) => void; + unavailableDates?: Date[]; + minBookingDays?: number; + overrides?: { + Day?: { + style: (params: DayOverrides) => StyleObject; + }; + }; + isDateBlocked?: (day: Date) => boolean; + isDateUnselectable?: (day: Date) => boolean; + numberOfMonths?: number; + dayRenderer?: (params: { date: Date }) => ReactNode; + initialVisibleMonth?: Date; +} + +export default function Calendar(props: CalendarProps) { + const [focus, setFocus] = useState(START_DATE); + const [css] = useStyletron(); + + function handleDateChange(params: OnDatesChangeProps) { + setFocus(params.focusedInput || START_DATE); + const end = focus === START_DATE ? undefined : params.endDate; + + props.onChange && + props.onChange( + { + start: params.startDate || undefined, + end: end || undefined, + }, + params.focusedInput + ); + } + + const startDate = props.date.start || null; + const endDate = props.date.end || null; + const minBookingDays = props.minBookingDays || 1; + + const { + firstDayOfWeek, + activeMonths, + isDateSelected, + isDateHovered, + isFirstOrLastSelectedDate, + isDateFocused, + isDateBlocked, + focusedDate, + onDateHover, + onDateSelect, + onDateFocus, + goToPreviousMonths, + goToNextMonths, + onResetDates, + } = useDatepicker({ + initialVisibleMonth: props.initialVisibleMonth, + isDateBlocked: props.isDateBlocked, + startDate, + endDate, + focusedInput: focus, + onDatesChange: handleDateChange, + numberOfMonths: props.numberOfMonths || 1, + unavailableDates: props.unavailableDates, + minBookingDays, + firstDayOfWeek: 1, + minBookingDate: new Date(), + }); + + const { onVisibleMonthsRangeChange } = props; + useEffect(() => { + if (onVisibleMonthsRangeChange) { + const nrOfMonths = activeMonths.length; + onVisibleMonthsRangeChange({ + start: startOfMonth(activeMonths[0].date), + end: endOfMonth(activeMonths[nrOfMonths - 1].date), + }); + } + }, [activeMonths]); + + const isDateUnselectable = props.isDateUnselectable + ? props.isDateUnselectable + : () => false; + + return ( + +
+ {activeMonths.map((month, i) => ( + + ))} +
+
+ ); +} diff --git a/.legacy/calendar/day.tsx b/.legacy/calendar/day.tsx new file mode 100644 index 0000000..1ff9756 --- /dev/null +++ b/.legacy/calendar/day.tsx @@ -0,0 +1,390 @@ +import { useDay } from "@datepicker-react/hooks"; +import { StatefulPopover } from "baseui/popover"; +import { addDays, isAfter, isBefore, isSameDay } from "date-fns"; +import { useContext, useEffect, useRef, useState } from "react"; +import { X } from "react-feather"; +import { useTranslation } from "react-i18next"; +import { StyleObject, useStyletron } from "styletron-react"; +import { border, borderRadius, margin, padding } from ".././utils/css"; +import { useLanguage } from "../utils/language"; +import CalendarContext from "./provider"; + +export function boxShadow(color: string) { + return `1px 0 0 0 ${color}, + 0 1px 0 0 ${color}, + 1px 1px 0 0 ${color}, + 1px 0 0 0 ${color} inset, + 0 1px 0 0 ${color} inset`; +} + +interface DayProps { + dayLabel: string; + date: Date; + styles: any; +} + +export default function CalendarDay({ dayLabel, date, styles }: DayProps) { + const [double, setDouble] = useState(false); + const [css] = useStyletron(); + const [t] = useTranslation(); + const { dateFormat } = useLanguage(); + const dayRef = useRef(null); + const { + startDate, + endDate, + focusedDate, + isDateFocused, + isDateSelected, + isDateHovered, + isDateBlocked, + isFirstOrLastSelectedDate, + onDateFocus, + onDateSelect, + onDateHover, + overrides, + onResetDates, + dayRenderer, + minBookingDays, + isDateUnselectable, + } = useContext(CalendarContext); + const { + isSelected, + isSelectedStartOrEnd, + isWithinHoverRange, + disabledDate, + onClick, + onKeyDown, + onMouseEnter, + tabIndex, + } = useDay({ + date, + focusedDate, + isDateFocused, + isDateSelected, + isDateHovered, + isDateBlocked, + isFirstOrLastSelectedDate, + onDateFocus, + onDateSelect, + onDateHover, + dayRef, + }); + + const dayIsSame = startDate && isSameDay(startDate, date); + + const isHistory = isBefore(date, new Date()); + const cantHover = (disabledDate || isDateUnselectable(date)) && !dayIsSame; + const notSelectableDate = isDateUnselectable(date); + + const onButtonClick = (ev: any) => { + if (!double) { + setDouble(true); + setTimeout(() => setDouble(false), 1000); + if ((disabledDate || isDateUnselectable(date)) && dayIsSame && !endDate) { + onResetDates(); + } else { + onClick(); + } + } + }; + + useEffect(() => { + const onTouchStart = () => { + if (dayRef.current) { + dayRef.current.addEventListener("touchend", onButtonClick, { + once: true, + }); + } + }; + + const onTouchMove = () => { + if (dayRef.current) { + dayRef.current.removeEventListener("touchend", onButtonClick); + } + }; + + if (dayRef && dayRef.current) { + dayRef.current.addEventListener("touchstart", onTouchStart); + dayRef.current.addEventListener("touchmove", onTouchMove); + } + + return () => { + if (dayRef.current) { + dayRef.current.removeEventListener("touchstart", onTouchStart); + dayRef.current.removeEventListener("touchmove", onTouchMove); + dayRef.current.removeEventListener("touchend", onButtonClick); + } + }; + }, [dayRef, onButtonClick]); + + if (!dayLabel) { + return
; + } + + const isMinReturnDay = + startDate && isSameDay(addDays(startDate, minBookingDays - 1), date) + ? true + : false; + + const colors = (function (): StyleObject { + const notSelectableStyle: StyleObject = notSelectableDate + ? { + borderBottom: "none", + fontWeight: "normal", + ":hover": { + backgroundColor: "var(--red)", + fontWeight: "normal", + }, + } + : {}; + + if (dayIsSame && !endDate && (disabledDate || isDateUnselectable(date))) { + return { + color: "var(--white)", + backgroundColor: "var(--red)", + fontWeight: "bold", + borderBottom: "none", + ":hover": { + backgroundColor: "var(--red)", + fontWeight: "normal", + }, + }; + } + + if (isSelectedStartOrEnd) + return { + color: "var(--white)", + backgroundColor: "var(--dark-blue)", + fontWeight: "bold", + }; + + if (startDate && isSameDay(addDays(startDate, minBookingDays - 1), date)) + return { + ...notSelectableStyle, + fontWeight: "bold", + color: "var(--dark-blue)", + backgroundColor: "var(--gray)", + }; + + if ( + isSelected || + isWithinHoverRange || + (startDate && isSameDay(addDays(startDate, minBookingDays - 1), date)) + ) + return { + ...notSelectableStyle, + fontWeight: "bold", + color: "var(--font-dark-gray)", + backgroundColor: "var(--light-gray)", + }; + + if (startDate && minBookingDays > 2) { + if ( + isAfter(date, startDate) && + isBefore(date, addDays(startDate, minBookingDays - 1)) + ) { + return { + ...notSelectableStyle, + fontWeight: "normal", + color: "var(--font-dark-gray)", + backgroundColor: "var(--light-gray)", + ":hover": { + backgroundColor: "var(--red)", + }, + }; + } + } + + if (disabledDate) { + return { + ...notSelectableStyle, + cursor: "not-allowed", + textDecorationLine: "line-through", + textDecorationColor: "var(--font-light-gray)", + borderBottom: "none", + fontWeight: "normal", + color: "var(--font-light-gray)", + ":hover": { + backgroundColor: "var(--red)", + fontWeight: "normal", + }, + }; + } + + if (notSelectableDate) { + return notSelectableStyle; + } + + return { + ":hover": { + backgroundColor: "inherit", + color: "inherit", + ...border({ + style: "solid", + width: "1px", + color: "var(--border-color)", + }), + borderTop: "none", + }, + }; + })(); + + const cantBook = + disabledDate && !startDate ? t("calendar.day.cant-book") : null; + const cantReturn = + disabledDate && startDate ? t("calendar.day.cant-return") : null; + const minDays = + startDate && + minBookingDays > 0 && + isAfter(date, startDate) && + isBefore(date, addDays(startDate, minBookingDays - 1)) + ? t("calendar.day.cant-min-days", { + date: dateFormat(addDays(startDate, minBookingDays - 1)), + }) + : null; + + if (cantHover && !isHistory) { + return ( + + {cantBook} + {!minDays && cantReturn} + {minDays} +
+ ) + } + > + + + ); + } + + return ( + + ); +} diff --git a/.legacy/calendar/month.tsx b/.legacy/calendar/month.tsx new file mode 100644 index 0000000..1139259 --- /dev/null +++ b/.legacy/calendar/month.tsx @@ -0,0 +1,132 @@ +import { FirstDayOfWeek, useMonth } from "@datepicker-react/hooks"; +import { format } from "date-fns"; +import * as locale from "date-fns/esm/locale"; +import React, { useContext } from "react"; +import * as Icon from "react-feather"; +import { useStyletron } from "styletron-react"; +import { useLanguage } from "../utils/language"; +import Day from "./day"; +import CalendarNavButton from "./nav-button"; +import CalendarContext from "./provider"; + +const LOCALE = "cs"; + +interface MonthProps { + year: number; + month: number; + firstDayOfWeek: FirstDayOfWeek; + showLeftArrow?: boolean; + showRightArrow?: boolean; +} + +export default function CalendarMonth({ + year, + month, + firstDayOfWeek, + showLeftArrow, + showRightArrow, +}: MonthProps) { + const [css] = useStyletron(); + const { lang } = useLanguage(); + const { goToNextMonths, goToPreviousMonths } = useContext(CalendarContext); + const { days, weekdayLabels, monthLabel } = useMonth({ + year, + month, + firstDayOfWeek, + weekdayLabelFormat: (date: Date) => + format(date, "cccccc", { locale: locale[lang as "cs"] }), + monthLabelFormat: (date: Date) => + format(date, "LLLL yyyy", { locale: locale[lang as "cs"] }), + }); + + return ( +
+
+
+ {showLeftArrow && ( + + + + )} +
+
+ {monthLabel} +
+
+ {showRightArrow && ( + + + + )} +
+
+
+ {weekdayLabels.map((dayLabel) => ( +
+ {dayLabel} +
+ ))} +
+
+ {days.map((day, index) => { + // "> *:first-child" + const styles = + index === 0 + ? { + gridRow: "1 / 1", + gridColumn: "1 / 1", + } + : {}; + + if (typeof day === "object") { + return ( + + ); + } + + return
; + })} +
+
+ ); +} diff --git a/.legacy/calendar/nav-button.tsx b/.legacy/calendar/nav-button.tsx new file mode 100644 index 0000000..78e94fb --- /dev/null +++ b/.legacy/calendar/nav-button.tsx @@ -0,0 +1,32 @@ +import { PropsWithChildren } from "react"; +import { useStyletron } from "styletron-react"; +import { border } from ".././utils/css"; + +export interface CalendarNavButtonProps { + onClick?: () => void; +} + +export default function CalendarNavButton({ + children, + onClick, +}: PropsWithChildren) { + const [css] = useStyletron(); + return ( + + ); +} diff --git a/.legacy/calendar/provider.tsx b/.legacy/calendar/provider.tsx new file mode 100644 index 0000000..a5f51de --- /dev/null +++ b/.legacy/calendar/provider.tsx @@ -0,0 +1,61 @@ +import React, { ReactNode } from "react"; +import { StyleObject } from "styletron-react"; + +export interface DayOverrides { + defaults: StyleObject; + date: Date; + isWithinHoverRange: boolean; + isSelected: boolean; + isSelectedStartOrEnd: boolean; + isDisabled: boolean; +} + +export const datepickerContextDefaultValue = { + focusedDate: null, + isDateFocused: () => false, + isDateSelected: () => false, + isDateHovered: () => false, + isDateBlocked: () => false, + isFirstOrLastSelectedDate: () => false, + onDateFocus: () => {}, + onDateSelect: () => {}, + onDateHover: () => {}, + onResetDates: () => {}, + goToPreviousMonths: () => {}, + goToNextMonths: () => {}, + isDateUnselectable: () => false, + startDate: null, + endDate: null, + minBookingDays: 0, +}; + +type Context = { + focusedDate: Date | null; + isDateFocused(date: Date): boolean; + isDateSelected(date: Date): boolean; + isDateHovered(date: Date): boolean; + isDateBlocked(date: Date): boolean; + isFirstOrLastSelectedDate(date: Date): boolean; + onDateFocus(date: Date): void; + onDateSelect(date: Date): void; + onDateHover(date: Date): void; + goToPreviousMonths: () => void; + goToNextMonths: () => void; + onResetDates(): void; + dayRenderer?: (params: { date: Date }) => ReactNode; + isDateUnselectable(date: Date): boolean; + minBookingDays: number; + startDate?: Date | null; + endDate?: Date | null; + overrides?: { + Day?: { + style: (params: DayOverrides) => StyleObject; + }; + }; +}; + +const CalendarContext = React.createContext( + datepickerContextDefaultValue +); + +export default CalendarContext; diff --git a/.legacy/card.tsx b/.legacy/card.tsx new file mode 100644 index 0000000..0b0aebf --- /dev/null +++ b/.legacy/card.tsx @@ -0,0 +1,49 @@ +import { Card as BaseCard } from "baseui/card"; +import React, { PropsWithChildren } from "react"; +import { useStyletron } from "styletron-react"; +import { borderColor, borderRadius, margin, padding } from "./utils/css"; + +export default function Card(props: PropsWithChildren<{}>) { + const [css] = useStyletron(); + return ( +
+ +
+ {props.children} +
+
+
+ ); +} diff --git a/.legacy/currency/currency-selector.tsx b/.legacy/currency/currency-selector.tsx new file mode 100644 index 0000000..2bd767c --- /dev/null +++ b/.legacy/currency/currency-selector.tsx @@ -0,0 +1,35 @@ +import Select from "react-select"; +import { useStyletron } from "styletron-react"; +import { useCurrency } from "../utils/currency"; +import { useLanguage } from "../utils/language"; +import useCashify from "../utils/use-cashify"; + +export default function CurrencySelector() { + const [css] = useStyletron(); + const [_, rate] = useCashify(); + const availableCurrencies = [rate.base, ...Object.keys(rate.rates)]; + const { currency, setCurrency } = useCurrency(); + const { priceFormat } = useLanguage(); + + const options = availableCurrencies.map((c) => ({ + value: c, + label: priceFormat(1, c)?.replace("1", ""), + })); + + return ( + + {isDragActive ? ( +
{t("form.image-uploader.drop-here")}
+ ) : ( +
+ {uploading > 0 + ? t("form.image-uploader.uploading-files", { + count: uploading, + }) + : t("form.image-uploader.click-or-drop-files")} +
+ )} +
+ ); + }} + + + + )} + /> + + ); +} diff --git a/.legacy/form/input-field-form.tsx b/.legacy/form/input-field-form.tsx new file mode 100644 index 0000000..c438c54 --- /dev/null +++ b/.legacy/form/input-field-form.tsx @@ -0,0 +1,67 @@ +import { + FastFieldProps, + Field, + FieldInputProps, + FieldMetaProps, + FormikProps, +} from "formik"; +import React, { useState } from "react"; +import InputField, { InputFieldProps } from "./input-field"; + +type InputFieldFormProps = { name: string; numeric?: boolean } & Omit< + InputFieldProps, + "onChange" | "onBlur" | "value" | "name" | "error" +>; + +function Wrapper( + props: InputFieldFormProps & { + field: FieldInputProps; + form: FormikProps; + meta: FieldMetaProps; + } +) { + const { name, field, form, meta, numeric, ...input } = props; + const val = + field.value === undefined || field.value === null ? "" : field.value; + const [value, setValue] = useState(val); + + return ( + { + if (numeric) { + if (!e.target.value) { + form.setFieldValue(name, undefined); + setValue(""); + } else { + form.setFieldValue( + name, + parseInt(e.target.value.replace(/\s/g, "") || undefined) + ); + setValue(e.target.value); + } + } else { + form.setFieldValue(name, e.target.value); + setValue(e.target.value); + } + }} + onBlur={field.onBlur} + error={(meta.touched && meta.error) ?? meta.error} + caption={meta.error || input.caption} + /> + ); +} + +export default function InputFieldForm(props: InputFieldFormProps) { + const { name, ...input } = props; + + return ( + + {({ field, form, meta }: FastFieldProps) => ( + + )} + + ); +} diff --git a/.legacy/form/input-field.tsx b/.legacy/form/input-field.tsx new file mode 100644 index 0000000..29f91fd --- /dev/null +++ b/.legacy/form/input-field.tsx @@ -0,0 +1,54 @@ +import React, { PropsWithChildren } from "react"; +import { useStyletron } from "styletron-react"; +import Field, { FieldProps } from "./field"; +import Input, { InputProps } from "./input"; + +export type InputFieldProps = PropsWithChildren<{ name: string }> & + FieldProps & + Omit; + +export default function InputField(props: InputFieldProps) { + const { + children, + autoHide, + classNameField, + name, + label, + caption, + error, + value, + onChange, + onBlur, + inline, + ...input + } = props; + const [css] = useStyletron(); + + return ( + +
+ + {children} +
+
+ ); +} diff --git a/.legacy/form/input.tsx b/.legacy/form/input.tsx new file mode 100644 index 0000000..8441bdf --- /dev/null +++ b/.legacy/form/input.tsx @@ -0,0 +1,51 @@ +import { Input as BaseInput } from "baseui/input"; +import React, { ReactNode } from "react"; +import { StyleObject } from "styletron-react"; + +export interface InputProps { + name?: string; + value?: string | number; + onChange?: (event: React.FormEvent) => void; + onBlur?: (e: React.FocusEvent) => void; + placeholder?: string; + disabled?: boolean; + error?: boolean; + prefix?: ReactNode; + suffix?: ReactNode; + style?: StyleObject; + clearable?: boolean; +} + +export default function Input(props: InputProps) { + const { + name, + value, + onChange, + placeholder, + disabled, + onBlur, + error, + style = {}, + clearable, + } = props; + + return ( + + ); +} diff --git a/.legacy/form/location-field-form.tsx b/.legacy/form/location-field-form.tsx new file mode 100644 index 0000000..9cb4fd6 --- /dev/null +++ b/.legacy/form/location-field-form.tsx @@ -0,0 +1,44 @@ +import { FastField, FastFieldProps } from "formik"; +import React, { PropsWithChildren } from "react"; +import LocationField, { LocationFieldProps } from "./location-field"; + +export type LocationFieldFormProps = { + name: string; + placeholder?: string; +} & Omit; + +export default function LocationFieldForm( + props: PropsWithChildren +) { + const { name, ...input } = props; + + return ( + + {({ + field, + form, + meta, + }: FastFieldProps< + { latitude?: number; longitude?: number; address?: string } | undefined + >) => { + const error = (meta.error as any) as + | { latitude?: string; longitude?: string; address?: string } + | string; + const errorMessage = + typeof error === "object" + ? error.address || error.latitude || error.longitude + : meta.error; + + return ( + form.setFieldValue(props.name, v)} + /> + ); + }} + + ); +} diff --git a/.legacy/form/location-field.tsx b/.legacy/form/location-field.tsx new file mode 100644 index 0000000..d721e77 --- /dev/null +++ b/.legacy/form/location-field.tsx @@ -0,0 +1,561 @@ +import { OnChangeParams, Select, TYPE } from "baseui/select"; +import { PropsWithChildren, ReactNode, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useStyletron } from "styletron-react"; +import Button, { ButtonAppearance } from "../button/button"; +import ListItem from "../list-item/list-item"; +import LogoLoader from "../loader/logo"; +import Maps, { Annotations } from "../maps"; +import { useMap } from "../maps/mapkit-provider"; +import { border, borderRadius, margin } from "../utils/css"; +import useGeocode from "../utils/geocoder"; +import { useLanguage } from "../utils/language"; +import CheckboxField from "./checkbox-field"; +import Field, { FieldProps } from "./field"; + +async function placesAround(lat: number, lng: number) { + const response = await fetch(`/api/places/${lat}/${lng}`); + const result = await response.json(); + return result; +} + +export type LocationFieldProps = { + showPlaces?: boolean; + showMap?: boolean; + name: string; + error?: string | ReactNode; + placeholder?: string; + value?: { + latitude?: number; + longitude?: number; + address?: string; + googlePlaceId?: string; + }; + setValue?: (v: { + latitude: number; + longitude: number; + address?: string; + radius?: number; + googlePlaceId?: string; + }) => void; +} & Omit; + +type Place = { + geometry: { + location: { + lat: number; + lng: number; + }; + }; + name: string; + place_id: string; + vicinity: string; +}; + +export default function LocationField( + props: PropsWithChildren +) { + const { + name, + label, + caption, + error, + autoHide, + value, + setValue = () => {}, + ...input + } = props; + const [t] = useTranslation(); + const defaultOptions = [ + { + id: 1, + label: t("location-field.use-my-location"), + }, + ]; + + const [information, setInformation] = useState(undefined); + const [search, setSearch] = useState(defaultOptions); + const [selectedOption, setSelectedOption] = useState(undefined); + const [isLoading, setLoading] = useState(false); + const { lang } = useLanguage(); + const { placeFromLocation, fromAddress } = useGeocode(lang); + const [places, setPlaces] = useState(undefined); + const [selectedValue, setSelectedValue] = + useState< + | { + latitude: number; + longitude: number; + address: string; + } + | undefined + >(undefined); + const [selectedPlace, setSelectedPlace] = + useState(undefined); + const { map, gMaps, isReady } = useMap(); + const [css] = useStyletron(); + + useEffect(() => { + if (map) { + map.addEventListener("drag-end", async function (event) { + const { latitude, longitude } = event.annotation.coordinate; + setValue({ ...event.annotation.coordinate, address: "." }); + showPlacesAround(latitude, longitude); + try { + const result = await placeFromLocation({ + latitude, + longitude, + }); + const v = { + id: result?.placeId, + label: result?.formattedAddress, + }; + + setValue({ + ...event.annotation.coordinate, + address: result?.formattedAddress, + }); + setSelectedValue({ + ...event.annotation.coordinate, + address: result?.formattedAddress || "", + }); + setSearch([ + ...defaultOptions, + { + ...v, + location: result?.geometry.location, + }, + ]); + setSelectedOption([ + { + ...v, + isCreatable: true, + }, + ]); + } catch (e) { + console.log(e); + } + }); + } + }, [map]); + + if (!isReady) { + return ; + } + + async function showPlacesAround(lat: number, lng: number) { + if (props.showPlaces) { + const response = await placesAround(lat, lng); + setPlaces(response.results); + } + } + + async function useMyLocation() { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + async ({ coords: { latitude, longitude } }) => { + showPlacesAround(latitude, longitude); + setValue({ latitude, longitude }); + const result = await placeFromLocation({ + latitude, + longitude, + }); + + const v = { + id: result?.placeId, + label: result?.formattedAddress, + }; + + setValue({ + latitude, + longitude, + address: result?.formattedAddress, + }); + + setSelectedValue({ + latitude, + longitude, + address: result?.formattedAddress || "", + }); + setSearch([ + ...defaultOptions, + { + ...v, + location: result?.geometry.location, + }, + ]); + setSelectedOption([ + { + ...v, + isCreatable: true, + }, + ]); + } + ); + } else { + setInformation("Geolocation is not supported by this browser."); + } + } + + async function onChange({ option, value: selectValue }: OnChangeParams) { + setSelectedOption(selectValue); + setLoading(true); + + if (option?.id === 1) { + if (navigator.geolocation) { + await new Promise((resolve) => { + navigator.geolocation.getCurrentPosition( + async ({ coords: { latitude, longitude } }) => { + showPlacesAround(latitude, longitude); + + const result = await placeFromLocation({ + latitude, + longitude, + }); + + const v = { + id: result?.placeId, + label: result?.formattedAddress, + }; + + setValue({ + latitude, + longitude, + address: result?.formattedAddress, + }); + + setSelectedValue({ + latitude, + longitude, + address: result?.formattedAddress || "", + }); + setSearch([ + ...defaultOptions, + { + ...v, + location: result?.geometry.location, + }, + ]); + setSelectedOption([ + { + ...v, + isCreatable: true, + }, + ]); + setLoading(false); + resolve({ latitude, longitude }); + }, + () => { + setLoading(false); + resolve({}); + } + ); + }); + } else { + setLoading(false); + setInformation("Geolocation is not supported by this browser."); + } + } else if (option?.isCreatable && option.id) { + try { + const result = await fromAddress(option.id.toString()); + const formatted = [ + { + id: result?.placeId, + label: result?.formattedAddress, + location: result?.geometry.location, + }, + ]; + if (result && result.placeId) { + setSearch([...defaultOptions, ...formatted]); + setSelectedOption([ + { + ...value, + id: result.placeId, + label: result.formattedAddress, + }, + ]); + setValue({ + latitude: result.geometry.location.lat, + longitude: result.geometry.location.lng, + address: result.formattedAddress, + }); + setSelectedValue({ + latitude: result.geometry.location.lat, + longitude: result.geometry.location.lng, + address: result.formattedAddress, + }); + showPlacesAround( + result.geometry.location.lat, + result.geometry.location.lng + ); + } + setLoading(false); + } catch (e) { + setLoading(false); + setSelectedOption([ + { + id: "Not found", + label: "Not found", + isCreatable: true, + }, + ]); + } + } else if (option?.label) { + const result = await fromAddress(option.label.toString()); + if (result && result.placeId) { + setSelectedOption([ + { + ...value, + id: result.placeId, + label: result.formattedAddress, + }, + ]); + setValue({ + latitude: result.geometry.location.lat, + longitude: result.geometry.location.lng, + address: result.formattedAddress, + }); + setSelectedValue({ + latitude: result.geometry.location.lat, + longitude: result.geometry.location.lng, + address: result.formattedAddress, + }); + showPlacesAround( + result.geometry.location.lat, + result.geometry.location.lng + ); + } + // setLoading(false); + } + + setLoading(false); + } + + const isEmpty = !selectedOption || selectedOption.length === 0; + + const autocomplete = new gMaps.places.AutocompleteService(); + + const placeholder = value?.address || props.placeholder; + + return ( + <> + + + {isDragActive ? ( +
{t("form.private-file-uploader.drop-here")}
+ ) : ( +
+ {uploading + ? t("form.private-file-uploader.uploading-files") + : t("form.private-file-uploader.click-or-drop-files")} +
+ )} + + ); + }} + +
+
+ {uploaded.map((file, index) => { + return ( +
+ {file.name} +
+ ); + })} +
+ + ); +} diff --git a/.legacy/form/radio-field-form.tsx b/.legacy/form/radio-field-form.tsx new file mode 100644 index 0000000..1e828f6 --- /dev/null +++ b/.legacy/form/radio-field-form.tsx @@ -0,0 +1,36 @@ +import { RadioGroup } from "baseui/radio"; +import { useField } from "formik"; +import React, { PropsWithChildren } from "react"; +import Field, { FieldProps } from "./field"; + +export type RadioFormProps = PropsWithChildren< + { name: string; align?: "horizontal" | "vertical" } & Omit< + FieldProps, + "error" + > +>; + +export default function RadioFieldForm(props: RadioFormProps) { + const { name, children, align, label, caption, autoHide, ...input } = props; + + const [{ value, onChange }, meta] = useField(name); + + return ( + + + {children} + + + ); +} diff --git a/.legacy/form/select-field-form.tsx b/.legacy/form/select-field-form.tsx new file mode 100644 index 0000000..9ac570e --- /dev/null +++ b/.legacy/form/select-field-form.tsx @@ -0,0 +1,166 @@ +import { FastField, FastFieldProps } from "formik"; +import React from "react"; +import { StyleObject } from "styletron-react"; +import SelectField, { SelectFieldProps } from "./select-field"; + +type SelectFieldFormProps = { name: string; style?: StyleObject } & Omit< + SelectFieldProps, + "onChange" | "onBlur" | "value" | "name" | "error" +>; + +export default function SelectFieldForm(props: SelectFieldFormProps) { + const { name, style, ...input } = props; + + return ( + + {({ + field, + form, + meta, + }: FastFieldProps) => ( + form.setFieldValue(name, v)} + error={(meta.touched && meta.error) ?? meta.error} + overrides={{ + ControlContainer: { + style: { + ...style, + }, + }, + Root: { + style: { + zIndex: 1, + }, + }, + Placeholder: { + style: { + zIndex: 1, + }, + }, + ValueContainer: { + style: { + zIndex: 1, + }, + }, + SingleValue: { + style: { + zIndex: 1, + }, + }, + MultiValue: { + style: { + zIndex: 1, + }, + }, + Tag: { + style: { + zIndex: 1, + }, + }, + InputContainer: { + style: { + zIndex: 1, + }, + }, + Input: { + style: { + zIndex: 1, + }, + }, + IconsContainer: { + style: { + zIndex: 1, + }, + }, + SelectArrow: { + style: { + zIndex: 1, + }, + }, + ClearIcon: { + style: { + zIndex: 1, + }, + }, + LoadingIndicator: { + style: { + zIndex: 1, + }, + }, + SearchIconContainer: { + style: { + zIndex: 1, + }, + }, + SearchIcon: { + style: { + zIndex: 1, + }, + }, + Popover: { + style: { + zIndex: 1, + }, + }, + DropdownContainer: { + style: { + zIndex: 1, + }, + }, + Dropdown: { + style: { + zIndex: 1, + }, + }, + DropdownOption: { + style: { + zIndex: 1, + }, + }, + DropdownListItem: { + style: { + zIndex: 1, + }, + }, + OptionContent: { + style: { + zIndex: 1, + }, + }, + StatefulMenu: { + style: { + zIndex: 1, + }, + }, + // Root?: Override; + // ControlContainer?: Override; + // Placeholder?: Override; + // ValueContainer?: Override; + // SingleValue?: Override; + // MultiValue?: Override; + // Tag?: Override; + // InputContainer?: Override; + // Input?: Override; + // IconsContainer?: Override; + // SelectArrow?: Override; + // ClearIcon?: Override; + // LoadingIndicator?: Override; + // SearchIconContainer?: Override; + // SearchIcon?: Override; + // Popover?: Override; + // DropdownContainer?: Override; + // Dropdown?: Override; + // DropdownOption?: Override; + // DropdownListItem?: Override; + // OptionContent?: Override; + // StatefulMenu?: Override; + // ...props.overrides, + }} + /> + )} + + ); +} diff --git a/.legacy/form/select-field.tsx b/.legacy/form/select-field.tsx new file mode 100644 index 0000000..ca75032 --- /dev/null +++ b/.legacy/form/select-field.tsx @@ -0,0 +1,63 @@ +import { + OnChangeParams, + Select, + SelectProps as BaseSelectProps, +} from "baseui/select"; +import { isNil, reject } from "ramda"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import Field, { FieldProps } from "./field"; + +type SelectOption = { + id: string; + label: string; +}; + +export type SelectProps = { + value?: string | string[]; + onChange?: (values?: string | string[]) => void; + options: Array; +} & Omit; + +export type SelectFieldProps = { name: string } & FieldProps & SelectProps; + +export default function SelectField(props: SelectFieldProps) { + const [t] = useTranslation(); + const { + autoHide, + name, + label, + caption, + error, + value, + options, + onChange, + ...input + } = props; + + const selectedValue = options.filter(({ id }) => + Array.isArray(value) ? value.find((v) => v === id) : id === value + ); + + const onSelect = (params: OnChangeParams) => { + if (input.multi) { + const fields = reject(isNil)(params.value.map(({ id }) => id)); + onChange && onChange(fields as any); + } else { + onChange && onChange(params.option?.id?.toString()); + } + }; + + return ( + + + + ); +} diff --git a/.legacy/form/slider-field-form.tsx b/.legacy/form/slider-field-form.tsx new file mode 100644 index 0000000..6044be8 --- /dev/null +++ b/.legacy/form/slider-field-form.tsx @@ -0,0 +1,30 @@ +import { FastField, FastFieldProps } from "formik"; +import React, { PropsWithChildren } from "react"; +import SliderField, { SliderFieldProps } from "./slider-field"; + +export type SliderFieldFormProps = { + name: string; + placeholder?: string; +} & Omit; + +export default function SliderFieldForm( + props: PropsWithChildren +) { + const { name, ...input } = props; + + return ( + + {({ field, form, meta }: FastFieldProps) => { + return ( + form.setFieldValue(props.name, v)} + /> + ); + }} + + ); +} diff --git a/.legacy/form/slider-field.tsx b/.legacy/form/slider-field.tsx new file mode 100644 index 0000000..4634d2a --- /dev/null +++ b/.legacy/form/slider-field.tsx @@ -0,0 +1,80 @@ +import { + Slider as BaseSlider, + SliderProps as BaseSliderProps, +} from "baseui/slider"; +import React, { useState } from "react"; +import Field, { FieldProps } from "./field"; +import Input from "./input"; + +export interface SliderProps { + showFields?: boolean; + range?: boolean; + min: number; + max: number; + value?: number[]; + onChange: (v: number[]) => void; +} + +export type SliderFieldProps = { name: string } & FieldProps & + SliderProps & + Omit; + +export default function SliderField(props: SliderFieldProps) { + const defaultValue = + props.value || props.range ? [props.min, props.max] : [props.min]; + const [continuousValue, setContinuousValue] = useState(defaultValue); + + const { + autoHide, + classNameField, + name, + label, + caption, + error, + // value, + onChange = () => {}, + ...input + } = props; + + return ( + +
+ + params.value && setContinuousValue(params.value) + } + onFinalChange={({ value }) => onChange(value)} + /> + {props.showFields && ( + <> + {props.range && ( + <> + + onChange([e.target.value, props.value?.[1]]) + } + /> + + onChange([props.value?.[0], e.target.value]) + } + /> + + )} + {!props.range && ( + onChange([e.target.value])} /> + )} + + )} +
+
+ ); +} diff --git a/.legacy/form/star-rating-field-form.tsx b/.legacy/form/star-rating-field-form.tsx new file mode 100644 index 0000000..2499c46 --- /dev/null +++ b/.legacy/form/star-rating-field-form.tsx @@ -0,0 +1,25 @@ +import { useField } from "formik"; +import StarRating, { StarRatingProps } from "../star-rating"; +import Field, { FieldProps } from "./field"; + +export type StarRatingFieldFormProps = { name: string } & Omit< + FieldProps, + "error" +> & + Omit; + +export default function StarRatingFieldForm(props: StarRatingFieldFormProps) { + const { name, label, caption, autoHide, ...input } = props; + const [{ value }, meta, { setValue }] = useField(name); + + return ( + + + + ); +} diff --git a/.legacy/form/textarea-field-form.tsx b/.legacy/form/textarea-field-form.tsx new file mode 100644 index 0000000..02226fc --- /dev/null +++ b/.legacy/form/textarea-field-form.tsx @@ -0,0 +1,32 @@ +import { useField } from "formik"; +import Textarea, { TextareaProps } from "../textarea"; +import Field, { FieldProps } from "./field"; + +export type TextareaFieldFormProps = { name: string } & Omit< + FieldProps, + "error" +> & + Omit; + +export default function TextareaFieldForm(props: TextareaFieldFormProps) { + const { name, label, caption, autoHide, ...input } = props; + const [{ value, onBlur, onChange }, meta] = useField(name); + + return ( + +