diff --git a/components/button/src/Button.tsx b/components/button/src/Button.tsx index 5e67f342..c2a0d2ba 100644 --- a/components/button/src/Button.tsx +++ b/components/button/src/Button.tsx @@ -1,3 +1,20 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { ThemedIcon } from "@cyclone-ui/themeable-icon"; import type { ColorTokens, FontSizeTokens } from "@tamagui/core"; import { View } from "@tamagui/core"; @@ -351,7 +368,7 @@ const ButtonFrame = styled(View, { } as const, defaultVariants: { - unstyled: process.env.TAMAGUI_HEADLESS === "1" ? true : false, + unstyled: process.env.TAMAGUI_HEADLESS === "1", disabled: false, outlined: false, circular: false @@ -432,7 +449,7 @@ const ButtonText = styled(SizableText, { } as const, defaultVariants: { - unstyled: process.env.TAMAGUI_HEADLESS === "1" ? true : false, + unstyled: process.env.TAMAGUI_HEADLESS === "1", disabled: false } }); @@ -452,15 +469,14 @@ const ButtonIcon = ButtonIconFrame.styleable( @@ -600,7 +616,7 @@ const ButtonContainerImpl = ButtonFrame.styleable( { const { children, ...rest } = props; @@ -32,38 +33,58 @@ const CheckboxFieldGroup = Field.styleable((props, forwardedRef) => { ); }); -const InnerCheckboxFieldLabel = forwardRef( - (props, forwardedRef) => { - const { children, ...rest } = props; - +const CheckboxFieldLabel = Field.Label.styleable( + ({ children, ...props }, forwardedRef) => { const store = useFieldStore(); + const disabled = store.get.disabled(); return ( - - {children} - {store.get.disabled() && } - + + + {children} + {disabled && } + + ); } ); -export const CheckboxFieldLabel = Field.Label.styled((props, forwardedRef) => { - const { children, ...rest } = props; +const CheckboxFieldControl = Checkbox.styleable( + ({ children, ...props }, forwardedRef) => { + const { focus, change, blur } = useFieldActions(); + const handleCheckedChange = useCallback( + async (checked: boolean) => { + await change(checked); + await blur(); + }, + [blur, change] + ); - return ( - - {children} - - ); -}); + const store = useFieldStore(); + const disabled = store.get.disabled(); + const focused = store.get.focused(); + const value = store.get.value(); + const initialValue = store.get.initialValue(); + + return ( + + {children} + + ); + } +); export const CheckboxField = withStaticProperties(CheckboxFieldGroup, { Label: CheckboxFieldLabel, - Control: Checkbox, + Control: CheckboxFieldControl, Details: Field.Details }); diff --git a/components/checkbox/package.json b/components/checkbox/package.json index c9f202b3..155e637a 100644 --- a/components/checkbox/package.json +++ b/components/checkbox/package.json @@ -27,24 +27,13 @@ "react-native": "0.74.1" }, "dependencies": { - "@tamagui/animate-presence": "^1.110.5", "@tamagui/checkbox": "^1.110.5", - "@tamagui/compose-refs": "^1.110.5", + "@tamagui/checkbox-headless": "^1.110.5", "@tamagui/constants": "^1.110.5", "@tamagui/core": "^1.110.5", - "@tamagui/focusable": "^1.110.5", - "@tamagui/font-size": "^1.110.5", - "@tamagui/get-button-sized": "^1.110.5", - "@tamagui/get-font-sized": "^1.110.5", - "@tamagui/get-token": "^1.110.5", "@tamagui/group": "^1.110.5", - "@tamagui/helpers": "^1.110.5", - "@tamagui/helpers-tamagui": "^1.110.5", - "@tamagui/label": "^1.110.5", "@tamagui/lucide-icons": "^1.110.5", "@tamagui/stacks": "^1.110.5", - "@tamagui/text": "^1.110.5", - "@tamagui/web": "^1.110.5", "react-native-svg": "^15.2.0" }, "devDependencies": { diff --git a/components/checkbox/src/Checkbox.stories.tsx b/components/checkbox/src/Checkbox.stories.tsx index 3e8620b9..cb29745a 100644 --- a/components/checkbox/src/Checkbox.stories.tsx +++ b/components/checkbox/src/Checkbox.stories.tsx @@ -26,8 +26,8 @@ const meta: Meta = { component: Checkbox, tags: ["autodocs"], render: (props: any) => ( -
- + + diff --git a/components/checkbox/src/Checkbox.tsx b/components/checkbox/src/Checkbox.tsx index 1c110e40..0da45536 100644 --- a/components/checkbox/src/Checkbox.tsx +++ b/components/checkbox/src/Checkbox.tsx @@ -1,34 +1,25 @@ -import { - FieldStatus, - useFieldActions, - useFieldStore -} from "@cyclone-ui/form-state"; -import { ThemedIcon } from "@cyclone-ui/themeable-icon"; +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { Checkbox as TamaguiCheckbox } from "@tamagui/checkbox"; import { isWeb } from "@tamagui/constants"; -import type { ColorTokens, FontSizeTokens } from "@tamagui/core"; -import { - createStyledContext, - styled, - View, - withStaticProperties -} from "@tamagui/core"; +import { styled } from "@tamagui/core"; import { XGroup } from "@tamagui/group"; import { Check } from "@tamagui/lucide-icons"; -import { XStack } from "@tamagui/stacks"; -import { useCallback } from "react"; - -const defaultContextValues = { - size: "$true", - color: undefined, - hideIcons: true -} as const; - -export const CheckboxContext = createStyledContext<{ - size: FontSizeTokens; - color?: ColorTokens | string; - hideIcons: boolean; -}>(defaultContextValues); export const defaultCheckboxGroupStyles = { size: "$true", @@ -71,23 +62,21 @@ export const CHECKBOX_NAME = "Checkbox"; const CheckboxGroupFrame = styled(XGroup, { name: CHECKBOX_NAME, + justifyContent: "space-between", - context: CheckboxContext, - animation: "$slow", + animation: "slow", + alignContent: "center", + verticalAlign: "center", variants: { unstyled: { false: defaultCheckboxGroupStyles }, - scaleIcon: { - ":number": {} as any - }, - - applyFocusStyle: { + focused: { ":boolean": (val, { props }) => { if (val) { - return props.focusStyle || defaultCheckboxGroupStyles.focusStyle; + return props.focusStyle ?? defaultCheckboxGroupStyles.focusStyle; } return {}; @@ -102,10 +91,6 @@ const CheckboxGroupFrame = styled(XGroup, { } }, - required: { - true: {} - }, - disabled: { true: { color: "$disabled", @@ -140,29 +125,31 @@ const CheckboxGroupFrame = styled(XGroup, { } as const, defaultVariants: { - unstyled: process.env.TAMAGUI_HEADLESS === "1" ? true : false, - required: false, + unstyled: process.env.TAMAGUI_HEADLESS === "1", + focused: false, disabled: false } }); const BaseCheckbox = styled(TamaguiCheckbox, { name: CHECKBOX_NAME, - unstyled: true, - context: CheckboxContext, verticalAlign: "center", height: "$1.5", width: "$1.5", padding: "$0.5", - // internalAutofillSelected: { - // backgroundColor: "transparent !important", - // color: "inherit !important" - // }, - variants: { + focused: { + true: { + // borderColor: "$accent10" + }, + false: { + // borderColor: "$borderColor" + } + }, + disabled: { true: { cursor: "not-allowed", @@ -176,85 +163,39 @@ const BaseCheckbox = styled(TamaguiCheckbox, { } as const, defaultVariants: { - disabled: false + disabled: false, + focused: false } }); -const BaseCheckboxImpl = BaseCheckbox.styleable((props, forwardedRef) => { - const { size } = CheckboxContext.useStyledContext(); - const { children, ...rest } = props; - - const store = useFieldStore(); - - const { focus, blur, change } = useFieldActions(); - const handleCheckedChange = useCallback( - async (checked: boolean) => { - await change(checked); - await blur(); - }, - [blur, change] - ); - - return ( - - - - - - - - ); -}); - -const CheckboxGroupImpl = BaseCheckboxImpl.styleable((props, forwardedRef) => { - const { children, ...rest } = props; - - const store = useFieldStore(); - const disabled = store.get.disabled(); - - return ( - - - { + return ( + + - {children} - + id={name} + {...props} + focused={focused} + disabled={disabled}> + + + + - - ); -}); - -export const Checkbox = withStaticProperties(CheckboxGroupImpl, { - Icon: ThemedIcon -}); + ); + } +); diff --git a/components/date-picker/src/DatePicker.stories.tsx b/components/date-picker/src/DatePicker.stories.tsx index c18f8b8f..56245358 100644 --- a/components/date-picker/src/DatePicker.stories.tsx +++ b/components/date-picker/src/DatePicker.stories.tsx @@ -24,7 +24,7 @@ import { useCallback } from "react"; import { DatePicker } from "./DatePicker"; const meta: Meta = { - title: "Form/DatePicker", + title: "Base/DatePicker", component: DatePicker, tags: ["autodocs"], render: (props: any) => { diff --git a/components/date-picker/src/DatePicker.tsx b/components/date-picker/src/DatePicker.tsx index 159f7dc7..be087054 100644 --- a/components/date-picker/src/DatePicker.tsx +++ b/components/date-picker/src/DatePicker.tsx @@ -1,9 +1,25 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { Button } from "@cyclone-ui/button"; import { ColorRole } from "@cyclone-ui/colors"; import { useFieldActions, useFieldStore } from "@cyclone-ui/form-state"; import { Input } from "@cyclone-ui/input"; import { LabelText } from "@cyclone-ui/label-text"; -import { ThemedIcon } from "@cyclone-ui/themeable-icon"; import type { DatePickerProviderProps, DPDay, @@ -18,8 +34,7 @@ import { AnimatePresence } from "@tamagui/animate-presence"; import { isWeb } from "@tamagui/constants"; import type { ColorTokens, FontSizeTokens } from "@tamagui/core"; import { createStyledContext, styled, useThemeName, View } from "@tamagui/core"; -import { withStaticProperties } from "@tamagui/helpers"; -import { Calendar, ChevronLeft, ChevronRight, X } from "@tamagui/lucide-icons"; +import { ChevronLeft, ChevronRight } from "@tamagui/lucide-icons"; import { Popover } from "@tamagui/popover"; import { XStack, YStack } from "@tamagui/stacks"; import { SizableText } from "@tamagui/text"; @@ -92,7 +107,7 @@ export const DEFAULT_DATE_FORMAT = "MM/DD/YYYY"; /** Rehookify internally return `onClick` and that's incompatible with native */ const swapOnClick = (d: D) => { - //@ts-ignore + // @ts-ignore d.onPress = d.onClick; return d; }; @@ -124,7 +139,7 @@ export function useDateAnimation({ setCurrentYearsSum(sumYears()); } } - }, [years, sumYears, currentYearsSum]); + }, [years, sumYears, listenTo, currentYearsSum]); useEffect(() => { if (listenTo === "month") { @@ -132,7 +147,7 @@ export function useDateAnimation({ setCurrentMonth(calendar.month); } } - }, [calendarListenTo, currentMonth]); + }, [listenTo, currentMonth]); useEffect(() => { if (listenTo === "year") { @@ -140,7 +155,7 @@ export function useDateAnimation({ setCurrentYear(calendar?.year); } } - }, [calendarListenTo, currentYear]); + }, [listenTo, currentYear]); const prevNextAnimation = useCallback(() => { if (listenTo === "years") { @@ -156,8 +171,10 @@ export function useDateAnimation({ if (currentMonth === null) { return { enterStyle: { opacity: 0 } }; } - const newDate = new Date(`${calendarListenTo} 1, ${calendar?.year}`); - const currentDate = new Date(`${currentMonth} 1, ${calendar?.year}`); + + const isPreviousDate = + new Date(`${calendarListenTo} 1, ${calendar?.year}`) < + new Date(`${currentMonth} 1, ${calendar?.year}`); if (currentMonth === "December" && calendar?.month === "January") { return { @@ -172,8 +189,8 @@ export function useDateAnimation({ }; } return { - enterStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 }, - exitStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 } + enterStyle: { opacity: 0, x: isPreviousDate ? -15 : 15 }, + exitStyle: { opacity: 0, x: isPreviousDate ? -15 : 15 } }; } @@ -181,12 +198,14 @@ export function useDateAnimation({ if (currentYear === null) { return { enterStyle: { opacity: 0 } }; } - const newDate = new Date(`${calendar?.month} 1, ${calendar?.year}`); - const currentDate = new Date(`${calendar?.month} 1, ${currentYear}`); + + const isPreviousDate = + new Date(`${calendar?.month} 1, ${calendar?.year}`) < + new Date(`${calendar?.month} 1, ${currentYear}`); return { - enterStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 }, - exitStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 } + enterStyle: { opacity: 0, x: isPreviousDate ? -15 : 15 }, + exitStyle: { opacity: 0, x: isPreviousDate ? -15 : 15 } }; } @@ -597,8 +616,6 @@ const DatePickerPopoverBody = () => { }; export const DatePickerControl = Input.styleable((props, ref) => { - const { size, ...rest } = props; - const store = useFieldStore(); const { focus, change } = useFieldActions(); @@ -610,9 +627,9 @@ export const DatePickerControl = Input.styleable((props, ref) => { - {value ? ( + {...props} + onChange={change}> + {/* value ? ( @@ -620,7 +637,7 @@ export const DatePickerControl = Input.styleable((props, ref) => { - )} + ) */} ); @@ -718,7 +735,7 @@ const DatePickerGroup = ({ children, ...props }: PropsWithChildren) => { ); }; -const DatePickerControlImpl = DatePickerControl.styleable( +export const DatePicker = DatePickerControl.styleable( ({ children, ...props }, forwardedRef) => { return ( @@ -751,6 +768,6 @@ const DatePickerControlImpl = DatePickerControl.styleable( } ); -export const DatePicker = withStaticProperties(DatePickerControlImpl, { - Icon: ThemedIcon -}); +// export const DatePicker = withStaticProperties(DatePickerControlImpl, { +// Icon: ThemedIcon +// }); diff --git a/components/field/package.json b/components/field/package.json index a7da215a..ef868089 100644 --- a/components/field/package.json +++ b/components/field/package.json @@ -37,6 +37,7 @@ "@tamagui/label": "^1.110.5", "@tamagui/lucide-icons": "^1.110.5", "@tamagui/stacks": "^1.110.5", + "@tamagui/tooltip": "^1.110.5", "@tamagui/web": "^1.110.5" }, "devDependencies": { diff --git a/components/field/src/Field.stories.tsx b/components/field/src/Field.stories.tsx index 203668a4..e87b1e38 100644 --- a/components/field/src/Field.stories.tsx +++ b/components/field/src/Field.stories.tsx @@ -15,20 +15,20 @@ -------------------------------------------------------------------*/ +import { BodyText } from "@cyclone-ui/body-text"; import { Form } from "@cyclone-ui/form"; -import { Input } from "@cyclone-ui/input"; import type { Meta, StoryObj } from "@storybook/react"; import { Field } from "./Field"; const meta: Meta = { - title: "Base/Field", + title: "Form/Field", component: Field, tags: ["autodocs"], render: (props: any) => ( Label Text - + The form field can be added here This is an example detailed message for an input diff --git a/components/field/src/Field.tsx b/components/field/src/Field.tsx index f2a2b50b..c2e00ef4 100644 --- a/components/field/src/Field.tsx +++ b/components/field/src/Field.tsx @@ -16,17 +16,21 @@ -------------------------------------------------------------------*/ import { BodyText } from "@cyclone-ui/body-text"; +import { Button } from "@cyclone-ui/button"; import { ColorRole } from "@cyclone-ui/colors"; import { FieldProvider, FieldProviderOptions, - FieldThemeIcon, + useFieldActions, useFieldStore, - ValidationMessage, Validator } from "@cyclone-ui/form-state"; import { LabelText } from "@cyclone-ui/label-text"; +import { Spinner } from "@cyclone-ui/spinner"; +import { ThemeIcon } from "@cyclone-ui/themeable-icon"; +import { ValidationText } from "@cyclone-ui/validation-text"; import { isBoolean } from "@storm-stack/types/type-checks/is-boolean"; +import { ValidationDetails } from "@storm-stack/types/utility-types/validations"; import type { ColorTokens, FontSizeTokens, GetProps } from "@tamagui/core"; import { createStyledContext, @@ -38,8 +42,9 @@ import { import { Label as TamaguiLabel } from "@tamagui/label"; import { Asterisk } from "@tamagui/lucide-icons"; import { ThemeableStack, XStack, YStack } from "@tamagui/stacks"; +import { Tooltip } from "@tamagui/tooltip"; import { Theme } from "@tamagui/web"; -import { ForwardedRef, forwardRef, useMemo } from "react"; +import { ForwardedRef, useMemo } from "react"; export const FieldContext = createStyledContext<{ size: FontSizeTokens; @@ -161,7 +166,7 @@ const FieldGroupInnerImpl = FieldGroupFrame.styleable((props, forwardedRef) => { disabled={isBoolean(disabled) ? disabled : undefined}> {children} - + @@ -265,7 +270,7 @@ const FieldDetailsImpl = FieldDetails.styleable((props, forwardedRef) => { return ( {children} @@ -343,7 +348,7 @@ const LabelXStack = styled(XStack, { } }); -export const FieldLabelText = StyledLabelText.styled<{ +export const FieldLabelText = StyledLabelText.styleable<{ required?: boolean; disabled?: boolean; focused?: boolean; @@ -390,19 +395,19 @@ export const FieldLabelText = StyledLabelText.styled<{ export type FieldLabelTextProps = GetProps; -export const FieldLabel = StyledLabelText.styled((props, forwardedRef) => { +export const FieldLabel = StyledLabelText.styleable((props, forwardedRef) => { const { children, ...rest } = props; const store = useFieldStore(); return ( } - pb="$0.5" + paddingBottom="$0.5" htmlFor={store.get.name()} {...rest} - disabled={!!store.get.disabled()} - required={!!store.get.required()} - focused={!!store.get.focused()}> + disabled={Boolean(store.get.disabled())} + required={Boolean(store.get.required())} + focused={Boolean(store.get.focused())}> {children} ); @@ -410,21 +415,126 @@ export const FieldLabel = StyledLabelText.styled((props, forwardedRef) => { export type FieldLabelProps = GetProps; -export const FieldFieldThemeIcon = forwardRef< - typeof FieldThemeIcon, - GetProps ->(props => { +const InnerFieldIcon = Button.styleable( + ({ children, color, ...rest }, forwardedRef) => { + const store = useFieldStore(); + + const disabled = store.get.disabled(); + const theme = store.get.theme(); + + return ( + + ); + } +); + +export const FieldIcon = InnerFieldIcon.styleable( + ({ children, ...rest }, forwardedRef) => { + const store = useFieldStore(); + if (store.get.disabled()) { + return null; + } + + return ( + + {children} + + ); + } +); + +const InnerFieldThemeIcon = InnerFieldIcon.styleable<{ + messages?: ValidationDetails[]; +}>(({ children, theme, messages, disabled, ...rest }, forwardedRef) => { + if ((!messages || messages.length === 0) && !disabled) { + return ( + + {children} + + ); + } + + return ( + + + + + + + + + {children} + + + + ); +}); + +const FieldThemeIcon = InnerFieldThemeIcon.styleable((props, forwardedRef) => { const store = useFieldStore(); + const { focus } = useFieldActions(); + const disabled = store.get.disabled(); + const validating = store.get.validating(); const theme = store.get.theme(); + const messages = store.get.messages(); + + if (validating) { + return ; + } else if ( + !theme?.toLowerCase().includes(ColorRole.ERROR) && + !theme?.toLowerCase().includes(ColorRole.WARNING) && + !theme?.toLowerCase().includes(ColorRole.INFO) && + !theme?.toLowerCase().includes(ColorRole.HELP) && + !theme?.toLowerCase().includes(ColorRole.SUCCESS) && + !disabled + ) { + return null; + } return ( - + + + ); }); export const Field = withStaticProperties(FieldGroup, { Label: FieldLabel, Details: FieldDetailsImpl, - StatusIcon: FieldThemeIcon + Icon: FieldIcon, + ThemeIcon: FieldThemeIcon }); diff --git a/components/form/src/Form.stories.tsx b/components/form/src/Form.stories.tsx index 32a2b0c3..7b5d64fd 100644 --- a/components/form/src/Form.stories.tsx +++ b/components/form/src/Form.stories.tsx @@ -19,7 +19,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { Form } from "./Form"; const meta: Meta = { - title: "Base/Form", + title: "Form/Form", component: Form, tags: ["autodocs"], render: (args: any) => diff --git a/components/input-field/src/InputField.tsx b/components/input-field/src/InputField.tsx index dfb670e1..3eb05673 100644 --- a/components/input-field/src/InputField.tsx +++ b/components/input-field/src/InputField.tsx @@ -16,7 +16,7 @@ -------------------------------------------------------------------*/ import { Field } from "@cyclone-ui/field"; -import { useFieldActions } from "@cyclone-ui/form-state"; +import { useFieldActions, useFieldStore } from "@cyclone-ui/form-state"; import { Input } from "@cyclone-ui/input"; import { withStaticProperties } from "@tamagui/web"; @@ -30,18 +30,33 @@ const InputFieldGroup = Field.styleable((props, forwardedRef) => { ); }); -const InputFieldControl = Input.styleable((props, forwardedRef) => { - const { children, ...rest } = props; +const InputFieldControl = Input.Value.styleable((props, forwardedRef) => { const { focus, blur, change } = useFieldActions(); + const store = useFieldStore(); + + const name = store.get.name(); + const disabled = store.get.disabled(); + const focused = store.get.focused(); + const validating = store.get.validating(); + const formattedValue = store.get.formattedValue(); + const initialValue = store.get.initialValue(); + return ( - - {children} + + {!disabled && } + + + + {(disabled || validating) && } ); }); @@ -49,5 +64,6 @@ const InputFieldControl = Input.styleable((props, forwardedRef) => { export const InputField = withStaticProperties(InputFieldGroup, { Label: Field.Label, Control: InputFieldControl, - Details: Field.Details + Details: Field.Details, + Icon: Field.Icon }); diff --git a/components/input/src/Input.stories.tsx b/components/input/src/Input.stories.tsx index e16333c0..65ed3963 100644 --- a/components/input/src/Input.stories.tsx +++ b/components/input/src/Input.stories.tsx @@ -24,11 +24,13 @@ const meta: Meta = { title: "Base/Input", component: Input, tags: ["autodocs"], - render: (props: any) => ( - + render: ({ defaultValue, ...props }: any) => ( + Label Text - + + + This is an example detailed message for an input diff --git a/components/input/src/Input.tsx b/components/input/src/Input.tsx index 6c6e72c3..808a1596 100644 --- a/components/input/src/Input.tsx +++ b/components/input/src/Input.tsx @@ -1,8 +1,20 @@ -import { - FieldIcon, - FieldThemeIcon, - useFieldStore -} from "@cyclone-ui/form-state"; +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { isWeb } from "@tamagui/constants"; import type { ColorTokens, FontSizeTokens } from "@tamagui/core"; import { @@ -11,19 +23,28 @@ import { withStaticProperties } from "@tamagui/core"; import { XGroup } from "@tamagui/group"; -import { Input as TamaguiInput, XStack } from "tamagui"; +import { Input as TamaguiInput } from "tamagui"; const defaultContextValues = { size: "$true", color: undefined, - hideIcons: false + hideIcons: false, + disabled: false, + focused: false, + required: false } as const; -export const InputContext = createStyledContext<{ +export type InputContextProps = { + name?: string; size: FontSizeTokens; color?: ColorTokens | string; hideIcons: boolean; -}>(defaultContextValues); + disabled: boolean; + focused: boolean; +}; + +export const InputContext = + createStyledContext(defaultContextValues); export const defaultInputGroupStyles = { size: "$1", @@ -71,6 +92,7 @@ const InputGroupFrame = styled(XGroup, { justifyContent: "space-between", animation: "slow", height: "$4.5", + alignItems: "center", variants: { unstyled: { @@ -81,10 +103,10 @@ const InputGroupFrame = styled(XGroup, { ":number": {} as any }, - applyFocusStyle: { + focused: { ":boolean": (val, { props }) => { if (val) { - return props.focusStyle || defaultInputGroupStyles.focusStyle; + return props.focusStyle ?? defaultInputGroupStyles.focusStyle; } return {}; @@ -99,10 +121,6 @@ const InputGroupFrame = styled(XGroup, { } }, - required: { - true: {} - }, - disabled: { true: { color: "$disabled", @@ -134,36 +152,11 @@ const InputGroupFrame = styled(XGroup, { } as const, defaultVariants: { - unstyled: process.env.TAMAGUI_HEADLESS === "1" ? true : false, - required: false, + unstyled: process.env.TAMAGUI_HEADLESS === "1", disabled: false } }); -// export const inputSizeVariant: SizeVariantSpreadFunction = ( -// val = "$true", -// extras -// ) => { -// const radiusToken = -// extras.tokens.radius[val] ?? extras.tokens.radius["$true"]; -// const paddingHorizontal = getSpace(val, { -// shift: -1, -// bounds: [2] -// }); -// const fontStyle = getFontSized(val as any, extras); -// // lineHeight messes up input on native -// if (!isWeb && fontStyle) { -// delete fontStyle["lineHeight"]; -// } - -// return { -// ...fontStyle, -// height: val, -// borderRadius: extras.props.circular ? 100_000 : radiusToken, -// paddingHorizontal -// }; -// }; - const BaseInput = styled(TamaguiInput, { name: INPUT_NAME, context: InputContext, @@ -197,47 +190,34 @@ const BaseInput = styled(TamaguiInput, { } }); -const BaseInputImpl = BaseInput.styleable((props, forwardedRef) => { - const store = useFieldStore(); - const disabled = store.get.disabled(); - const formattedValue = store.get.formattedValue(); +const InputValue = BaseInput.styleable((props, forwardedRef) => { + const { disabled, name } = InputContext.useStyledContext(); return ( ); }); -const InputGroupImpl = BaseInputImpl.styleable((props, forwardedRef) => { - const { children, ...rest } = props; - - const store = useFieldStore(); - const disabled = store.get.disabled(); - const validating = store.get.validating(); - - return ( - - - {!disabled && } - - - {children} - {(disabled || validating) && } - - - ); -}); +const InputGroupImpl = InputGroupFrame.styleable>( + (props, forwardedRef) => { + const { children } = props; + + return ( + + + {children} + + + ); + } +); export const Input = withStaticProperties(InputGroupImpl, { - Icon: FieldIcon + Value: InputValue }); diff --git a/components/label-text/src/LabelText.stories.tsx b/components/label-text/src/LabelText.stories.tsx index 9710137f..72c5d23b 100644 --- a/components/label-text/src/LabelText.stories.tsx +++ b/components/label-text/src/LabelText.stories.tsx @@ -1,3 +1,20 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import type { Meta, StoryObj } from "@storybook/react"; import { LabelText } from "./LabelText"; diff --git a/components/label-text/src/LabelText.tsx b/components/label-text/src/LabelText.tsx index 8b9fec82..ff625e21 100644 --- a/components/label-text/src/LabelText.tsx +++ b/components/label-text/src/LabelText.tsx @@ -1,3 +1,20 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { GetProps, styled } from "@tamagui/core"; import { Paragraph } from "@tamagui/text"; @@ -9,7 +26,8 @@ export const LabelText = styled(Paragraph, { color: "$color", fontFamily: "$label", fontSize: "$true", - fontWeight: "$true" + fontWeight: "$true", + lineHeight: "$true" }); export type LabelTextProps = GetProps; diff --git a/components/radio-group-field/package.json b/components/radio-group-field/package.json index 8c65990f..7b22cab9 100644 --- a/components/radio-group-field/package.json +++ b/components/radio-group-field/package.json @@ -27,16 +27,21 @@ "types" ], "peerDependencies": { + "jotai": ">=2.8.0", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.74.1" }, "dependencies": { + "@storm-stack/hooks": "latest", + "@storm-stack/types": "latest", "@tamagui/core": "^1.110.5", - "@tamagui/web": "^1.110.5", + "@tamagui/label": "^1.110.5", + "@tamagui/stacks": "^1.110.5", "react-native-svg": "^15.2.0" }, "devDependencies": { + "jotai": ">=2.8.0", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.74.1" diff --git a/components/radio-group-field/src/RadioGroupField.stories.tsx b/components/radio-group-field/src/RadioGroupField.stories.tsx index bae064be..79d602c6 100644 --- a/components/radio-group-field/src/RadioGroupField.stories.tsx +++ b/components/radio-group-field/src/RadioGroupField.stories.tsx @@ -23,21 +23,15 @@ const meta: Meta = { title: "Form/RadioGroupField", component: RadioGroupField, tags: ["autodocs"], - render: (props: any) => ( - - + render: ({ defaultValue, ...props }: any) => ( + + Label Text - - {options.map((option, i) => ( - - {option.name} - - ))} - + This is an example detailed message for an select field @@ -50,7 +44,7 @@ export default meta; type Story = StoryObj; -const options = [ +const items = [ { name: "Apple", value: "Apple", diff --git a/components/radio-group-field/src/RadioGroupField.tsx b/components/radio-group-field/src/RadioGroupField.tsx index b0d585ca..81398389 100644 --- a/components/radio-group-field/src/RadioGroupField.tsx +++ b/components/radio-group-field/src/RadioGroupField.tsx @@ -15,22 +15,191 @@ -------------------------------------------------------------------*/ +import { BodyText } from "@cyclone-ui/body-text"; import { Field } from "@cyclone-ui/field"; -import { RadioGroup } from "@cyclone-ui/radio-group"; -import { withStaticProperties } from "@tamagui/web"; +import { useFieldActions, useFieldStore } from "@cyclone-ui/form-state"; +import { + RADIO_GROUP_NAME, + RadioGroup, + RadioGroupContext +} from "@cyclone-ui/radio-group"; +import { SelectOption } from "@storm-stack/types/index"; +import { styled, withStaticProperties } from "@tamagui/core"; +import { Label } from "@tamagui/label"; +import { YStack } from "@tamagui/stacks"; +import { Atom, useAtomValue } from "jotai"; +import { PropsWithChildren, useCallback } from "react"; -const RadioGroupFieldGroup = Field.styleable((props, forwardedRef) => { - const { children, ...rest } = props; +const RadioGroupFieldGroup = Field.styleable( + ({ children, ...props }, forwardedRef) => { + return ( + + {children} + + ); + } +); + +const RadioGroupItemLabel = styled(Label, { + name: RADIO_GROUP_NAME, + + tag: "label", + cursor: "pointer", + animation: "100ms", + color: "$base10", + fontFamily: "$label", + fontSize: "$6", + fontWeight: "$4", + lineHeight: "$true", + wordWrap: "break-word", + verticalAlign: "middle", + + variants: { + selected: { + true: { + color: "$fg", + fontWeight: "$6" + } + }, + + disabled: { + true: { + color: "$disabled", + placeholderColor: "$disabled", + backgroundColor: "transparent", + userSelect: "none", + cursor: "not-allowed", + + hoverStyle: { + color: "$disabled" + }, + + focusStyle: { + color: "$disabled" + }, + + pressStyle: { + color: "$disabled" + } + }, + false: { + cursor: "pointer" + } + } + } as const, + + defaultVariants: { + selected: false, + disabled: false + } +}); + +const RadioGroupItemDescription = styled(BodyText, { + name: RADIO_GROUP_NAME, + context: RadioGroupContext, + + animation: "slow", + color: "$color", + fontSize: "$5", + + variants: { + disabled: { + true: { + color: "$disabled", + backgroundColor: "transparent", + userSelect: "none", + cursor: "not-allowed", + + hoverStyle: { + color: "$disabled" + }, + + focusStyle: { + color: "$disabled" + }, + + pressStyle: { + color: "$disabled" + } + }, + false: { + cursor: "pointer" + } + } + } as const, + + defaultVariants: { + disabled: false + } +}); + +const RadioGroupItem = ( + props: PropsWithChildren<{ itemAtom: Atom }> +) => { + const item = useAtomValue(props.itemAtom); + const { value, selected, disabled, name, description } = item; + + const { change } = useFieldActions(); + const handlePress = useCallback(() => { + if (!disabled) { + change(value); + } + }, [disabled, value, change]); + + return ( + + + + {name} + + {description && ( + + {description} + + )} + + + {disabled && } + + ); +}; + +const RadioGroupFieldControl = RadioGroup.styleable((props, forwardedRef) => { + const { focus, blur, change } = useFieldActions(); + + const store = useFieldStore(); + const name = store.get.name(); + const disabled = store.get.disabled(); + const formattedValue = store.get.formattedValue(); + const initialValue = store.get.initialValue(); + const itemsAtoms = store.get.itemsAtoms(); return ( - - {children} - + + {itemsAtoms.map((itemAtom, i) => { + return ; + })} + ); }); export const RadioGroupField = withStaticProperties(RadioGroupFieldGroup, { Label: Field.Label, - Control: RadioGroup, + Control: RadioGroupFieldControl, Details: Field.Details }); diff --git a/components/radio-group/src/RadioGroup.stories.tsx b/components/radio-group/src/RadioGroup.stories.tsx index c8bfc494..9c2fb9fe 100644 --- a/components/radio-group/src/RadioGroup.stories.tsx +++ b/components/radio-group/src/RadioGroup.stories.tsx @@ -24,16 +24,19 @@ const meta: Meta = { title: "Base/RadioGroup", component: RadioGroup, tags: ["autodocs"], - render: (props: any) => ( - + render: ({ defaultValue, ...props }: any) => ( + Label Text {options.map((option, i) => ( + description={option.description} + disabled={false} + selected={false}> {option.name} ))} diff --git a/components/radio-group/src/RadioGroup.tsx b/components/radio-group/src/RadioGroup.tsx index cf939274..2221e5ee 100644 --- a/components/radio-group/src/RadioGroup.tsx +++ b/components/radio-group/src/RadioGroup.tsx @@ -15,14 +15,6 @@ -------------------------------------------------------------------*/ -import { BodyText } from "@cyclone-ui/body-text"; -import { - FieldThemeIcon, - useFieldActions, - useFieldStore -} from "@cyclone-ui/form-state"; -import { ThemedIcon } from "@cyclone-ui/themeable-icon"; -import { useEvent } from "@storm-stack/hooks/use-event"; import { SelectOption } from "@storm-stack/types/utility-types/form"; import { isWeb } from "@tamagui/constants"; import type { ColorTokens, FontSizeTokens } from "@tamagui/core"; @@ -33,23 +25,27 @@ import { withStaticProperties } from "@tamagui/core"; import { getFontSized } from "@tamagui/get-font-sized"; -import { Label } from "@tamagui/label"; import { RadioGroup as TamaguiRadioGroup } from "@tamagui/radio-group"; import { XStack, YStack } from "@tamagui/stacks"; -import type { SizeVariantSpreadFunction } from "@tamagui/web"; -import { useLayoutEffect, useMemo } from "react"; - -const defaultContextValues = { - size: "$true", - color: undefined, - hideIcons: true -} as const; -export const RadioGroupContext = createStyledContext<{ +export type RadioGroupContextProps = { + name?: string; size: FontSizeTokens; color?: ColorTokens | string; hideIcons: boolean; -}>(defaultContextValues); + disabled: boolean; + focused: boolean; + required: boolean; +}; + +export const RadioGroupContext = createStyledContext({ + size: "$true", + color: undefined, + hideIcons: false, + disabled: false, + focused: false, + required: false +}); export const defaultRadioGroupStyles = { size: "$true", @@ -71,123 +67,15 @@ export const defaultRadioGroupStyles = { minWidth: 0 } as const; -export const radioGroupSizeVariant: SizeVariantSpreadFunction = ( - val = "$true", - extras -) => { - const fontStyle = getFontSized(val as any, extras); - // lineHeight messes up select on native - if (!isWeb && fontStyle) { - delete fontStyle["lineHeight"]; - } - - return { - ...fontStyle - }; -}; - export const RADIO_GROUP_NAME = "RadioGroup"; -const RadioGroupItemLabel = styled(Label, { - name: RADIO_GROUP_NAME, - context: RadioGroupContext, - - tag: "label", - cursor: "pointer", - animation: "100ms", - color: "$base10", - fontFamily: "$label", - fontSize: "$true", - fontWeight: "$3", - wordWrap: "break-word", - verticalAlign: "middle", - - variants: { - selected: { - true: { - color: "$fg", - fontWeight: "$5" - } - }, - - disabled: { - true: { - color: "$disabled", - placeholderColor: "$disabled", - backgroundColor: "transparent", - userSelect: "none", - cursor: "not-allowed", - - hoverStyle: { - color: "$disabled" - }, - - focusStyle: { - color: "$disabled" - }, - - pressStyle: { - color: "$disabled" - } - }, - false: { - cursor: "pointer" - } - } - } as const, - - defaultVariants: { - selected: false, - disabled: false - } -}); - -const RadioGroupItemDescription = styled(BodyText, { - name: RADIO_GROUP_NAME, - context: RadioGroupContext, - - animation: "slow", - color: "$color", - - variants: { - disabled: { - true: { - color: "$disabled", - backgroundColor: "transparent", - userSelect: "none", - cursor: "not-allowed", - - hoverStyle: { - color: "$disabled" - }, - - focusStyle: { - color: "$disabled" - }, - - pressStyle: { - color: "$disabled" - } - }, - false: { - cursor: "pointer" - } - } - } as const, - - defaultVariants: { - disabled: false - } -}); - -const BaseRadioGroupItem = styled(TamaguiRadioGroup.Item, { +const RadioGroupItem = styled(TamaguiRadioGroup.Item, { name: RADIO_GROUP_NAME, context: RadioGroupContext, radiused: true, hoverTheme: false, pressTheme: true, - focusable: true, animation: "slow", size: "$true", @@ -198,14 +86,6 @@ const BaseRadioGroupItem = styled(TamaguiRadioGroup.Item, { backgroundColor: "transparent" }, - focusStyle: { - // outlineColor: "$accent10", - // outlineWidth: 2, - // outlineOffset: "$1.25", - // outlineStyle: "solid", - borderColor: "$borderColorFocus" - }, - variants: { unstyled: { false: { @@ -222,6 +102,16 @@ const BaseRadioGroupItem = styled(TamaguiRadioGroup.Item, { } }, + // selected: { + // true: { + // outlineColor: "$accent10", + // outlineWidth: 2, + // outlineOffset: "$1.25", + // outlineStyle: "solid", + // borderColor: "$borderColorFocus" + // } + // }, + disabled: { true: { color: "$disabled", @@ -252,11 +142,12 @@ const BaseRadioGroupItem = styled(TamaguiRadioGroup.Item, { defaultVariants: { unstyled: process.env.TAMAGUI_HEADLESS === "1", + // selected: false, disabled: false } }); -const BaseRadioGroupItemIndicator = styled(TamaguiRadioGroup.Indicator, { +const RadioGroupItemIndicator = styled(TamaguiRadioGroup.Indicator, { name: RADIO_GROUP_NAME, context: RadioGroupContext, @@ -308,7 +199,7 @@ const BaseRadioGroupItemIndicator = styled(TamaguiRadioGroup.Indicator, { } }); -const RadioGroupItemContainer = styled(XStack, { +const RadioGroupItemContainerFrame = styled(XStack, { name: RADIO_GROUP_NAME, context: RadioGroupContext, @@ -379,88 +270,52 @@ const RadioGroupItemContainer = styled(XStack, { } }); -const RadioGroupItem = RadioGroupItemContainer.styleable< - Partial & Pick ->(({ children, value, description, ...rest }, forwardedRef) => { - const { size } = RadioGroupContext.useStyledContext(); - - const store = useFieldStore(); - const setItems = store.set.items(); - - const fieldValue = store.get.value(); - const fieldDisabled = store.get.disabled(); - - const selected = useMemo(() => fieldValue === value, [fieldValue, value]); - const disabled = useMemo( - () => Boolean(fieldDisabled || rest.disabled), - [fieldDisabled, rest.disabled] - ); - - useLayoutEffect(() => { - setItems(prev => [ - ...prev.filter(item => item.value !== value), - { - name: children, - value, - disabled, - selected, - description - } as SelectOption - ]); - }, [disabled, value, selected]); - - const { change } = useFieldActions(); - const handlePress = useEvent(() => change(value)); - - return ( - - e.stopPropagation()}> - - {selected && } - - - - - {children} - - {description && ( - +>( + ( + { children, value, disabled, selected, onPress, ...props }, + forwardedRef + ) => { + const { size } = RadioGroupContext.useStyledContext(); + + return ( + + e.stopPropagation()}> + - {description} - - )} - - - {disabled && } - - ); -}); + selected={selected} + $group-hover={{ + borderColor: disabled + ? "$disabled" + : selected + ? "$colorFocus" + : "$accent10" + }} + $group-focus={{ + outlineColor: "$accent10", + outlineWidth: 2, + outlineOffset: "$1.25", + outlineStyle: "solid", + borderColor: "$borderColorFocus" + }}> + {selected && } + + + + {children} + + ); + } +); const RadioGroupFrame = styled(TamaguiRadioGroup, { name: RADIO_GROUP_NAME, @@ -476,7 +331,17 @@ const RadioGroupFrame = styled(TamaguiRadioGroup, { variants: { size: { - "...size": radioGroupSizeVariant + "...size": (val = "$true", extras) => { + const fontStyle = getFontSized(val as any, extras as any); + // lineHeight messes up select on native + if (!isWeb && fontStyle) { + delete fontStyle["lineHeight"]; + } + + return { + ...fontStyle + }; + } }, disabled: { @@ -496,26 +361,23 @@ const RadioGroupFrame = styled(TamaguiRadioGroup, { } }); -const RadioGroupImpl = RadioGroupFrame.styleable( - ({ children, ...rest }, forwardedRef) => { +const RadioGroupImpl = RadioGroupFrame.styleable<{ + defaultValue?: string | null; +}>( + ( + { children, name, required, disabled, value, defaultValue, ...props }, + forwardedRef + ) => { const { size } = RadioGroupContext.useStyledContext(); - const store = useFieldStore(); - const disabled = store.get.disabled(); - const value = store.get.value(); - const required = store.get.required(); - - const { change } = useFieldActions(); - return ( =2.8.0", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.74.1" }, "dependencies": { "@tamagui/core": "^1.110.5", + "@tamagui/create-context": "^1.110.5", "@tamagui/web": "^1.110.5", "react-native-svg": "^15.2.0" }, "devDependencies": { + "jotai": ">=2.8.0", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.74.1" diff --git a/components/select-field/src/SelectField.tsx b/components/select-field/src/SelectField.tsx index c8d20bd7..0d03d3e8 100644 --- a/components/select-field/src/SelectField.tsx +++ b/components/select-field/src/SelectField.tsx @@ -16,8 +16,16 @@ -------------------------------------------------------------------*/ import { Field } from "@cyclone-ui/field"; +import { + useFieldActions, + useFieldApi, + useFieldStore +} from "@cyclone-ui/form-state"; import { Select } from "@cyclone-ui/select"; -import { withStaticProperties } from "@tamagui/web"; +import { GetProps, withStaticProperties } from "@tamagui/web"; +import { Getter } from "jotai"; +import { useAtomCallback } from "jotai/utils"; +import { useCallback, useMemo } from "react"; const SelectFieldGroup = Field.styleable((props, forwardedRef) => { const { children, ...rest } = props; @@ -29,8 +37,99 @@ const SelectFieldGroup = Field.styleable((props, forwardedRef) => { ); }); +const SelectFieldItem = Select.Items.Item.styleable( + ({ children, value, ...props }, forwardedRef) => { + const store = useFieldStore(); + const fieldValue = store.get.value(); + const fieldDisabled = store.get.disabled(); + + const selected = useMemo(() => fieldValue === value, [fieldValue, value]); + const disabled = useMemo( + () => Boolean(fieldDisabled || props.disabled), + [fieldDisabled, props.disabled] + ); + return ( + + {children} + + ); + } +); + +const SelectFieldControl = Select.styleable< + Pick, "placeholder"> +>(({ placeholder, ...props }, forwardedRef) => { + const { focus, blur, change, toggleFocused } = useFieldActions(); + const fieldApi = useFieldApi(); + + const store = useFieldStore(); + const name = store.get.name(); + const disabled = store.get.disabled(); + const focused = store.get.focused(); + const validating = store.get.validating(); + const value = store.get.value(); + const formattedValue = store.get.formattedValue(); + const initialValue = store.get.initialValue(); + + return ( + + ); +}); + export const SelectField = withStaticProperties(SelectFieldGroup, { Label: Field.Label, - Control: Select, - Details: Field.Details + Control: SelectFieldControl, + Details: Field.Details, + Icon: Field.Icon }); diff --git a/components/select/package.json b/components/select/package.json index 7221ef7e..e5f54d96 100644 --- a/components/select/package.json +++ b/components/select/package.json @@ -37,6 +37,7 @@ "@tamagui/compose-refs": "^1.110.5", "@tamagui/constants": "^1.110.5", "@tamagui/core": "^1.110.5", + "@tamagui/create-context": "^1.110.5", "@tamagui/focusable": "^1.110.5", "@tamagui/font-size": "^1.110.5", "@tamagui/get-button-sized": "^1.110.5", diff --git a/components/select/src/Select.stories.tsx b/components/select/src/Select.stories.tsx index d912efcd..33805c0e 100644 --- a/components/select/src/Select.stories.tsx +++ b/components/select/src/Select.stories.tsx @@ -24,16 +24,28 @@ const meta: Meta = { title: "Base/Select", component: Select, tags: ["autodocs"], - render: (props: any) => ( - + render: ({ defaultValue, ...props }: any) => ( + Label Text - + + + + + + + {options.map((option, i) => ( + + {option.name} + + ))} + This is an example detailed message for an select diff --git a/components/select/src/Select.tsx b/components/select/src/Select.tsx index 8bc81149..6a4f0dce 100644 --- a/components/select/src/Select.tsx +++ b/components/select/src/Select.tsx @@ -1,11 +1,22 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + +import { Button } from "@cyclone-ui/button"; import { ColorRole } from "@cyclone-ui/colors"; -import { - FieldIcon, - FieldThemeIcon, - useFieldActions, - useFieldStore -} from "@cyclone-ui/form-state"; -import { ThemedIcon } from "@cyclone-ui/themeable-icon"; import { SelectOption } from "@storm-stack/types/utility-types/form"; import { Adapt } from "@tamagui/adapt"; import { isWeb } from "@tamagui/constants"; @@ -14,31 +25,36 @@ import { createStyledContext, styled, Theme, + useThemeName, View, withStaticProperties } from "@tamagui/core"; -import { getFontSized } from "@tamagui/get-font-sized"; -import { getSpace } from "@tamagui/get-token"; import { XGroup } from "@tamagui/group"; import { LinearGradient } from "@tamagui/linear-gradient"; import { Check, ChevronDown, ChevronUp } from "@tamagui/lucide-icons"; import { Select as TamaguiSelect } from "@tamagui/select"; import { Sheet } from "@tamagui/sheet"; -import { XStack, YStack } from "@tamagui/stacks"; -import type { GetProps, SizeVariantSpreadFunction } from "@tamagui/web"; -import { forwardRef, useCallback, useLayoutEffect, useMemo } from "react"; +import { YStack } from "@tamagui/stacks"; const defaultContextValues = { size: "$true", color: undefined, - hideIcons: true + hideIcons: true, + disabled: false, + focused: false } as const; -export const SelectContext = createStyledContext<{ +export type SelectContextProps = { + name?: string; size: FontSizeTokens; color?: ColorTokens | string; - hideIcons: boolean; -}>(defaultContextValues); + hideIcons?: boolean; + disabled: boolean; + focused: boolean; +}; + +export const SelectContext = + createStyledContext(defaultContextValues); export const defaultSelectGroupStyles = { size: "$true", @@ -96,10 +112,6 @@ const SelectGroupFrame = styled(XGroup, { } }, - required: { - true: {} - }, - disabled: { true: { color: "$disabled", @@ -132,7 +144,7 @@ const SelectGroupFrame = styled(XGroup, { } }, - open: { + focused: { true: { outlineColor: "$accent10", outlineWidth: 2, @@ -144,38 +156,12 @@ const SelectGroupFrame = styled(XGroup, { } as const, defaultVariants: { - unstyled: process.env.TAMAGUI_HEADLESS === "1" ? true : false, - required: false, + unstyled: process.env.TAMAGUI_HEADLESS === "1", disabled: false, - open: false + focused: false } }); -export const selectSizeVariant: SizeVariantSpreadFunction = ( - val = "$true", - extras -) => { - const radiusToken = - extras.tokens.radius[val] ?? extras.tokens.radius["$true"]; - const paddingHorizontal = getSpace(val, { - shift: -2, - bounds: [2] - }); - - const fontStyle = getFontSized(val as any, extras); - // lineHeight messes up select on native - if (!isWeb && fontStyle) { - delete fontStyle["lineHeight"]; - } - - return { - ...fontStyle, - height: val, - borderRadius: extras.props.circular ? 100_000 : radiusToken, - paddingHorizontal - }; -}; - const SelectTrigger = styled(TamaguiSelect.Trigger, { name: SELECT_NAME, context: SelectContext, @@ -200,6 +186,9 @@ const SelectTrigger = styled(TamaguiSelect.Trigger, { outlineColor: "transparent", outlineStyle: "none", paddingHorizontal: "$2", + flexDirection: "row", + alignItems: "center", + width: "100%", variants: { scaleIcon: { @@ -241,69 +230,6 @@ const SelectTrigger = styled(TamaguiSelect.Trigger, { } }); -const SelectValueFrame = styled(TamaguiSelect.Value, { - name: SELECT_NAME, - context: SelectContext, - - fontFamily: "$body", - fontSize: "$true", - fontWeight: "$true", - color: "$fg", - backgroundColor: "transparent", - flexGrow: 1, - marginHorizontal: "$1.75", - - hoverStyle: { - backgroundColor: "transparent" - }, - - focusStyle: { - backgroundColor: "transparent" - }, - - variants: { - placeholding: { - true: { - color: "$placeholderColor" - } - }, - - disabled: { - true: { - cursor: "not-allowed", - color: "$disabled" - }, - false: { - cursor: "pointer" - } - } - } as const, - - defaultVariants: { - disabled: false, - placeholding: false - } -}); - -const SelectValue = SelectValueFrame.styleable<{ - placeholder?: string; -}>(({ children, ...props }, ref) => { - const store = useFieldStore(); - const disabled = store.get.disabled(); - - return ( - - {children} - - ); -}); - const SelectItemFrame = styled(TamaguiSelect.Item, { name: SELECT_NAME, context: SelectContext, @@ -384,60 +310,99 @@ const SelectItemTextFrame = styled(TamaguiSelect.ItemText, { } }); -export const SelectItem = forwardRef< - typeof TamaguiSelect.Item, - GetProps & Partial> ->((props, forwardedRef) => { - const { children, value, ...rest } = props; +const SelectItem = SelectItemFrame.styleable>( + ({ children, value, selected, disabled, ...props }, forwardedRef) => { + return ( + + + {children} + + + + + + + + ); + } +); + +const SelectValueFrame = styled(TamaguiSelect.Value, { + name: SELECT_NAME, + context: SelectContext, - const store = useFieldStore(); - const setItems = store.set.items(); + fontFamily: "$body", + fontSize: "$true", + fontWeight: "$true", + color: "$fg", + backgroundColor: "transparent", + flexGrow: 1, + marginHorizontal: "$1.75", - const fieldValue = store.get.value(); - const fieldDisabled = store.get.disabled(); + hoverStyle: { + backgroundColor: "transparent" + }, - const selected = useMemo(() => fieldValue === value, [fieldValue, value]); - const disabled = useMemo( - () => !!(fieldDisabled || props.disabled), - [fieldDisabled, props.disabled] - ); + focusStyle: { + backgroundColor: "transparent" + }, - useLayoutEffect(() => { - setItems(prev => [ - ...prev.filter(item => item.value !== value), - { name: children, value, disabled: disabled, selected } as SelectOption - ]); - }, [disabled, value, selected]); + variants: { + placeholding: { + true: { + color: "$placeholderColor" + } + }, + + disabled: { + true: { + cursor: "not-allowed", + color: "$disabled" + }, + false: { + cursor: "pointer" + } + } + } as const, + + defaultVariants: { + disabled: false, + placeholding: false + } +}); + +const SelectValue = SelectValueFrame.styleable<{ + placeholder?: string; +}>(({ children, placeholding, ...props }, forwardedRef) => { + const { disabled, name } = SelectContext.useStyledContext(); return ( - - - {children} - - - - - - - + size={0} + {...props} + disabled={disabled} + placeholding={placeholding && !disabled}> + {children} + ); }); const BaseSelect = styled(TamaguiSelect, { name: SELECT_NAME, - context: SelectContext, justifyContent: "center", @@ -460,66 +425,79 @@ const BaseSelect = styled(TamaguiSelect, { } }); -export type SelectExtraProps = { - onOpen?: () => any; - onClose?: () => any; - placeholder?: string; -}; +const SelectButtonIcon = styled(Button.Icon, { + name: SELECT_NAME, + context: SelectContext, -const BaseSelectImpl = BaseSelect.styleable((props, ref) => { - const { size } = SelectContext.useStyledContext(); - const { children, onOpen, onClose, placeholder, ...rest } = props; - - const store = useFieldStore(); - const disabled = store.get.disabled(); - const validating = store.get.validating(); - const value = store.get.value(); - const focused = store.get.focused(); - const required = store.get.required(); - - const { change, blur, focus } = useFieldActions(); - const handleOpenChange = useCallback( - (next: boolean) => { - if (next) { - focus(); - onOpen?.(); - } else { - blur(); - onClose?.(); + animation: "slow", + + variants: { + focused: { + true: { + rotate: "180deg" + }, + false: { + rotate: "0deg" } - }, - [focus, blur, onOpen, onClose] - ); + } + } as const, - return ( - - - - {!disabled && } - - - {(disabled || validating) && } - {!disabled && ( - - - - - - )} - - + defaultVariants: { + focused: false + } +}); + +const SelectButton = Button.styleable<{ rotateOnFocused?: boolean }>( + ({ children, rotateOnFocused = true, ...props }, forwardedRef) => { + const { color, focused, disabled } = SelectContext.useStyledContext(); + const theme = useThemeName(); + + return ( + + ); + } +); +const SelectGroup = BaseSelect.styleable>( + ({ name, disabled, focused, children, ...props }, forwardedRef) => { + return ( + + + {children} + + + ); + } +); + +const SelectItems = View.styleable(({ children, ...props }, forwardedRef) => { + return ( + ((props, ref) => { enterStyle={{ opacity: 0, scale: 0.9, y: -10 }} exitStyle={{ opacity: 0, scale: 0.95, y: 10 }} minWidth={200}> - - {!required && ( - - )} - {children} - + {children} ((props, ref) => { /> - + ); }); -const SelectIconChevron = styled(View, { - name: SELECT_NAME, - context: SelectContext, - - marginRight: 0, - animation: "slow", - - variants: { - open: { - true: { - rotate: "180deg" - }, - false: { - rotate: "0deg" - } - } - } as const, - - defaultVariants: { - open: false - } -}); - -const SelectGroupImpl = BaseSelectImpl.styleable( - (props, forwardedRef) => { - const { children, ...rest } = props; - - const store = useFieldStore(); - const focused = store.get.focused(); - const disabled = store.get.disabled(); - - return ( - - - {children} - - - ); - } -); - -export const Select = withStaticProperties(SelectGroupImpl, { - Icon: ThemedIcon, - Item: SelectItem +export const Select = withStaticProperties(SelectGroup, { + Trigger: withStaticProperties(SelectTrigger, { + Value: SelectValue, + Button: SelectButton + }), + Items: withStaticProperties(SelectItems, { + Item: SelectItem + }) }); diff --git a/components/validation-text/src/ValidationText.stories.tsx b/components/validation-text/src/ValidationText.stories.tsx index 32a2b0c3..55b983bf 100644 --- a/components/validation-text/src/ValidationText.stories.tsx +++ b/components/validation-text/src/ValidationText.stories.tsx @@ -15,36 +15,37 @@ -------------------------------------------------------------------*/ +import { MessageType } from "@storm-stack/types/utility-types/messages"; import type { Meta, StoryObj } from "@storybook/react"; -import { Form } from "./Form"; +import { ValidationText } from "./ValidationText"; -const meta: Meta = { - title: "Base/Form", - component: Form, +const meta: Meta = { + title: "Typography/ValidationText", + component: ValidationText, tags: ["autodocs"], - render: (args: any) => -} satisfies Meta; + render: (args: any) => ( + + ) +} satisfies Meta; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Base: Story = { args: {} }; -export const Small: Story = { - args: { - size: "small" - } -}; - -export const Large: Story = { - args: { - size: "large" - } -}; - export const Brand: Story = { args: { theme: "brand" diff --git a/components/validation-text/src/ValidationText.tsx b/components/validation-text/src/ValidationText.tsx index 0af9be01..61b2b3aa 100644 --- a/components/validation-text/src/ValidationText.tsx +++ b/components/validation-text/src/ValidationText.tsx @@ -23,7 +23,7 @@ import { styled } from "@tamagui/core"; import { Dot } from "@tamagui/lucide-icons"; import { XStack, YStack } from "@tamagui/stacks"; -const ValidationTextFrame = styled(BodyText, { +const ValidationBodyText = styled(BodyText, { animation: "slow", marginTop: "$0.5", @@ -75,7 +75,7 @@ const ValidationTextFrame = styled(BodyText, { } as const }); -export const ValidationText = ValidationTextFrame.styleable<{ +export const ValidationText = ValidationBodyText.styleable<{ messages?: ValidationDetails[]; theme?: string | null; disabled?: boolean; @@ -86,9 +86,9 @@ export const ValidationText = ValidationTextFrame.styleable<{ if ((messages.length === 1 && messages[0]?.message) || disabled) { const message = messages[0]?.message || "This field is disabled"; return ( - + {message} - + ); } else if (messages.length === 0) { return null; @@ -105,19 +105,19 @@ export const ValidationText = ValidationTextFrame.styleable<{ return ( - + {heading} - + {messages .filter(message => message.message) .map(message => ( - + - + {message.message} - + ))} diff --git a/eslint.config.mjs b/eslint.config.mjs index a07aa415..3ebcf8a6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,7 @@ export default getStormConfig({ "unicorn/no-useless-switch-case": 0, "no-undef": 0, "no-unused-vars": "warn", + "no-redeclare": 0, "unicorn/consistent-function-scoping": 0, "class-methods-use-this": 0, "operator-linebreak": 0, diff --git a/packages/colors/src/schemes/brand-dark.ts b/packages/colors/src/schemes/brand-dark.ts index 17879806..0d7ba343 100644 --- a/packages/colors/src/schemes/brand-dark.ts +++ b/packages/colors/src/schemes/brand-dark.ts @@ -1,130 +1,3 @@ -export const theme = { - "base": { - "base1": "hsl(228,7.94%,12.35%)", - "base2": "hsl(228,7.25%,13.53%)", - "base3": "hsl(230,7.32%,16.08%)", - "base4": "hsl(222.86,6.93%,19.8%)", - "base5": "hsl(225,6.35%,24.71%)", - "base6": "hsl(220,5.81%,30.39%)", - "base7": "hsl(218.18,5.82%,37.06%)", - "base8": "hsl(216.92,5.68%,44.9%)", - "base9": "hsl(214.29,5.88%,53.33%)", - "base10": "hsl(212.73,26.83%,83.92%)", - "base11": "hsl(212.73,26.83%,91.96%)", - "base12": "hsl(0,0%,100%)" - }, - "brand": { - "brand1": "hsl(180,32.63%,18.63%)", - "brand2": "hsl(176.17,41.59%,22.16%)", - "brand3": "hsl(175.08,46.56%,25.69%)", - "brand4": "hsl(173.77,51.68%,29.22%)", - "brand5": "hsl(174.07,54.49%,32.75%)", - "brand6": "hsl(173.27,57.84%,36.27%)", - "brand7": "hsl(173.06,59.61%,39.8%)", - "brand8": "hsl(172.55,61.99%,43.33%)", - "brand9": "hsl(172.85,63.18%,46.86%)", - "brand10": "hsl(172.46,66.01%,50.39%)", - "brand11": "hsl(174.74,57%,60.78%)", - "brand12": "hsl(180.94,43.84%,71.37%)" - }, - "alternate": { - "alternate1": "hsl(328.13,33.33%,18.82%)", - "alternate2": "hsl(329.39,43.36%,22.16%)", - "alternate3": "hsl(330.46,49.62%,25.69%)", - "alternate4": "hsl(331.11,54.36%,29.22%)", - "alternate5": "hsl(332.45,58.33%,32.94%)", - "alternate6": "hsl(332.63,61.29%,36.47%)", - "alternate7": "hsl(332.77,63.73%,40%)", - "alternate8": "hsl(332.65,66.52%,43.33%)", - "alternate9": "hsl(333.29,68.33%,47.06%)", - "alternate10": "hsl(333.33,71.43%,50.59%)", - "alternate11": "hsl(330.77,58.79%,60.98%)", - "alternate12": "hsl(320.38,36.05%,71.18%)" - }, - "help": { - "help1": "hsl(233.08,21.67%,23.53%)", - "help2": "hsl(233.33,24.32%,29.02%)", - "help3": "hsl(233.62,26.55%,34.71%)", - "help4": "hsl(233.68,27.8%,40.2%)", - "help5": "hsl(234.63,28.51%,46.08%)", - "help6": "hsl(234.55,31.17%,51.57%)", - "help7": "hsl(234.55,40.37%,57.25%)", - "help8": "hsl(234.49,51.58%,62.75%)", - "help9": "hsl(234.5,67.7%,68.43%)", - "help10": "hsl(234.45,89.47%,73.92%)", - "help11": "hsl(233.02,74.14%,77.25%)", - "help12": "hsl(228,55.56%,80.59%)" - }, - "success": { - "success1": "hsl(166.45,36.47%,16.67%)", - "success2": "hsl(164.68,48.45%,19.02%)", - "success3": "hsl(163.55,56.36%,21.57%)", - "success4": "hsl(162.08,62.6%,24.12%)", - "success5": "hsl(161.29,67.88%,26.86%)", - "success6": "hsl(160.56,72%,29.41%)", - "success7": "hsl(160.49,75.46%,31.96%)", - "success8": "hsl(160.58,79.43%,34.31%)", - "success9": "hsl(160.13,81.91%,36.86%)", - "success10": "hsl(160.12,84.08%,39.41%)", - "success11": "hsl(162.93,48.74%,53.33%)", - "success12": "hsl(169.52,38.18%,67.65%)" - }, - "info": { - "info1": "hsl(213.53,29.82%,22.35%)", - "info2": "hsl(213.06,35.25%,27.25%)", - "info3": "hsl(212.81,39.02%,32.16%)", - "info4": "hsl(212.31,41.05%,37.25%)", - "info5": "hsl(212.55,43.52%,42.35%)", - "info6": "hsl(212.22,44.63%,47.45%)", - "info7": "hsl(212.2,50.62%,52.35%)", - "info8": "hsl(212.17,63.3%,57.25%)", - "info9": "hsl(211.97,79.17%,62.35%)", - "info10": "hsl(211.98,100%,67.25%)", - "info11": "hsl(211.76,85.61%,72.75%)", - "info12": "hsl(212.57,63.64%,78.43%)" - }, - "warning": { - "warning1": "hsl(45,17.24%,22.75%)", - "warning2": "hsl(44.52,21.68%,28.04%)", - "warning3": "hsl(45,25.88%,33.33%)", - "warning4": "hsl(45,28.57%,38.43%)", - "warning5": "hsl(45.22,30.94%,43.73%)", - "warning6": "hsl(45.19,32.53%,48.82%)", - "warning7": "hsl(45.32,40.17%,54.12%)", - "warning8": "hsl(45.14,50.72%,59.41%)", - "warning9": "hsl(45.25,65.56%,64.71%)", - "warning10": "hsl(45.23,84.42%,69.8%)", - "warning11": "hsl(46.5,61.54%,74.51%)", - "warning12": "hsl(51.43,26.42%,79.22%)" - }, - "error": { - "error1": "hsl(350.32,33.33%,18.24%)", - "error2": "hsl(352.17,41.82%,21.57%)", - "error3": "hsl(353.23,49.21%,24.71%)", - "error4": "hsl(354.62,54.17%,28.24%)", - "error5": "hsl(354.89,58.75%,31.37%)", - "error6": "hsl(355.64,61.8%,34.9%)", - "error7": "hsl(355.71,64.95%,38.04%)", - "error8": "hsl(355.74,66.82%,41.37%)", - "error9": "hsl(355.82,69.3%,44.71%)", - "error10": "hsl(356.18,70.61%,48.04%)", - "error11": "hsl(354.11,53.85%,59.22%)", - "error12": "hsl(345.88,33.77%,70.39%)" - }, - "accent": { - "accent1": "hsl(180,32.63%,18.63%)", - "accent2": "hsl(176.17,41.59%,22.16%)", - "accent3": "hsl(175.08,46.56%,25.69%)", - "accent4": "hsl(173.77,51.68%,29.22%)", - "accent5": "hsl(174.07,54.49%,32.75%)", - "accent6": "hsl(173.27,57.84%,36.27%)", - "accent7": "hsl(173.06,59.61%,39.8%)", - "accent8": "hsl(172.55,61.99%,43.33%)", - "accent9": "hsl(172.85,63.18%,46.86%)", - "accent10": "hsl(172.46,66.01%,50.39%)", - "accent11": "hsl(174.74,57%,60.78%)", - "accent12": "hsl(180.94,43.84%,71.37%)" - } -}; +export const theme = {"base":{"base1":"hsl(228,7.94%,12.35%)","base2":"hsl(228,7.25%,13.53%)","base3":"hsl(230,7.32%,16.08%)","base4":"hsl(222.86,6.93%,19.8%)","base5":"hsl(225,6.35%,24.71%)","base6":"hsl(220,5.81%,30.39%)","base7":"hsl(218.18,5.82%,37.06%)","base8":"hsl(216.92,5.68%,44.9%)","base9":"hsl(214.29,5.88%,53.33%)","base10":"hsl(212.73,26.83%,83.92%)","base11":"hsl(212.73,26.83%,91.96%)","base12":"hsl(0,0%,100%)"},"brand":{"brand1":"hsl(180,32.63%,18.63%)","brand2":"hsl(176.17,41.59%,22.16%)","brand3":"hsl(175.08,46.56%,25.69%)","brand4":"hsl(173.77,51.68%,29.22%)","brand5":"hsl(174.07,54.49%,32.75%)","brand6":"hsl(173.27,57.84%,36.27%)","brand7":"hsl(173.06,59.61%,39.8%)","brand8":"hsl(172.55,61.99%,43.33%)","brand9":"hsl(172.85,63.18%,46.86%)","brand10":"hsl(172.46,66.01%,50.39%)","brand11":"hsl(174.74,57%,60.78%)","brand12":"hsl(180.94,43.84%,71.37%)"},"alternate":{"alternate1":"hsl(328.13,33.33%,18.82%)","alternate2":"hsl(329.39,43.36%,22.16%)","alternate3":"hsl(330.46,49.62%,25.69%)","alternate4":"hsl(331.11,54.36%,29.22%)","alternate5":"hsl(332.45,58.33%,32.94%)","alternate6":"hsl(332.63,61.29%,36.47%)","alternate7":"hsl(332.77,63.73%,40%)","alternate8":"hsl(332.65,66.52%,43.33%)","alternate9":"hsl(333.29,68.33%,47.06%)","alternate10":"hsl(333.33,71.43%,50.59%)","alternate11":"hsl(330.77,58.79%,60.98%)","alternate12":"hsl(320.38,36.05%,71.18%)"},"help":{"help1":"hsl(233.08,21.67%,23.53%)","help2":"hsl(233.33,24.32%,29.02%)","help3":"hsl(233.62,26.55%,34.71%)","help4":"hsl(233.68,27.8%,40.2%)","help5":"hsl(234.63,28.51%,46.08%)","help6":"hsl(234.55,31.17%,51.57%)","help7":"hsl(234.55,40.37%,57.25%)","help8":"hsl(234.49,51.58%,62.75%)","help9":"hsl(234.5,67.7%,68.43%)","help10":"hsl(234.45,89.47%,73.92%)","help11":"hsl(233.02,74.14%,77.25%)","help12":"hsl(228,55.56%,80.59%)"},"success":{"success1":"hsl(166.45,36.47%,16.67%)","success2":"hsl(164.68,48.45%,19.02%)","success3":"hsl(163.55,56.36%,21.57%)","success4":"hsl(162.08,62.6%,24.12%)","success5":"hsl(161.29,67.88%,26.86%)","success6":"hsl(160.56,72%,29.41%)","success7":"hsl(160.49,75.46%,31.96%)","success8":"hsl(160.58,79.43%,34.31%)","success9":"hsl(160.13,81.91%,36.86%)","success10":"hsl(160.12,84.08%,39.41%)","success11":"hsl(162.93,48.74%,53.33%)","success12":"hsl(169.52,38.18%,67.65%)"},"info":{"info1":"hsl(213.53,29.82%,22.35%)","info2":"hsl(213.06,35.25%,27.25%)","info3":"hsl(212.81,39.02%,32.16%)","info4":"hsl(212.31,41.05%,37.25%)","info5":"hsl(212.55,43.52%,42.35%)","info6":"hsl(212.22,44.63%,47.45%)","info7":"hsl(212.2,50.62%,52.35%)","info8":"hsl(212.17,63.3%,57.25%)","info9":"hsl(211.97,79.17%,62.35%)","info10":"hsl(211.98,100%,67.25%)","info11":"hsl(211.76,85.61%,72.75%)","info12":"hsl(212.57,63.64%,78.43%)"},"warning":{"warning1":"hsl(45,17.24%,22.75%)","warning2":"hsl(44.52,21.68%,28.04%)","warning3":"hsl(45,25.88%,33.33%)","warning4":"hsl(45,28.57%,38.43%)","warning5":"hsl(45.22,30.94%,43.73%)","warning6":"hsl(45.19,32.53%,48.82%)","warning7":"hsl(45.32,40.17%,54.12%)","warning8":"hsl(45.14,50.72%,59.41%)","warning9":"hsl(45.25,65.56%,64.71%)","warning10":"hsl(45.23,84.42%,69.8%)","warning11":"hsl(46.5,61.54%,74.51%)","warning12":"hsl(51.43,26.42%,79.22%)"},"error":{"error1":"hsl(350.32,33.33%,18.24%)","error2":"hsl(352.17,41.82%,21.57%)","error3":"hsl(353.23,49.21%,24.71%)","error4":"hsl(354.62,54.17%,28.24%)","error5":"hsl(354.89,58.75%,31.37%)","error6":"hsl(355.64,61.8%,34.9%)","error7":"hsl(355.71,64.95%,38.04%)","error8":"hsl(355.74,66.82%,41.37%)","error9":"hsl(355.82,69.3%,44.71%)","error10":"hsl(356.18,70.61%,48.04%)","error11":"hsl(354.11,53.85%,59.22%)","error12":"hsl(345.88,33.77%,70.39%)"},"accent":{"accent1":"hsl(180,32.63%,18.63%)","accent2":"hsl(176.17,41.59%,22.16%)","accent3":"hsl(175.08,46.56%,25.69%)","accent4":"hsl(173.77,51.68%,29.22%)","accent5":"hsl(174.07,54.49%,32.75%)","accent6":"hsl(173.27,57.84%,36.27%)","accent7":"hsl(173.06,59.61%,39.8%)","accent8":"hsl(172.55,61.99%,43.33%)","accent9":"hsl(172.85,63.18%,46.86%)","accent10":"hsl(172.46,66.01%,50.39%)","accent11":"hsl(174.74,57%,60.78%)","accent12":"hsl(180.94,43.84%,71.37%)"}}; -export default theme; +export default theme; \ No newline at end of file diff --git a/packages/config/src/tamagui.config.ts b/packages/config/src/tamagui.config.ts index ad518558..0173c6b5 100644 --- a/packages/config/src/tamagui.config.ts +++ b/packages/config/src/tamagui.config.ts @@ -1,3 +1,20 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { animations } from "@cyclone-ui/animations"; import { createMonaSansFont } from "@cyclone-ui/font-mona-sans"; import { createPermanentMarkerFont } from "@cyclone-ui/font-permanent-marker"; @@ -79,6 +96,9 @@ export const options: CreateTamaguiProps = { defaultProps: { Paragraph: { fontFamily: "body" + }, + Label: { + fontFamily: "label" } } } satisfies CreateTamaguiProps; diff --git a/packages/form-state/src/atoms/atom-with-field.ts b/packages/form-state/src/atoms/atom-with-field.ts index 8e6f4e60..3cefe3d3 100644 --- a/packages/form-state/src/atoms/atom-with-field.ts +++ b/packages/form-state/src/atoms/atom-with-field.ts @@ -18,8 +18,9 @@ /* eslint-disable unicorn/no-null */ import { ColorRole } from "@cyclone-ui/colors"; +import { SelectOption } from "@storm-stack/types/utility-types/form"; import { atom, Atom } from "jotai"; -import { FieldStatus } from "../types"; +import { FieldOptions, FieldStatus, InferFieldState } from "../types"; export const atomWithFieldStatus = ( themeAtom: Atom @@ -45,35 +46,28 @@ export const atomWithFieldStatus = ( }); }; -// export const getFieldIndicator = ( -// fieldIndicators: InferFieldState, -// ifValueOverride?: boolean -// ): boolean => { -// if (isBoolean(fieldIndicators)) { -// return Boolean(fieldIndicators); -// } else { -// return !!Object.entries(fieldIndicators).reduce( -// (ret, [_, fieldIndicator]) => { -// if (isBoolean(fieldIndicator)) { -// return isSet(ifValueOverride) && ret === ifValueOverride -// ? ret -// : ret && fieldIndicator; -// } +export const atomWithFieldItems = ( + optionsAtom: Atom, + valueAtom: Atom, + disabledAtom: Atom> +): Atom => { + return atom(get => { + const options = get(optionsAtom); + const value = get(valueAtom); + const disabled = get(disabledAtom); -// return isSet(ifValueOverride) && ret === ifValueOverride -// ? ret -// : ret && getFieldIndicator(fieldIndicator, ifValueOverride); -// }, -// undefined as undefined | boolean -// ); -// } -// }; + return (options.items ?? []).reduce((ret, item, index) => { + if (!ret.some(existing => existing.value === item.value)) { + ret.push({ + index, -// export const atomWithFieldIndicator = ( -// fieldIndicatorAtom: Atom>, -// ifValueOverride?: boolean -// ) => { -// return atom(get => { -// return getFieldIndicator(get(fieldIndicatorAtom), ifValueOverride); -// }); -// }; + ...item, + selected: item.value === value, + disabled: Boolean(item.disabled) || Boolean(disabled) + }); + } + + return ret; + }, [] as SelectOption[]); + }); +}; diff --git a/packages/form-state/src/components/FieldIcon.tsx b/packages/form-state/src/components/FieldIcon.tsx deleted file mode 100644 index 5d51022c..00000000 --- a/packages/form-state/src/components/FieldIcon.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Button } from "@cyclone-ui/button"; -import { ColorRole } from "@cyclone-ui/colors"; -import { Spinner } from "@cyclone-ui/spinner"; -import { ThemeIcon } from "@cyclone-ui/themeable-icon"; -import { Tooltip } from "@tamagui/tooltip"; -import { useFieldActions, useFieldStore } from "../hooks"; -import { ValidationMessage } from "./ValidationMessage"; - -const InnerFieldIcon = Button.styleable((props, forwardedRef) => { - const { children, color, ...rest } = props; - - const store = useFieldStore(); - - const disabled = store.get.disabled(); - const theme = store.get.theme(); - const messages = store.get.messages(); - - return ( - - - - - - - - - - - ); -}); - -export const FieldIcon = Button.styleable((props, forwardedRef) => { - const { children, color, ...rest } = props; - - const store = useFieldStore(); - if (store.get.disabled()) { - return null; - } - - return ( - - {children} - - ); -}); - -export const FieldThemeIcon = FieldIcon.styleable((props, forwardedRef) => { - const store = useFieldStore(); - const { focus } = useFieldActions(); - - const disabled = store.get.disabled(); - const validating = store.get.validating(); - const theme = store.get.theme(); - - if (validating) { - return ; - } else if ( - !theme?.toLowerCase().includes(ColorRole.ERROR) && - !theme?.toLowerCase().includes(ColorRole.WARNING) && - !theme?.toLowerCase().includes(ColorRole.INFO) && - !theme?.toLowerCase().includes(ColorRole.HELP) && - !theme?.toLowerCase().includes(ColorRole.SUCCESS) && - !disabled - ) { - return null; - } - - return ( - - - - ); -}); diff --git a/packages/form-state/src/components/index.ts b/packages/form-state/src/components/index.ts deleted file mode 100644 index f26e2dc2..00000000 --- a/packages/form-state/src/components/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*------------------------------------------------------------------- - - ⚡ Storm Software - Cyclone UI - - This code was released as part of the Cyclone UI project. Cyclone UI - is maintained by Storm Software under the Apache-2.0 License, and is - free for commercial and private use. For more information, please visit - our licensing page. - - Website: https://stormsoftware.com - Repository: https://github.com/storm-software/cyclone-ui - Documentation: https://stormsoftware.com/projects/cyclone-ui/docs - Contact: https://stormsoftware.com/contact - License: https://stormsoftware.com/projects/cyclone-ui/license - - -------------------------------------------------------------------*/ - -export * from "./FieldIcon"; diff --git a/packages/form-state/src/hooks/use-field-actions.ts b/packages/form-state/src/hooks/use-field-actions.ts index 16cb480a..4d65d01d 100644 --- a/packages/form-state/src/hooks/use-field-actions.ts +++ b/packages/form-state/src/hooks/use-field-actions.ts @@ -269,11 +269,30 @@ export const useFieldActions = < ) ); + const setFocused = useCallback( + (focused: boolean) => { + if (focused) { + return focus(); + } + return blur(); + }, + [focus, blur] + ); + + const toggleFocused = useAtomCallback( + useCallback( + (get: Getter) => setFocused(!get(fieldApi.atom.focused)), + [setFocused] + ) + ); + return { initialize, change, focus, blur, + setFocused, + toggleFocused, validate, reset }; diff --git a/packages/form-state/src/index.ts b/packages/form-state/src/index.ts index 31647942..44e68ea3 100644 --- a/packages/form-state/src/index.ts +++ b/packages/form-state/src/index.ts @@ -15,8 +15,8 @@ -------------------------------------------------------------------*/ -export * from "./components"; export * from "./hooks"; export * from "./providers"; export * from "./stores"; export * from "./types"; +export * from "./utilities"; diff --git a/packages/form-state/src/providers/FieldStoreProvider.tsx b/packages/form-state/src/providers/FieldStoreProvider.tsx index da4e11eb..030a7224 100644 --- a/packages/form-state/src/providers/FieldStoreProvider.tsx +++ b/packages/form-state/src/providers/FieldStoreProvider.tsx @@ -1,3 +1,20 @@ +/*------------------------------------------------------------------- + + ⚡ Storm Software - Cyclone UI + + This code was released as part of the Cyclone UI project. Cyclone UI + is maintained by Storm Software under the Apache-2.0 License, and is + free for commercial and private use. For more information, please visit + our licensing page. + + Website: https://stormsoftware.com + Repository: https://github.com/storm-software/cyclone-ui + Documentation: https://stormsoftware.com/projects/cyclone-ui/docs + Contact: https://stormsoftware.com/contact + License: https://stormsoftware.com/projects/cyclone-ui/license + + -------------------------------------------------------------------*/ + import { useIsomorphicLayoutEffect } from "@storm-stack/hooks"; import { deepMerge } from "@storm-stack/utilities/helper-fns/deep-merge"; import { getField } from "@storm-stack/utilities/helper-fns/get-field"; @@ -42,12 +59,28 @@ export const FieldProvider = < const defaultValue = useMemo( () => getField(formOptions.defaultValues, options.name), - [formOptions.defaultFieldOptions, options.name] + [formOptions.defaultValues, options.name] ); const fieldOptions = useMemo( () => deepMerge(formOptions.defaultFieldOptions, options), [formOptions.defaultFieldOptions, options] ); + // const items = useMemo( + // () => + // (fieldOptions.items ?? []).reduce((ret, item, index) => { + // if (!ret.some(existing => existing.value === item.value)) { + // ret.push({ + // index, + // disabled: Boolean(fieldOptions.disabled), + // selected: item.value === defaultValue, + // ...item + // }); + // } + + // return ret; + // }, [] as SelectOption[]), + // [fieldOptions.items, fieldOptions.disabled, defaultValue] + // ); const fieldStore = useMemo( () => createFieldStore(fieldOptions.name), [fieldOptions.name] @@ -66,7 +99,6 @@ export const FieldProvider = < TFieldValue, boolean >, - items: fieldOptions.items ?? [], value: defaultValue, initialValue: defaultValue, options: { diff --git a/packages/form-state/src/stores/field-store.ts b/packages/form-state/src/stores/field-store.ts index fa87da37..c91f4621 100644 --- a/packages/form-state/src/stores/field-store.ts +++ b/packages/form-state/src/stores/field-store.ts @@ -15,8 +15,6 @@ -------------------------------------------------------------------*/ -/* eslint-disable unicorn/no-null */ - import { createAtomStore, CreateAtomStoreOptions, @@ -28,6 +26,8 @@ import { isEqual } from "@storm-stack/utilities/helper-fns/is-deep-equal"; import { toPath } from "@storm-stack/utilities/helper-fns/to-path"; import { atom } from "jotai"; import { focusAtom } from "jotai-optics"; +import { splitAtom } from "jotai/utils"; +import { atomWithFieldItems } from "../atoms/atom-with-field"; import { atomWithFieldsMessageList, atomWithFieldsMessageTypes, @@ -110,12 +110,22 @@ export const createFieldStore = (name: string) => { return String(value); }); + const itemsAtom = atomWithFieldItems( + atoms.options, + atoms.value, + atoms.disabled + ); + const itemsAtomsAtom = splitAtom(itemsAtom); + return { pristine: atom(get => !get(dirtyAtom)), dirty: dirtyAtom, formattedValue: formattedValueAtom, + items: itemsAtom, + itemsAtoms: itemsAtomsAtom, + errors: errorsAtom, warnings: warningsAtom, info: infoAtom, @@ -191,7 +201,6 @@ export const createFieldStore = (name: string) => { optic.path(...path) ), value: focusAtom(formStore.api.atom.values, optic => optic.path(...path)), - items: [], options: {} as FieldOptions }, selectors: fieldStoreSelectors diff --git a/packages/form-state/src/types.ts b/packages/form-state/src/types.ts index 94bf629d..94dd6b4d 100644 --- a/packages/form-state/src/types.ts +++ b/packages/form-state/src/types.ts @@ -246,7 +246,7 @@ export type FieldOptions< theme?: string; required?: boolean; disabled?: boolean; - items?: SelectOption[]; + items?: Array & Pick>; validate?: Record< `on${Capitalize}`, TValidator[] | undefined @@ -336,11 +336,6 @@ export type FieldBaseState = { */ value: TFieldValue | null; - /** - * A list of options for the field. - */ - items: SelectOption[]; - /** * The options provided when creating the field. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0698ac61..eefa77c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -411,10 +411,10 @@ importers: version: 1.106.0(typescript@5.6.2) '@storm-stack/errors': specifier: latest - version: 1.29.2(qv4fx2hc3vtgs63gzmf4xdsdza) + version: 1.30.0(qv4fx2hc3vtgs63gzmf4xdsdza) '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@trpc/client': specifier: 11.0.0-rc.382 version: 11.0.0-rc.382(@trpc/server@11.0.0-rc.382) @@ -481,7 +481,7 @@ importers: version: 1.194.0(fd2zhq2oqjlbh54bpjfl2eud2e) '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 tslib: specifier: ^2.6.2 version: 2.6.3 @@ -783,60 +783,27 @@ importers: components/checkbox: dependencies: - '@tamagui/animate-presence': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) '@tamagui/checkbox': specifier: ^1.110.5 version: 1.110.5(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) - '@tamagui/compose-refs': + '@tamagui/checkbox-headless': specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) + version: 1.110.5(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@tamagui/constants': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) - '@tamagui/focusable': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/font-size': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/get-button-sized': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/get-font-sized': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/get-token': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) '@tamagui/group': specifier: ^1.110.5 version: 1.110.5(@types/react@18.3.6)(immer@10.1.1)(react@18.3.1) - '@tamagui/helpers': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/helpers-tamagui': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/label': - specifier: ^1.110.5 - version: 1.110.5(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@tamagui/lucide-icons': specifier: ^1.110.5 version: 1.110.5(react-native-svg@15.3.0(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1))(react@18.3.1) '@tamagui/stacks': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) - '@tamagui/text': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/web': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) react-native-svg: specifier: ^15.2.0 version: 15.3.0(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) @@ -853,21 +820,18 @@ importers: components/checkbox-field: dependencies: - '@tamagui/core': - specifier: ^1.110.5 - version: 1.110.5(react@18.3.1) - '@tamagui/label': + '@storm-stack/types': + specifier: latest + version: 0.17.0 + '@tamagui/checkbox-headless': specifier: ^1.110.5 version: 1.110.5(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) - '@tamagui/stacks': + '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) - '@tamagui/web': + '@tamagui/stacks': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) - react-native-svg: - specifier: ^15.2.0 - version: 15.3.0(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) devDependencies: react: specifier: 18.3.1 @@ -978,10 +942,10 @@ importers: version: 6.6.1(react@18.3.1) '@storm-stack/date-time': specifier: latest - version: 1.30.0 + version: 1.31.0 '@storm-stack/hooks': specifier: latest - version: 0.8.2(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + version: 0.9.0(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@tamagui/adapt': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1121,7 +1085,7 @@ importers: dependencies: '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1134,6 +1098,9 @@ importers: '@tamagui/stacks': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) + '@tamagui/tooltip': + specifier: ^1.110.5 + version: 1.110.5(@types/react@18.3.6)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@tamagui/web': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1550,10 +1517,10 @@ importers: dependencies: '@storm-stack/hooks': specifier: latest - version: 0.8.2(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + version: 0.9.0(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@tamagui/constants': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1594,16 +1561,28 @@ importers: components/radio-group-field: dependencies: + '@storm-stack/hooks': + specifier: latest + version: 0.9.0(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + '@storm-stack/types': + specifier: latest + version: 0.17.0 '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) - '@tamagui/web': + '@tamagui/label': + specifier: ^1.110.5 + version: 1.110.5(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + '@tamagui/stacks': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) react-native-svg: specifier: ^15.2.0 version: 15.3.0(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) devDependencies: + jotai: + specifier: '>=2.8.0' + version: 2.8.4(@types/react@18.3.6)(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -1618,7 +1597,7 @@ importers: dependencies: '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@tamagui/adapt': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1631,6 +1610,9 @@ importers: '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) + '@tamagui/create-context': + specifier: ^1.110.5 + version: 1.110.5(react@18.3.1) '@tamagui/focusable': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1698,6 +1680,9 @@ importers: '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) + '@tamagui/create-context': + specifier: ^1.110.5 + version: 1.110.5(react@18.3.1) '@tamagui/web': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -1705,6 +1690,9 @@ importers: specifier: ^15.2.0 version: 15.3.0(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) devDependencies: + jotai: + specifier: '>=2.8.0' + version: 2.8.4(@types/react@18.3.6)(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -1946,7 +1934,7 @@ importers: dependencies: '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -2113,22 +2101,22 @@ importers: dependencies: '@storm-stack/errors': specifier: latest - version: 1.29.2(qv4fx2hc3vtgs63gzmf4xdsdza) + version: 1.30.0(qv4fx2hc3vtgs63gzmf4xdsdza) '@storm-stack/hooks': specifier: latest - version: 0.8.2(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + version: 0.9.0(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@storm-stack/serialization': specifier: latest - version: 1.28.0 + version: 1.29.0 '@storm-stack/string-fns': specifier: latest - version: 0.11.2 + version: 0.12.0 '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@storm-stack/utilities': specifier: latest - version: 1.29.2 + version: 1.30.0 '@tamagui/core': specifier: ^1.110.5 version: 1.110.5(react@18.3.1) @@ -2244,7 +2232,7 @@ importers: dependencies: '@storm-stack/errors': specifier: latest - version: 1.29.2(qv4fx2hc3vtgs63gzmf4xdsdza) + version: 1.30.0(qv4fx2hc3vtgs63gzmf4xdsdza) '@trpc/client': specifier: 11.0.0-rc.382 version: 11.0.0-rc.382(@trpc/server@11.0.0-rc.382) @@ -2263,16 +2251,16 @@ importers: dependencies: '@storm-stack/serialization': specifier: latest - version: 1.28.0 + version: 1.29.0 '@storm-stack/string-fns': specifier: latest - version: 0.11.2 + version: 0.12.0 '@storm-stack/types': specifier: latest - version: 0.16.0 + version: 0.17.0 '@storm-stack/utilities': specifier: latest - version: 1.29.2 + version: 1.30.0 devDependencies: '@tanstack/store': specifier: ^0.5.5 @@ -7299,14 +7287,14 @@ packages: '@microsoft/api-extractor': optional: true - '@storm-stack/date-time@1.30.0': - resolution: {integrity: sha512-7zCEpFCGWHcIKhhhhaiA1b7sECrftCjmdCaU91LqbYbGwCqORvibZ37SDQVULd1Z9fwm1YnygmnIiUkkagnk4Q==} + '@storm-stack/date-time@1.31.0': + resolution: {integrity: sha512-HsLDfXh7xhK7SbwnkayN+ydPXHwtaG2OqoVXYzlH6ZSxLLBejddroOvUMq2z6/ZqX258g04iMj5fVIqtZ4cXsw==} - '@storm-stack/errors@1.29.2': - resolution: {integrity: sha512-x7aBOAh6Vm2fSeM1vCTfFCUKoXpRweTL5nijWevKHf69IRsyl5ZclwH94OIr8glr6AP5NMlInzUnfZ6cJmNJkw==} + '@storm-stack/errors@1.30.0': + resolution: {integrity: sha512-8gWskmPrSluxPUWubFk4iVM2L6f8N0JzJTBgqAd+Gqngtp7GxmP0PBd374FzJJ5X6i9SUgNMBeCDVNOmEWqjrA==} - '@storm-stack/hooks@0.8.2': - resolution: {integrity: sha512-gD4PRvKkA9e43HYxvzeheiLhBX/WOBjtUT96PeJHmTYa8qnvyz1engVjygGqGGBr42spP/tKtvpJJmvi24vGvA==} + '@storm-stack/hooks@0.9.0': + resolution: {integrity: sha512-Uu95iqkbmyIFDV2otw9uAkp3AF/QG+NbqoFMInQ1ZLunNRMBIjg2fOjhFmMI+2ADCR54lE/b4JS8shsZpY7euQ==} peerDependencies: react: 19.0.0-rc-f6cce072-20240723 react-dom: 19.0.0-rc-f6cce072-20240723 @@ -7319,17 +7307,17 @@ packages: react-native: optional: true - '@storm-stack/serialization@1.28.0': - resolution: {integrity: sha512-36HuZ6zJA3IxTNsbQojx4BmA+H+45x7k2KcmyHVXUGy5MP0X5huCevpRKL2NznTit3zKH2cZWE9RHmYJYTiDwQ==} + '@storm-stack/serialization@1.29.0': + resolution: {integrity: sha512-ZN90AH00NwGaREEYMioOM5IxUmrdXvUPr/w8O6pNNYuh0H69eUXa3XB4050AKptMtBl4TRQSzjuHOAdM0KNNXw==} - '@storm-stack/string-fns@0.11.2': - resolution: {integrity: sha512-7bB1XDSXXAUHDNPPFU0TiSrfbjFsnlmnBMns2bJoa/rU9WO0YfQogKCkPC1rwW/FuOlyWPjGt9/nKrzXwxLIOw==} + '@storm-stack/string-fns@0.12.0': + resolution: {integrity: sha512-kizm008RJm7++UPWk0uaRzs9FFPLviBgfuhjEBq595nTq2gx+rlbLJrTwqJs1Ov91fai7OfAbXKSGGyZ7k7aRg==} - '@storm-stack/types@0.16.0': - resolution: {integrity: sha512-8ibAnEKY6yjyx4rSCbZYN7SkFg/bwHna/qtD5/npKv2Vv2qgWLNt2QtFfrkDH6R8wyyBbAGLGTaAYS7psCUutA==} + '@storm-stack/types@0.17.0': + resolution: {integrity: sha512-yw/MBGURNuJrjsNbl8bAOBGTtT90YoFwfGDqx/SrmcKScOBJYf5R+bdh5sPORpBZ2/zK65vQ/TJ690eGWEiQlw==} - '@storm-stack/utilities@1.29.2': - resolution: {integrity: sha512-6tvT4rAnLaIf3TmH3A0vUns5p3kouhbCdCVOlV5fgufrUMokpnIR8iCpO88oW09i3OlFIW0ncM1e4QB85kxYlQ==} + '@storm-stack/utilities@1.30.0': + resolution: {integrity: sha512-NJEHzdyb3wJKlQkW8LKCsl5Abs4q0W0ZH4fNv/RM/V3BVJr7yWf6BV96eGCiapO0ph4LKvq14DX1bJMjjfl8wg==} '@storybook/addon-actions@8.3.0': resolution: {integrity: sha512-HvAc3fW979JVw8CSKXZMouvgrJ2BNLNWaUB8jNokQb3Us00P6igVKLwg/pBV8GBgDr5Ng4pHYqi/ZH+xzEYFFw==} @@ -29729,12 +29717,12 @@ snapshots: - ts-node - webpack-sources - '@storm-stack/date-time@1.30.0': + '@storm-stack/date-time@1.31.0': dependencies: '@formkit/tempo': 0.1.2 '@js-temporal/polyfill': 0.4.4 - '@storm-stack/errors@1.29.2(qv4fx2hc3vtgs63gzmf4xdsdza)': + '@storm-stack/errors@1.30.0(qv4fx2hc3vtgs63gzmf4xdsdza)': dependencies: '@storm-software/testing-tools': 1.78.0(@nx/jest@19.7.3(@babel/traverse@7.25.6)(@swc-node/register@1.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(nx@19.7.3(@swc-node/register@1.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.12)(typescript@5.6.2))(@swc/core@1.7.26(@swc/helpers@0.5.13)))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.6.2))(typescript@5.6.2)(verdaccio@5.31.1(encoding@0.1.13)(typanion@3.14.0)))(jest@29.7.0(@types/node@22.5.5)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.6.2))) '@storm-software/workspace-tools': 1.194.0(fd2zhq2oqjlbh54bpjfl2eud2e) @@ -29764,23 +29752,23 @@ snapshots: - typescript - webpack-sources - '@storm-stack/hooks@0.8.2(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)': + '@storm-stack/hooks@0.9.0(react-dom@18.3.1(react@18.3.1))(react-native@0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)': optionalDependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-native: 0.74.1(@babel/core@7.25.2)(@babel/preset-env@7.24.7(@babel/core@7.25.2))(@types/react@18.3.6)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10) - '@storm-stack/serialization@1.28.0': + '@storm-stack/serialization@1.29.0': dependencies: buffer: 6.0.3 superjson: 2.2.1 ufo: 1.5.4 - '@storm-stack/string-fns@0.11.2': {} + '@storm-stack/string-fns@0.12.0': {} - '@storm-stack/types@0.16.0': {} + '@storm-stack/types@0.17.0': {} - '@storm-stack/utilities@1.29.2': {} + '@storm-stack/utilities@1.30.0': {} '@storybook/addon-actions@8.3.0(storybook@8.3.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: