Remove unused hooks and utils, simplify Field component, and adjust imports
Some checks failed
forgejo/Procyon/procyon/pipeline/head There was a failure building this commit

This commit is contained in:
Roman Jaroš 2024-07-08 15:48:13 +00:00
parent deebdb261d
commit cda0d4569f
39 changed files with 2963 additions and 4872 deletions

View file

@ -18,7 +18,7 @@ import {
import { TolgeeChain, TolgeeProvider, useTolgee } from '@procyon/localization/tolgee'; import { TolgeeChain, TolgeeProvider, useTolgee } from '@procyon/localization/tolgee';
import { buildColorTheme } from '@procyon/styles/theme/color'; import { buildColorTheme } from '@procyon/styles/theme/color';
import { baseTheme } from '@procyon/styles/theme/base'; import { baseTheme } from '@procyon/styles/theme/base';
import { MediaQueryProvider } from '@procyon/hooks/useMediaQuery'; import { MediaQueryProvider } from '@procyon/styles/useMediaQuery';
import { ModalProvider } from '@procyon/components/Modal/context'; import { ModalProvider } from '@procyon/components/Modal/context';
import { Modals } from '@procyon/components/Modal'; import { Modals } from '@procyon/components/Modal';
import { Theme } from '@emotion/react'; import { Theme } from '@emotion/react';

View file

@ -26,77 +26,70 @@
"test": "jest --passWithNoTests" "test": "jest --passWithNoTests"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/native": "^11.11.0", "@emotion/native": "^11.11.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"clsx": "2.1.0",
"date-fns": "3.3.1", "date-fns": "3.3.1",
"postcss": "8.4.33", "lucide-react-native": "^0.402.0",
"ramda": "0.29.1", "ramda": "0.29.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-native": "0.74.1", "react-native": "0.74.1",
"react-native-advanced-ripple": "1.0.0", "react-native-advanced-ripple": "1.0.0",
"react-native-gesture-handler": "^2.16.2", "react-native-gesture-handler": "^2.16.1",
"react-native-reanimated": "3.7.0", "react-native-reanimated": "3.7.0",
"react-native-svg": "15.1.0", "react-native-svg": "15.1.0",
"react-native-web": "0.19.12", "react-native-web": "0.19.12",
"scroll-into-view-if-needed": "3.0.10", "scroll-into-view-if-needed": "3.1.0",
"lucide-react-native": "^0.379.0",
"tiny-invariant": "1.3.3", "tiny-invariant": "1.3.3",
"values.js": "^2.1.1", "values.js": "^2.1.1",
"wouter": "2.12.1", "wouter": "3.3.1",
"yup": "1.3.3" "yup": "1.3.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.23.9", "@babel/core": "7.24.7",
"@babel/preset-env": "^7.23.9", "@babel/preset-env": "^7.24.7",
"@babel/preset-typescript": "7.23.3", "@babel/preset-typescript": "7.24.7",
"@storybook/addon-actions": "^8.1.5", "@storybook/addon-actions": "^8.1.11",
"@storybook/addon-essentials": "^8.1.5", "@storybook/addon-essentials": "^8.1.11",
"@storybook/addon-links": "^8.1.5", "@storybook/addon-links": "^8.1.11",
"@storybook/addon-mdx-gfm": "^8.1.5", "@storybook/addon-mdx-gfm": "^8.1.11",
"@storybook/addon-react-native-web": "0.0.23", "@storybook/addon-react-native-web": "0.0.24",
"@storybook/addon-viewport": "^8.1.5", "@storybook/addon-viewport": "^8.1.11",
"@storybook/addon-webpack5-compiler-babel": "^3.0.3", "@storybook/addon-webpack5-compiler-babel": "^3.0.3",
"@storybook/blocks": "^8.1.5", "@storybook/blocks": "^8.1.11",
"@storybook/manager-api": "^8.1.5", "@storybook/manager-api": "^8.1.11",
"@storybook/react": "^8.1.5", "@storybook/react": "^8.1.11",
"@storybook/react-native": "7.6.19", "@storybook/react-native": "7.6.20",
"@storybook/react-webpack5": "^8.1.5", "@storybook/react-webpack5": "^8.1.11",
"@storybook/types": "^8.1.5", "@storybook/types": "^8.1.11",
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/mdx": "2.0.10", "@types/mdx": "2.0.13",
"@types/node": "18.18.2", "@types/node": "20.14.10",
"@types/ramda": "0.29.10", "@types/ramda": "0.30.1",
"@types/react": "18.2.52", "@types/react": "18.3.3",
"@types/react-dom": "18.2.18", "@types/react-dom": "18.3.0",
"@types/yup": "0.29.14", "@typescript-eslint/eslint-plugin": "7.15.0",
"@typescript-eslint/eslint-plugin": "6.20.0", "@typescript-eslint/parser": "7.15.0",
"@typescript-eslint/parser": "6.20.0", "autoprefixer": "10.4.19",
"autoprefixer": "10.4.17",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"babel-plugin-react-native-web": "^0.19.10", "babel-plugin-react-native-web": "^0.19.12",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-mdx": "3.1.5", "eslint-plugin-mdx": "3.1.5",
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.34.3",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-simple-import-sort": "12.1.1",
"eslint-plugin-typescript-sort-keys": "3.1.0", "eslint-plugin-typescript-sort-keys": "3.2.0",
"esprima": "4.0.1", "esprima": "4.0.1",
"http-server": "14.1.1", "http-server": "14.1.1",
"jest": "29.7.0", "jest": "29.7.0",
"nightwatch": "3.3.5", "prettier": "3.3.2",
"postcss-cli": "11.0.0",
"postcss-loader": "8.1.0",
"prettier": "3.2.4",
"raw-loader": "4.0.2", "raw-loader": "4.0.2",
"storybook": "^8.1.5", "storybook": "^8.1.5",
"tailwindcss": "3.4.1", "ts-jest": "29.1.5",
"ts-jest": "29.1.2",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.3.3" "typescript": "5.3.3"

View file

@ -16,12 +16,6 @@
"linkDirectory": false "linkDirectory": false
}, },
"dependencies": { "dependencies": {
"@procyon/auth": "workspace:^", "@procyon/auth": "workspace:^"
"@procyon/types": "workspace:^",
"@procyon/utils": "workspace:^",
"async-mutex": "0.4.1"
},
"peerDependencies": {
"@reduxjs/toolkit": "2.1.0"
} }
} }

View file

@ -15,11 +15,5 @@
{ {
"path": "../auth/tsconfig.build.json" "path": "../auth/tsconfig.build.json"
}, },
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../types/tsconfig.build.json"
},
], ],
} }

View file

@ -20,18 +20,16 @@
"@procyon/localization": "workspace:^", "@procyon/localization": "workspace:^",
"@procyon/types": "workspace:^", "@procyon/types": "workspace:^",
"@procyon/utils": "workspace:^", "@procyon/utils": "workspace:^",
"@procyon/styles": "workspace:^" "@procyon/styles": "workspace:^",
"values.js": "^2.1.1"
}, },
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/native": "^11.11.0",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"clsx": "2.1.0", "@emotion/native": "^11.11.0",
"date-fns": "3.3.1", "date-fns": "3.3.1",
"lucide-react-native": "^0.379.0",
"ramda": "0.29.1", "ramda": "0.29.1",
"scroll-into-view-if-needed": "3.1.0", "lucide-react-native": "^0.379.0",
"react-native-reanimated": "3.7.0", "react-native-reanimated": "3.7.0"
"react-native-svg": "15.1.0"
} }
} }

View file

@ -36,7 +36,7 @@ export const Alert: FC<AlertProps> = ({ color = 'secondary', title, Icon, childr
); );
}; };
const MessageContainer = styled.View<{ const MessageContainer = styled(View)<{
color: AlertProps['color']; color: AlertProps['color'];
}>(({ theme, color }) => { }>(({ theme, color }) => {
return { return {

View file

@ -1,13 +1,13 @@
import React, { FC, ReactNode, useState } from 'react'; import React, { FC, ReactNode, useState } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { Theme } from '@emotion/react';
import { MenuIcon } from 'lucide-react-native'; import { MenuIcon } from 'lucide-react-native';
import { View } from 'react-native'; import { View } from 'react-native';
import { Modals } from '@procyon/components/Modal/Modals'; import { Modals } from '@procyon/components/Modal/Modals';
import { Toasters } from '@procyon/components/Toaster'; import { Toasters } from '@procyon/components/Toaster';
import { ToastersProps } from '@procyon/components/Toaster/Toasters'; import { ToastersProps } from '@procyon/components/Toaster/Toasters';
import { useScreenDimensions } from '@procyon/hooks/useScreenDimensions'; import { useMediaQuery } from '@procyon/styles/useMediaQuery';
import { mQ } from '@procyon/styles/mq';
export type AppLayoutProps = { export type AppLayoutProps = {
Footer?: FC; Footer?: FC;
@ -31,7 +31,7 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
const enabledSideNav = !!SideNav; const enabledSideNav = !!SideNav;
const enabledTopNav = !!TopNav || !!UserNav; const enabledTopNav = !!TopNav || !!UserNav;
useScreenDimensions(); const mq = useMediaQuery();
const [sideCollapsed, setSideCollapsed] = useState(false); const [sideCollapsed, setSideCollapsed] = useState(false);
const [topCollapsed, setTopCollapsed] = useState(true); const [topCollapsed, setTopCollapsed] = useState(true);
@ -47,10 +47,11 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
return ( return (
<AppLayoutContainer> <AppLayoutContainer>
{enabledTopNav && ( {enabledTopNav && (
<AppLayoutTopNav onPress={toggleTop}> <AppLayoutTopNav mq={mq} onPress={toggleTop}>
<AppLayoutTopLeftSide placeholder={!Logo}> <AppLayoutTopLeftSide mq={mq} placeholder={!Logo}>
<AppLayoutLogo>{Logo ?? <View />}</AppLayoutLogo> <AppLayoutLogo>{Logo ?? <View />}</AppLayoutLogo>
<AppLayoutMenuToggle <AppLayoutMenuToggle
mq={mq}
style={{ style={{
marginBottom: 2, marginBottom: 2,
}} }}
@ -58,12 +59,14 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
/> />
</AppLayoutTopLeftSide> </AppLayoutTopLeftSide>
<AppLayoutNavSection <AppLayoutNavSection
mq={mq}
visible={!topCollapsed} visible={!topCollapsed}
direction="row" direction="row"
justifyContent="flex-start"> justifyContent="flex-start">
{TopNav?.map((Nav: any, i) => <Nav key={i} />)} {TopNav?.map((Nav: any, i) => <Nav key={i} />)}
</AppLayoutNavSection> </AppLayoutNavSection>
<AppLayoutNavSection <AppLayoutNavSection
mq={mq}
visible={!topCollapsed} visible={!topCollapsed}
direction="row" direction="row"
justifyContent="flex-end"> justifyContent="flex-end">
@ -71,11 +74,12 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
</AppLayoutNavSection> </AppLayoutNavSection>
</AppLayoutTopNav> </AppLayoutTopNav>
)} )}
<AppLayoutContent> <AppLayoutContent mq={mq}>
{enabledSideNav && ( {enabledSideNav && (
<AppLayoutSideNav onPress={toggleSide}> <AppLayoutSideNav mq={mq} onPress={toggleSide}>
<AppLayoutMenuToggle style={{ minWidth: 30 }} strokeWidth={1} /> <AppLayoutMenuToggle mq={mq} style={{ minWidth: 30 }} strokeWidth={1} />
<AppLayoutNavSection <AppLayoutNavSection
mq={mq}
visible={sideCollapsed} visible={sideCollapsed}
direction="column" direction="column"
justifyContent="center"> justifyContent="center">
@ -92,25 +96,25 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
); );
}; };
const AppLayoutContainer = styled.View(({ theme }) => { const AppLayoutContainer = styled(View)(({ theme }) => {
return { return {
backgroundColor: theme.palette.background.main, backgroundColor: theme.palette.background.main,
}; };
}); });
const AppLayoutNavSection = styled.View<{ const AppLayoutNavSection = styled(View)<{
direction: 'row' | 'column'; direction: 'row' | 'column';
justifyContent: 'flex-start' | 'center' | 'flex-end'; justifyContent: 'flex-start' | 'center' | 'flex-end';
mq: keyof Theme['breakpoints'];
visible: boolean; visible: boolean;
}>(({ theme, direction, visible, justifyContent }) => { }>(({ theme, direction, visible, justifyContent, mq }) => {
const br = mQ(theme);
return { return {
display: 'flex', display: 'flex',
flexDirection: direction, flexDirection: direction,
columnGap: 4, columnGap: 4,
rowGap: 4, rowGap: 4,
...br.xs({ ...(mq === 'xs' && {
marginTop: theme.spacing.sm, marginTop: theme.spacing.sm,
flexWrap: 'wrap', flexWrap: 'wrap',
flex: 1, flex: 1,
@ -124,8 +128,8 @@ const AppLayoutNavSection = styled.View<{
}; };
}); });
const AppLayoutTopNav = styled.Pressable(({ theme }) => { const AppLayoutTopNav = styled.Pressable<{ mq: keyof Theme['breakpoints'] }>(
const br = mQ(theme); ({ theme, mq }) => {
return { return {
backgroundColor: theme.palette.primary.light, backgroundColor: theme.palette.primary.light,
display: 'flex', display: 'flex',
@ -135,23 +139,24 @@ const AppLayoutTopNav = styled.Pressable(({ theme }) => {
alignItems: 'center', alignItems: 'center',
padding: theme.spacing.sm, padding: theme.spacing.sm,
...br.xs({ ...(mq === 'xs' && {
flexDirection: 'column', flexDirection: 'column',
alignItems: 'stretch', alignItems: 'stretch',
}), }),
}; };
}); },
);
const AppLayoutTopLeftSide = styled.View<{ const AppLayoutTopLeftSide = styled(View)<{
mq: keyof Theme['breakpoints'];
placeholder: boolean; placeholder: boolean;
}>(({ theme, placeholder }) => { }>(({ placeholder, mq }) => {
const br = mQ(theme);
return { return {
...(placeholder && { ...(placeholder && {
display: 'none', display: 'none',
}), }),
...br.xs({ ...(mq === 'xs' && {
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -168,33 +173,36 @@ const AppLayoutLogo = styled.Text(({ theme }) => {
}; };
}); });
const AppLayoutMenuToggle = styled(MenuIcon)(({ theme }) => { const AppLayoutMenuToggle = styled(MenuIcon)<{
const br = mQ(theme); mq: keyof Theme['breakpoints'];
}>(({ mq }) => {
return { return {
display: 'none', display: 'none',
...br.xs({ ...(mq === 'xs' && {
display: 'flex', display: 'flex',
}), }),
}; };
}); });
const AppLayoutContent = styled.View(({ theme }) => { const AppLayoutContent = styled(View)<{
const br = mQ(theme); mq: keyof Theme['breakpoints'];
}>(({ theme, mq }) => {
return { return {
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
gap: 4, gap: 4,
marginTop: theme.spacing.md, marginTop: theme.spacing.md,
...br.xs({ ...(mq === 'xs' && {
flexDirection: 'column', flexDirection: 'column',
marginTop: theme.spacing.sm, marginTop: theme.spacing.sm,
}), }),
}; };
}); });
const AppLayoutSideNav = styled.Pressable(({ theme }) => { const AppLayoutSideNav = styled.Pressable<{
const br = mQ(theme); mq: keyof Theme['breakpoints'];
}>(({ theme, mq }) => {
return { return {
width: 150, width: 150,
borderRadius: theme.rounded.small, borderRadius: theme.rounded.small,
@ -206,7 +214,7 @@ const AppLayoutSideNav = styled.Pressable(({ theme }) => {
padding: theme.spacing.sm, padding: theme.spacing.sm,
marginRight: 2, marginRight: 2,
...br.xs({ ...(mq === 'xs' && {
width: 'auto', width: 'auto',
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',
@ -215,7 +223,7 @@ const AppLayoutSideNav = styled.Pressable(({ theme }) => {
}; };
}); });
const AppLayoutPage = styled.View(({ theme }) => { const AppLayoutPage = styled(View)(({ theme }) => {
return { return {
padding: theme.spacing.md, padding: theme.spacing.md,
flex: 1, flex: 1,

View file

@ -1,15 +1,19 @@
import React, { ReactNode, useMemo, useState } from 'react'; import React, { FC, ReactNode, useMemo, useState } from 'react';
import styled, { css } from '@emotion/native'; import styled, { css } from '@emotion/native';
import { LucideProps } from 'lucide-react-native';
import { Platform, StyleProp, TextStyle, View, ViewStyle } from 'react-native'; import { Platform, StyleProp, TextStyle, View, ViewStyle } from 'react-native';
import { useAnimatedRef } from 'react-native-reanimated'; import { useAnimatedRef } from 'react-native-reanimated';
import Values from 'values.js'; import Values from 'values.js';
import { RippleEffect } from '@procyon/components/RippleEffect'; import { RippleEffect } from '@procyon/components/RippleEffect';
import { useMediaQuery } from '@procyon/hooks/useMediaQuery'; import { Stack } from '@procyon/components/Stack';
import { useTheme } from '@procyon/styles/ThemeProvider'; import { useTheme } from '@procyon/styles/ThemeProvider';
import { isDark } from '@procyon/utils/color'; import { useMediaQuery } from '@procyon/styles/useMediaQuery';
import { isDark } from '@procyon/styles/utils/color';
export type ButtonProps = { export type ButtonProps = {
LeftIcon?: FC<LucideProps>;
RightIcon?: FC<LucideProps>;
children: ReactNode; children: ReactNode;
color?: 'primary' | 'secondary' | 'positive' | 'negative'; color?: 'primary' | 'secondary' | 'positive' | 'negative';
disabled?: boolean; disabled?: boolean;
@ -33,6 +37,8 @@ export const Button: React.FC<ButtonProps> = (props) => {
variant = 'contained', variant = 'contained',
fullWidth, fullWidth,
style, style,
LeftIcon,
RightIcon,
} = props; } = props;
const rippleAnimateRef = useAnimatedRef<View>(); const rippleAnimateRef = useAnimatedRef<View>();
@ -79,6 +85,8 @@ export const Button: React.FC<ButtonProps> = (props) => {
style?.container, style?.container,
], ],
}}> }}>
<Stack direction="row" gap={1} alignItems="center">
{LeftIcon && <LeftIcon />}
<ButtonText <ButtonText
variant={variant} variant={variant}
size={size} size={size}
@ -87,6 +95,8 @@ export const Button: React.FC<ButtonProps> = (props) => {
disabled={disabled}> disabled={disabled}>
{children} {children}
</ButtonText> </ButtonText>
{RightIcon && <RightIcon />}
</Stack>
</RippleEffect> </RippleEffect>
</ButtonContainer> </ButtonContainer>
); );

View file

@ -10,7 +10,7 @@ import { Platform, StyleProp, View, ViewStyle } from 'react-native';
import { ButtonProps } from '@procyon/components/Button'; import { ButtonProps } from '@procyon/components/Button';
import { useTheme } from '@procyon/styles/ThemeProvider'; import { useTheme } from '@procyon/styles/ThemeProvider';
import { isNilOrEmpty } from '@procyon/utils/isNilOrEmpty'; import { notNil } from '@procyon/utils/nil';
export type ButtonGroupProps = { export type ButtonGroupProps = {
children: ReactNode[]; children: ReactNode[];
@ -29,7 +29,7 @@ export const ButtonGroup = forwardRef(
ref: MutableRefObject<View>, ref: MutableRefObject<View>,
) => { ) => {
const { theme } = useTheme(); const { theme } = useTheme();
const filteredChildren = children.filter((val) => !isNilOrEmpty(val)); const filteredChildren = children.filter(notNil);
return ( return (
<View <View

View file

@ -6,7 +6,7 @@ import { add, endOfMonth, getWeeksInMonth, startOfMonth, sub } from 'date-fns';
import { Grid } from '@procyon/components/Grid'; import { Grid } from '@procyon/components/Grid';
import { Stack } from '@procyon/components/Stack'; import { Stack } from '@procyon/components/Stack';
import { useTheme } from '@procyon/styles/ThemeProvider'; import { useTheme } from '@procyon/styles/ThemeProvider';
import { isNilOrEmpty } from '@procyon/utils/isNilOrEmpty'; import { notNil } from '@procyon/utils/nil';
import { CalendarWeek } from './CalendarWeek'; import { CalendarWeek } from './CalendarWeek';
import { Controls, ControlsProps } from './Controls'; import { Controls, ControlsProps } from './Controls';
@ -189,7 +189,7 @@ export function Calendar({
return ( return (
<Stack direction="column" gap={theme.spacing.xl}> <Stack direction="column" gap={theme.spacing.xl}>
{!isNilOrEmpty(title) && <CalendarTitle>{title}</CalendarTitle>} {notNil(title) && <CalendarTitle>{title}</CalendarTitle>}
<ControlsView <ControlsView
layout={layout} layout={layout}
onNextClick={handleClickNext} onNextClick={handleClickNext}

View file

@ -86,7 +86,7 @@ export const DateSlot: FC<DateSlotProps> = ({
); );
}; };
const CalendarDateContainer = styled.View<{ const CalendarDateContainer = styled(View)<{
hovered: boolean; hovered: boolean;
isOtherMonth: boolean; isOtherMonth: boolean;
isSelected: DateSlotProps['isSelected']; isSelected: DateSlotProps['isSelected'];

View file

@ -1,6 +1,6 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { StyleProp, TextStyle, ViewStyle } from 'react-native'; import { StyleProp, TextStyle, View, ViewStyle } from 'react-native';
import { CalendarProps } from '@procyon/components/Calendar/index'; import { CalendarProps } from '@procyon/components/Calendar/index';
import { Grid } from '@procyon/components/Grid'; import { Grid } from '@procyon/components/Grid';
@ -50,7 +50,7 @@ export const WeekDays: FC<WeekDaysProps> = ({ workDate, layout, longNames, style
); );
}; };
const CalendarWeekDaysContainer = styled.View(({ theme }) => { const CalendarWeekDaysContainer = styled(View)(({ theme }) => {
return { return {
borderBottomWidth: 1, borderBottomWidth: 1,
borderColor: theme.palette.primary.dark, borderColor: theme.palette.primary.dark,
@ -59,7 +59,7 @@ const CalendarWeekDaysContainer = styled.View(({ theme }) => {
}; };
}); });
const CalendarWeekDays = styled.View(() => { const CalendarWeekDays = styled(View)(() => {
return { return {
alignItems: 'center', alignItems: 'center',
}; };

View file

@ -10,7 +10,7 @@ import Animated, {
import { FieldMessage } from '@procyon/components/FieldMessage'; import { FieldMessage } from '@procyon/components/FieldMessage';
import { Label } from '@procyon/components/Label'; import { Label } from '@procyon/components/Label';
import { useMediaQuery } from '@procyon/hooks/useMediaQuery'; import { useMediaQuery } from '@procyon/styles/useMediaQuery';
import { InputElementType } from '@procyon/types/form'; import { InputElementType } from '@procyon/types/form';
export type CheckboxProps<V = boolean> = Omit< export type CheckboxProps<V = boolean> = Omit<
@ -137,7 +137,7 @@ const CheckboxInput = styled.Pressable<{
}, },
); );
const CheckboxSwitch = styled.View<{ const CheckboxSwitch = styled(View)<{
checked: CheckboxProps['checked']; checked: CheckboxProps['checked'];
color: CheckboxProps['color']; color: CheckboxProps['color'];
disabled: CheckboxProps['disabled']; disabled: CheckboxProps['disabled'];

View file

@ -2,7 +2,7 @@ import React, { Children, FC, ReactNode } from 'react';
import { range } from 'ramda'; import { range } from 'ramda';
import { StyleProp, View, ViewStyle } from 'react-native'; import { StyleProp, View, ViewStyle } from 'react-native';
import { useMediaQuery } from '@procyon/hooks/useMediaQuery'; import { useMediaQuery } from '@procyon/styles/useMediaQuery';
export type GridProps = { export type GridProps = {
children: ReactNode; children: ReactNode;

View file

@ -1,6 +1,12 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { FlatList, GestureResponderEvent, StyleProp, ViewStyle } from 'react-native'; import {
FlatList,
GestureResponderEvent,
StyleProp,
View,
ViewStyle,
} from 'react-native';
import { ListItem, ListItemProps } from '@procyon/components/ListItem'; import { ListItem, ListItemProps } from '@procyon/components/ListItem';
@ -74,7 +80,7 @@ export const List = <Items extends ListData[]>({
); );
}; };
const ListContainer = styled.View(({ theme }) => { const ListContainer = styled(View)(({ theme }) => {
return { return {
borderRadius: theme.rounded.small, borderRadius: theme.rounded.small,
backgroundColor: theme.palette.background.main, backgroundColor: theme.palette.background.main,

View file

@ -1,13 +1,13 @@
import React, { ReactNode, useMemo, useState } from 'react'; import React, { ReactNode, useMemo, useState } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { GestureResponderEvent, StyleProp, View, ViewProps } from 'react-native'; import { GestureResponderEvent, StyleProp, Text, View, ViewProps } from 'react-native';
import { useAnimatedRef } from 'react-native-reanimated'; import { useAnimatedRef } from 'react-native-reanimated';
import Values from 'values.js'; import Values from 'values.js';
import { ListData } from '@procyon/components/List'; import { ListData } from '@procyon/components/List';
import { RippleEffect } from '@procyon/components/RippleEffect'; import { RippleEffect } from '@procyon/components/RippleEffect';
import { useTheme } from '@procyon/styles/ThemeProvider'; import { useTheme } from '@procyon/styles/ThemeProvider';
import { isDark } from '@procyon/utils/color'; import { isDark } from '@procyon/styles/utils/color';
import { debounceAfter } from '@procyon/utils/debounce'; import { debounceAfter } from '@procyon/utils/debounce';
type CommonProps<Item extends ListData> = { type CommonProps<Item extends ListData> = {
@ -125,7 +125,7 @@ const ListItemContainer = styled.Pressable<{
}; };
}); });
export const ListItemView = styled.View(() => { export const ListItemView = styled(View)(() => {
return { return {
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
@ -133,7 +133,7 @@ export const ListItemView = styled.View(() => {
}; };
}); });
export const ListItemText = styled.Text<{ export const ListItemText = styled(Text)<{
color?: ListItemProps<any, any>['color']; color?: ListItemProps<any, any>['color'];
hover?: boolean; hover?: boolean;
selected?: ListItemProps<any, any>['selected']; selected?: ListItemProps<any, any>['selected'];

View file

@ -1,9 +1,9 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { Modal, Platform, View } from 'react-native'; import { Modal, Platform, Pressable, Text, View } from 'react-native';
import Values from 'values.js'; import Values from 'values.js';
import { isNilOrEmpty } from '@procyon/utils/isNilOrEmpty'; import { notNil } from '@procyon/utils/nil';
import { ModalId, useModal } from './context'; import { ModalId, useModal } from './context';
@ -33,7 +33,7 @@ export const Modals = () => {
} }
}, [opened]); }, [opened]);
if (isNilOrEmpty(opened)) { if (!notNil(opened)) {
return null; return null;
} }
@ -49,7 +49,7 @@ export const Modals = () => {
}); });
}; };
const ModalOverflow = styled.Pressable(({ theme }) => { const ModalOverflow = styled(Pressable)(({ theme }) => {
return { return {
...(Platform.OS === 'web' && ({ cursor: 'close' } as any)), ...(Platform.OS === 'web' && ({ cursor: 'close' } as any)),
position: 'absolute', position: 'absolute',
@ -62,7 +62,7 @@ const ModalOverflow = styled.Pressable(({ theme }) => {
}; };
}); });
export const ModalContainer = styled.Pressable(({ theme }) => { export const ModalContainer = styled(Pressable)(({ theme }) => {
return { return {
width: 200, width: 200,
marginLeft: 'auto', marginLeft: 'auto',
@ -76,7 +76,7 @@ export const ModalContainer = styled.Pressable(({ theme }) => {
}; };
}); });
export const ModalTitle = styled.Text(({ theme }) => { export const ModalTitle = styled(Text)(({ theme }) => {
return { return {
fontSize: theme.fontSize.lg, fontSize: theme.fontSize.lg,
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,

View file

@ -1,5 +1,6 @@
import React, { FC, useMemo, useState } from 'react'; import React, { FC, useMemo, useState } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; import Animated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
@ -124,7 +125,7 @@ export const Puzzle: FC<PuzzleProps> = ({
); );
}; };
const PuzzleContainer = styled.View(({ theme }) => { const PuzzleContainer = styled(View)(({ theme }) => {
return { return {
position: 'relative', position: 'relative',
borderWidth: 1, borderWidth: 1,

View file

@ -4,7 +4,7 @@ import { View } from 'react-native';
import { Label } from '@procyon/components/Label'; import { Label } from '@procyon/components/Label';
import { RadioItem } from '@procyon/components/RadioItem'; import { RadioItem } from '@procyon/components/RadioItem';
import { useMediaQuery } from '@procyon/hooks/useMediaQuery'; import { useMediaQuery } from '@procyon/styles/useMediaQuery';
import { InputElementType } from '@procyon/types/form'; import { InputElementType } from '@procyon/types/form';
import { FieldMessage } from './FieldMessage'; import { FieldMessage } from './FieldMessage';
@ -90,7 +90,7 @@ export const RadioButton: FC<RadioButtonProps> = ({
); );
}; };
const RadioButtonOptions = styled.View<{ const RadioButtonOptions = styled(View)<{
horizontal: RadioButtonProps['horizontal']; horizontal: RadioButtonProps['horizontal'];
}>(({ theme, horizontal }) => { }>(({ theme, horizontal }) => {
return { return {

View file

@ -1,7 +1,7 @@
import React, { FC, useMemo } from 'react'; import React, { FC, useMemo } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { ChevronDownIcon } from 'lucide-react-native'; import { ChevronDownIcon } from 'lucide-react-native';
import { Platform, Pressable, StyleProp, View, ViewStyle } from 'react-native'; import { Platform, Pressable, StyleProp, Text, View, ViewStyle } from 'react-native';
import { measure, runOnJS, runOnUI, useAnimatedRef } from 'react-native-reanimated'; import { measure, runOnJS, runOnUI, useAnimatedRef } from 'react-native-reanimated';
import { List, ListData, ListProps } from '@procyon/components/List'; import { List, ListData, ListProps } from '@procyon/components/List';
@ -171,7 +171,7 @@ const DefaultInput = <Items extends ListData[]>({
return <FieldText>{getLabel ? getLabel(item) : item.label}</FieldText>; return <FieldText>{getLabel ? getLabel(item) : item.label}</FieldText>;
}; };
export const FieldText = styled.Text<{ export const FieldText = styled(Text)<{
size?: SelectboxProps<any>['size']; size?: SelectboxProps<any>['size'];
}>(({ theme, size = 'md' }) => { }>(({ theme, size = 'md' }) => {
return { return {

View file

@ -1,6 +1,6 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { Text } from 'react-native'; import { Text, View } from 'react-native';
import Values from 'values.js'; import Values from 'values.js';
import { Grid, GridProps } from '@procyon/components/Grid'; import { Grid, GridProps } from '@procyon/components/Grid';
@ -83,14 +83,14 @@ const TableContainer = styled(Stack)(({ theme }) => {
}; };
}); });
export const TableHead = styled.View(({ theme }) => { export const TableHead = styled(View)(({ theme }) => {
return { return {
justifyContent: 'center', justifyContent: 'center',
padding: theme.spacing.md, padding: theme.spacing.md,
}; };
}); });
export const TableCell = styled.View(({ theme }) => { export const TableCell = styled(View)(({ theme }) => {
return { return {
justifyContent: 'center', justifyContent: 'center',
padding: theme.spacing.md, padding: theme.spacing.md,

View file

@ -14,8 +14,8 @@ import { NativeSyntheticEvent } from 'react-native/Libraries/Types/CoreEventType
import Values from 'values.js'; import Values from 'values.js';
import { FieldMessage } from '@procyon/components/FieldMessage'; import { FieldMessage } from '@procyon/components/FieldMessage';
import { useMediaQuery } from '@procyon/hooks/useMediaQuery';
import { useTheme } from '@procyon/styles/ThemeProvider'; import { useTheme } from '@procyon/styles/ThemeProvider';
import { useMediaQuery } from '@procyon/styles/useMediaQuery';
import { InputElementType } from '@procyon/types/form'; import { InputElementType } from '@procyon/types/form';
import { Masked } from '@procyon/utils/imask'; import { Masked } from '@procyon/utils/imask';
@ -188,7 +188,7 @@ export const TextField: React.FC<
); );
}; };
const TextFieldWrapper = styled.View<{ const TextFieldWrapper = styled(View)<{
color: TextFieldProps['color']; color: TextFieldProps['color'];
disabled: boolean; disabled: boolean;
}>(({ theme, color, disabled }) => { }>(({ theme, color, disabled }) => {
@ -200,7 +200,7 @@ const TextFieldWrapper = styled.View<{
borderRadius: theme.rounded.small, borderRadius: theme.rounded.small,
paddingHorizontal: theme.spacing.lg, paddingHorizontal: theme.spacing.lg,
borderWidth: 1, borderWidth: 1,
borderColor: theme.palette[color!].main, borderColor: theme.palette[color!].dark,
backgroundColor: theme.palette[color!].light, backgroundColor: theme.palette[color!].light,
...(color === 'secondary' && { ...(color === 'secondary' && {
@ -229,7 +229,7 @@ const TextFieldInput = styled.TextInput<{
}; };
}); });
const TextFieldIconContainer = styled.View(() => { const TextFieldIconContainer = styled(View)(() => {
return { return {
justifyContent: 'center', justifyContent: 'center',
}; };

View file

@ -1,7 +1,8 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import styled from '@emotion/native'; import styled from '@emotion/native';
import { View } from 'react-native';
import { isNilOrEmpty } from '@procyon/utils/isNilOrEmpty'; import { notNil } from '@procyon/utils/nil';
import { Alert } from '../Alert'; import { Alert } from '../Alert';
import { useToaster } from './hooks'; import { useToaster } from './hooks';
@ -21,7 +22,7 @@ export type ToastersProps = {
export const Toasters: FC<ToastersProps> = ({ position }) => { export const Toasters: FC<ToastersProps> = ({ position }) => {
const { toasters } = useToaster(); const { toasters } = useToaster();
if (isNilOrEmpty(toasters)) { if (!notNil(toasters)) {
return null; return null;
} }
@ -39,7 +40,7 @@ export const Toasters: FC<ToastersProps> = ({ position }) => {
); );
}; };
const ToastersContainer = styled.View<{ const ToastersContainer = styled(View)<{
position: ToastersProps['position']; position: ToastersProps['position'];
}>( }>(
() => { () => {

View file

@ -20,14 +20,10 @@
"@procyon/components": "workspace:^", "@procyon/components": "workspace:^",
"@procyon/types": "workspace:^", "@procyon/types": "workspace:^",
"@procyon/utils": "workspace:^", "@procyon/utils": "workspace:^",
"react-hook-form": "7.50.0" "react-hook-form": "7.50.0",
"tiny-invariant": "1.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.11.4", "yup": "1.3.3"
"@emotion/native": "^11.11.0",
"@emotion/styled": "^11.11.5",
"yup": "1.3.3",
"clsx": "2.1.0",
"tiny-invariant": "1.3.3"
} }
} }

View file

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import styled from '@emotion/native';
import { useController, useFormContext } from 'react-hook-form'; import { useController, useFormContext } from 'react-hook-form';
import { Checkbox } from '@procyon/components/Checkbox'; import { Checkbox } from '@procyon/components/Checkbox';
@ -23,7 +22,6 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
switch (props.component) { switch (props.component) {
case 'TEXT': case 'TEXT':
return ( return (
<FieldWrapper>
<TextField <TextField
{...props} {...props}
value={field.value} value={field.value}
@ -34,11 +32,9 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
onChange={(e, value) => field.onChange(value)} onChange={(e, value) => field.onChange(value)}
onBlur={field.onBlur} onBlur={field.onBlur}
/> />
</FieldWrapper>
); );
case 'CHECK': case 'CHECK':
return ( return (
<FieldWrapper>
<Checkbox <Checkbox
{...props} {...props}
{...(fieldState.error && { {...(fieldState.error && {
@ -48,11 +44,9 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
checked={field.value} checked={field.value}
onChange={(e, checked) => field.onChange(checked)} onChange={(e, checked) => field.onChange(checked)}
/> />
</FieldWrapper>
); );
case 'RADIO': case 'RADIO':
return ( return (
<FieldWrapper>
<RadioButton <RadioButton
{...props} {...props}
value={field.value} value={field.value}
@ -63,11 +57,9 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
onChange={(e, value) => field.onChange(value)}> onChange={(e, value) => field.onChange(value)}>
{props.children} {props.children}
</RadioButton> </RadioButton>
</FieldWrapper>
); );
case 'SELECT': case 'SELECT':
return ( return (
<FieldWrapper>
<Selectbox <Selectbox
{...props} {...props}
value={field.value} value={field.value}
@ -77,11 +69,9 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
})} })}
onChange={(e, value) => field.onChange(value)} onChange={(e, value) => field.onChange(value)}
/> />
</FieldWrapper>
); );
case 'DATE': case 'DATE':
return ( return (
<FieldWrapper>
<DatePicker <DatePicker
{...props} {...props}
value={field.value} value={field.value}
@ -91,11 +81,9 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
})} })}
onChange={(e, value) => field.onChange(value)} onChange={(e, value) => field.onChange(value)}
/> />
</FieldWrapper>
); );
case 'TIME': case 'TIME':
return ( return (
<FieldWrapper>
<TimePicker <TimePicker
{...props} {...props}
value={field.value} value={field.value}
@ -105,15 +93,8 @@ export const Field = <E extends ListData[]>({ ...props }: FieldProps<E>) => {
})} })}
onChange={(e, value) => field.onChange(value)} onChange={(e, value) => field.onChange(value)}
/> />
</FieldWrapper>
); );
default: default:
return null; return null;
} }
}; };
export const FieldWrapper = styled.View(({ theme }) => {
return {
marginVertical: theme.spacing.md,
};
}) as any; // inferred type issue, IDK

View file

@ -14,8 +14,5 @@
"publishConfig": { "publishConfig": {
"directory": "dist", "directory": "dist",
"linkDirectory": false "linkDirectory": false
},
"dependencies": {
"@procyon/styles": "workspace:^"
} }
} }

View file

@ -1,22 +0,0 @@
import { useEffect, useState } from 'react';
import { Dimensions } from 'react-native';
export function useScreenDimensions(): { height: number; width: number } {
const initialDimensions = Dimensions.get('screen');
const [width, setWidth] = useState(initialDimensions.width);
const [height, setHeight] = useState(initialDimensions.height);
const handleChange = ({ screen }) => {
setWidth(screen.width);
setHeight(screen.height);
};
useEffect(() => {
const sub = Dimensions.addEventListener('change', handleChange);
return () => {
sub.remove();
};
}, []);
return { width, height };
}

View file

@ -5,7 +5,7 @@
"author": "romanjaros <jarosr93@gmail.com>", "author": "romanjaros <jarosr93@gmail.com>",
"license": "ISC", "license": "ISC",
"scripts": { "scripts": {
"clean": "rm -rf dist/", "clean": "rm -rf dist/ && npm run build -- --clean",
"build": "tsc --b tsconfig.build.json", "build": "tsc --b tsconfig.build.json",
"prepublish": "cp package.json dist", "prepublish": "cp package.json dist",
"postpublish": "rm dist/package.json", "postpublish": "rm dist/package.json",
@ -14,13 +14,5 @@
"publishConfig": { "publishConfig": {
"directory": "dist", "directory": "dist",
"linkDirectory": false "linkDirectory": false
},
"dependencies": {
"postcss-import": "16.0.0",
"postcss-nested": "6.0.1"
},
"peerDependencies": {
"postcss": "8.4.33",
"tailwindcss": "3.4.1"
} }
} }

View file

@ -1,32 +0,0 @@
import { Theme } from '@emotion/react';
import { Dimensions, StyleProp, TextStyle, ViewStyle } from 'react-native';
type StyleProps = StyleProp<ViewStyle | TextStyle>;
// Function to apply style based on dimension constraints
const applyStyleIfWithinRange = (value: number, nextValue: number, style: StyleProps) => {
const windowWidth = Dimensions.get('window').width;
if (value <= windowWidth && (nextValue >= windowWidth || value === nextValue)) {
return style;
}
return null;
};
export const mQ = (theme: Theme) => {
const brs = theme.breakpoints;
// Use Object.entries and reduce to create the 'breakpoints' object
return Object.entries(brs).reduce(
(acc, [key, value], i, entries) => {
// Get next breakpoint value, and default to the last breakpoint value in case of undefined
const nextValue = brs[entries[i + 1]?.[0]] ?? Object.values(brs).at(-1);
// Assign computed function to the respective breakpoint key in the accumulator object
acc[key] = (style: StyleProps) => applyStyleIfWithinRange(value, nextValue, style);
// Return
return acc;
},
{} as Record<keyof Theme['breakpoints'], (style: StyleProp<ViewStyle>) => object>,
);
};

View file

@ -1,3 +1,5 @@
import Values from 'values.js';
type RGB = [number, number, number]; type RGB = [number, number, number];
type HSL = [number, number, number]; type HSL = [number, number, number];
@ -84,3 +86,7 @@ export function getContrastColor(rgb: RGB): string {
const contrastRGB = HSLToRGB(hsl); const contrastRGB = HSLToRGB(hsl);
return contrastRGB.map((v) => v.toString(16).padStart(2, '0')).join(''); return contrastRGB.map((v) => v.toString(16).padStart(2, '0')).join('');
} }
export const isDark = (color: string) => {
return new Values(color).getBrightness() <= 55;
};

View file

@ -14,6 +14,5 @@
"publishConfig": { "publishConfig": {
"directory": "dist", "directory": "dist",
"linkDirectory": false "linkDirectory": false
}, }
"peerDependencies": {}
} }

View file

@ -16,7 +16,6 @@
"linkDirectory": false "linkDirectory": false
}, },
"dependencies": { "dependencies": {
"@types/vanilla-masker": "1.2.4",
"imask": "7.6.1" "imask": "7.6.1"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -1,5 +0,0 @@
import Values from 'values.js';
export const isDark = (color: string) => {
return new Values(color).getBrightness() <= 55;
};

View file

@ -1,3 +0,0 @@
import { either, isEmpty, isNil } from 'ramda';
export const isNilOrEmpty = either(isNil, isEmpty);

View file

@ -0,0 +1,3 @@
export function notNil<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}

7271
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { DeleteIcon, LogOutIcon } from 'lucide-react-native'; import { DeleteIcon, LogOutIcon } from 'lucide-react-native';
import { Text as RnText } from 'react-native';
import { Button } from '@procyon/components/Button'; import { Button } from '@procyon/components/Button';
import { T } from '@procyon/localization/tolgee'; import { T } from '@procyon/localization/tolgee';
@ -87,13 +86,10 @@ export const LabelAsIcon: Story = {
}; };
export const LabelWithIcon: Story = { export const LabelWithIcon: Story = {
args: {}, args: {
render: (args) => ( LeftIcon: (props) => <LogOutIcon />,
<Button {...args}> },
<LogOutIcon /> render: (args) => <Button {...args}>Logout</Button>,
<RnText>Logout</RnText>
</Button>
),
}; };
export const ExtraLargeSize: Story = { export const ExtraLargeSize: Story = {

View file

@ -7,7 +7,7 @@ import { Grid } from '@procyon/components/Grid';
import { Puzzle } from '@procyon/components/Puzzle'; import { Puzzle } from '@procyon/components/Puzzle';
import { RadioItem } from '@procyon/components/RadioItem'; import { RadioItem } from '@procyon/components/RadioItem';
import { Stack } from '@procyon/components/Stack'; import { Stack } from '@procyon/components/Stack';
import { Field, FieldWrapper } from '@procyon/forms/Field'; import { Field } from '@procyon/forms/Field';
import { Form } from '@procyon/forms/Form'; import { Form } from '@procyon/forms/Form';
import { useForm } from '@procyon/forms/useForm'; import { useForm } from '@procyon/forms/useForm';
@ -76,9 +76,7 @@ export const ExampleForm = () => {
<Field component="DATE" name="datePicker" label="DatePicker" /> <Field component="DATE" name="datePicker" label="DatePicker" />
<Field component="TIME" name="timePicker" label="TimePicker" /> <Field component="TIME" name="timePicker" label="TimePicker" />
</Grid> </Grid>
<FieldWrapper>
<Puzzle onVerified={() => null} /> <Puzzle onVerified={() => null} />
</FieldWrapper>
<Grid cols={{ sm: 2 }} gap={10}> <Grid cols={{ sm: 2 }} gap={10}>
<View /> <View />
<Button fullWidth onClick={formMethods.handleSubmit(onSubmit)}> <Button fullWidth onClick={formMethods.handleSubmit(onSubmit)}>