@korsolutions/ui 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/index.d.mts +84 -0
  2. package/dist/components/index.mjs +288 -0
  3. package/dist/index-Dafk8ZGv.d.mts +265 -0
  4. package/dist/index.d.mts +45 -0
  5. package/dist/index.mjs +11 -0
  6. package/dist/portal-DoPaAohb.mjs +61 -0
  7. package/dist/primitives/index.d.mts +2 -0
  8. package/dist/primitives/index.mjs +4 -0
  9. package/dist/primitives-BYUlEz2_.mjs +442 -0
  10. package/dist/themes-BrLbh9h6.mjs +86 -0
  11. package/package.json +20 -8
  12. package/src/components/button/button.tsx +24 -0
  13. package/src/components/button/variants/default.tsx +52 -0
  14. package/src/components/button/variants/index.ts +5 -0
  15. package/src/components/card/card.tsx +26 -0
  16. package/src/components/card/variants/default.tsx +38 -0
  17. package/src/components/card/variants/index.ts +5 -0
  18. package/src/components/field/field.tsx +27 -0
  19. package/src/components/field/variants/default.tsx +29 -0
  20. package/src/components/field/variants/index.ts +5 -0
  21. package/src/components/index.ts +5 -0
  22. package/src/components/input/input.tsx +14 -0
  23. package/src/components/input/variants/default.tsx +34 -0
  24. package/src/components/input/variants/index.ts +5 -0
  25. package/src/components/select/select.tsx +35 -0
  26. package/src/components/select/variants/default.tsx +81 -0
  27. package/src/components/select/variants/index.ts +5 -0
  28. package/src/index.tsx +13 -0
  29. package/src/primitives/button/button-context.tsx +5 -5
  30. package/src/primitives/button/button-label.tsx +7 -5
  31. package/src/primitives/button/button-root.tsx +12 -8
  32. package/src/primitives/button/button-spinner.tsx +14 -0
  33. package/src/primitives/button/index.ts +5 -3
  34. package/src/primitives/button/types.ts +6 -5
  35. package/src/primitives/card/card-body.tsx +1 -1
  36. package/src/primitives/card/card-footer.tsx +1 -1
  37. package/src/primitives/card/card-header.tsx +1 -1
  38. package/src/primitives/card/card-root.tsx +1 -1
  39. package/src/primitives/card/card-title.tsx +1 -1
  40. package/src/primitives/card/index.ts +1 -1
  41. package/src/primitives/field/context.ts +5 -19
  42. package/src/primitives/field/field-description.tsx +17 -0
  43. package/src/primitives/field/field-error.tsx +17 -0
  44. package/src/primitives/field/field-label.tsx +7 -3
  45. package/src/primitives/field/field-root.tsx +13 -59
  46. package/src/primitives/field/index.ts +9 -6
  47. package/src/primitives/field/types.ts +8 -7
  48. package/src/primitives/index.ts +0 -2
  49. package/src/primitives/input/index.ts +1 -1
  50. package/src/primitives/input/input.tsx +48 -13
  51. package/src/primitives/input/types.ts +2 -4
  52. package/src/primitives/select/context.ts +3 -3
  53. package/src/primitives/select/index.ts +2 -2
  54. package/src/primitives/select/select-content.tsx +2 -2
  55. package/src/primitives/select/select-option.tsx +27 -4
  56. package/src/primitives/select/select-overlay.tsx +1 -1
  57. package/src/primitives/select/select-root.tsx +13 -11
  58. package/src/primitives/select/select-trigger.tsx +3 -2
  59. package/src/primitives/select/select-value.tsx +4 -1
  60. package/src/primitives/select/types.ts +3 -1
  61. package/src/themes/default/colors.ts +45 -0
  62. package/src/themes/default/index.ts +11 -0
  63. package/src/themes/index.ts +2 -0
  64. package/src/themes/provider.tsx +56 -0
  65. package/src/themes/themes.ts +6 -0
  66. package/src/themes/types.ts +30 -0
  67. package/src/utils/hsla-utils.ts +10 -0
  68. package/src/utils/use-themed-styles.ts +13 -0
  69. package/tsconfig.json +8 -0
  70. package/tsdown.config.ts +8 -0
  71. package/src/composites/card.tsx +0 -23
  72. package/src/composites/index.ts +0 -1
  73. package/src/index.ts +0 -1
  74. package/src/primitives/field/field-control.tsx +0 -29
  75. package/src/primitives/provider.tsx +0 -10
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { StyleProp, Text, TextStyle } from "react-native";
3
+ import { useField } from "./context";
4
+
5
+ export interface FieldErrorProps {
6
+ children: string;
7
+
8
+ render?: (props: FieldErrorProps) => React.ReactNode;
9
+
10
+ style?: StyleProp<TextStyle>;
11
+ }
12
+
13
+ export function FieldError(props: FieldErrorProps) {
14
+ const field = useField();
15
+ const Component = props.render ?? Text;
16
+ return <Component {...props} style={[field.styles?.error, props.style]} />;
17
+ }
@@ -4,13 +4,17 @@ import { Text, TextProps } from "react-native";
4
4
 
5
5
  export interface FieldLabelProps {
6
6
  children: string;
7
- style?: TextProps["style"];
7
+
8
+ htmlFor?: string;
9
+
8
10
  render?: (props: FieldLabelProps) => React.ReactNode;
11
+
12
+ style?: TextProps["style"];
9
13
  }
10
14
 
11
15
  export function FieldLabel(props: FieldLabelProps) {
12
16
  const field = useField();
13
- const calculatedStyle = [field.styles?.label?.default, field.styles?.label?.[field.state], props.style];
14
17
 
15
- return props.render ? props.render(props) : <Text style={calculatedStyle}>{props.children}</Text>;
18
+ const Component = props.render ?? Text;
19
+ return <Component {...props} htmlFor={props.htmlFor} style={[field.styles?.label, props.style]} />;
16
20
  }
@@ -1,69 +1,23 @@
1
- import React, { useState } from "react";
2
- import { Pressable, StyleProp, View, ViewStyle } from "react-native";
1
+ import React from "react";
2
+ import { StyleProp, View, ViewStyle } from "react-native";
3
3
  import { FieldContext } from "./context";
4
- import { FieldState, FieldStyles } from "./types";
4
+ import { FieldStyles } from "./types";
5
5
 
6
- export interface FieldRootProps<TControlStyles = unknown> {
7
- value: string | null;
8
- onChange: (value: string) => void;
9
-
10
- required?: boolean;
11
- disabled?: boolean;
12
- error?: string | null;
6
+ export interface FieldPrimitiveRootProps {
13
7
  children?: React.ReactNode;
14
- style?: StyleProp<ViewStyle>;
15
-
16
- styles?: FieldStyles<TControlStyles>;
17
- }
18
-
19
- const calculateState = (props: FieldRootProps, focused: boolean, hovered: boolean): FieldState => {
20
- if (props.disabled) {
21
- return "disabled";
22
- }
23
- if (props.error) {
24
- return "error";
25
- }
26
- if (focused) {
27
- return "focused";
28
- }
29
- if (hovered) {
30
- return "hovered";
31
- }
32
-
33
- return "default";
34
- };
35
-
36
- export function FieldRoot<TControlStyles = unknown>(props: FieldRootProps<TControlStyles>) {
37
- const [focused, setFocused] = useState(false);
38
- const [hovered, setHovered] = useState(false);
39
8
 
40
- const state = calculateState(props, focused, hovered);
9
+ render?: (props: FieldPrimitiveRootProps) => React.ReactNode;
41
10
 
42
- const calculatedStyle = [props.styles?.root?.default, props.styles?.root?.[state], props.style];
11
+ style?: StyleProp<ViewStyle>;
12
+ styles?: FieldStyles;
13
+ }
43
14
 
15
+ export function FieldRoot(props: FieldPrimitiveRootProps) {
16
+ const composedStyles = [props.styles?.root, props.style];
17
+ const Component = props.render ?? View;
44
18
  return (
45
- <FieldContext.Provider
46
- value={{
47
- value: props.value,
48
- onChange: props.onChange,
49
-
50
- focused,
51
- setFocused,
52
-
53
- hovered,
54
- setHovered,
55
-
56
- required: props.required,
57
- disabled: props.disabled,
58
- error: props.error ?? null,
59
-
60
- state: state,
61
- styles: props.styles,
62
- }}
63
- >
64
- <Pressable onHoverIn={() => setHovered(true)} onHoverOut={() => setHovered(false)} style={calculatedStyle}>
65
- {props.children}
66
- </Pressable>
19
+ <FieldContext.Provider value={{ styles: props.styles }}>
20
+ <Component {...props} style={composedStyles} />
67
21
  </FieldContext.Provider>
68
22
  );
69
23
  }
@@ -1,14 +1,17 @@
1
1
  import { FieldRoot } from "./field-root";
2
2
  import { FieldLabel } from "./field-label";
3
- import { FieldControl } from "./field-control";
3
+ import { FieldDescription } from "./field-description";
4
+ import { FieldError } from "./field-error";
4
5
 
5
- export const Field = {
6
+ export const FieldPrimitive = {
6
7
  Root: FieldRoot,
7
8
  Label: FieldLabel,
8
- Control: FieldControl,
9
+ Description: FieldDescription,
10
+ Error: FieldError,
9
11
  };
10
12
 
11
- export type { FieldRootProps } from "./field-root";
13
+ export type { FieldPrimitiveRootProps } from "./field-root";
12
14
  export type { FieldLabelProps } from "./field-label";
13
- export type { FieldControlProps } from "./field-control";
14
- export * from "./types";
15
+ export type { FieldDescriptionProps } from "./field-description";
16
+ export type { FieldErrorProps } from "./field-error";
17
+ export type { FieldStyles } from "./types";
@@ -1,10 +1,11 @@
1
+ import { FieldPrimitiveRootProps } from "./field-root";
1
2
  import { FieldLabelProps } from "./field-label";
2
- import { FieldRootProps } from "./field-root";
3
+ import { FieldDescriptionProps } from "./field-description";
4
+ import { FieldErrorProps } from "./field-error";
3
5
 
4
- export type FieldState = "default" | "disabled" | "error" | "focused" | "hovered";
5
-
6
- export interface FieldStyles<TControlStyles = unknown> {
7
- root?: Partial<Record<FieldState, FieldRootProps["style"]>>;
8
- label?: Partial<Record<FieldState, FieldLabelProps["style"]>>;
9
- control?: Partial<Record<FieldState, TControlStyles>>;
6
+ export interface FieldStyles {
7
+ root?: FieldPrimitiveRootProps["style"];
8
+ label?: FieldLabelProps["style"];
9
+ description?: FieldDescriptionProps["style"];
10
+ error?: FieldErrorProps["style"];
10
11
  }
@@ -1,5 +1,3 @@
1
- export * from "./provider";
2
-
3
1
  export * from "./field";
4
2
  export * from "./input";
5
3
  export * from "./button";
@@ -1,2 +1,2 @@
1
1
  export * from "./input";
2
- export { InputStyles } from "./types";
2
+ export type { InputStyles } from "./types";
@@ -1,20 +1,55 @@
1
- import { StyleProp, TextInput, TextInputProps, TextStyle } from "react-native";
2
- import { InputStyles } from "./types";
3
- import { calculateComposedStyles } from "../../utils/calculate-styles";
1
+ import { TextInput, TextInputProps } from "react-native";
2
+ import { InputState, InputStyles } from "./types";
3
+ import { useState } from "react";
4
4
 
5
- export interface InputProps {
6
- defaultValue?: TextInputProps["defaultValue"];
7
- value: string | null;
8
- onChange: (value: string) => void;
5
+ export type InputPrimitiveBaseProps = Omit<TextInputProps, "onChange"> & {
6
+ onChange?: TextInputProps["onChangeText"];
9
7
 
10
- onFocus?: TextInputProps["onFocus"];
11
- onBlur?: TextInputProps["onBlur"];
8
+ isDisabled?: boolean;
9
+ };
10
+
11
+ export interface InputPrimitiveProps extends InputPrimitiveBaseProps {
12
+ render?: (props: InputPrimitiveProps) => React.ReactNode;
12
13
 
13
- style?: StyleProp<TextStyle>;
14
14
  styles?: InputStyles;
15
15
  }
16
16
 
17
- export function Input(props: InputProps) {
18
- const composedStyles = calculateComposedStyles(props.styles, "default", "root", props.style);
19
- return <TextInput {...props} value={props.value ?? ""} onChange={undefined} onChangeText={props.onChange} style={composedStyles} />;
17
+ const calculateState = (props: InputPrimitiveProps, isFocused: boolean): InputState => {
18
+ if (props.isDisabled) {
19
+ return "disabled";
20
+ }
21
+ if (isFocused) {
22
+ return "focused";
23
+ }
24
+ return "default";
25
+ };
26
+
27
+ export function InputPrimitive(props: InputPrimitiveProps) {
28
+ const [isFocused, setIsFocused] = useState(false);
29
+ const state = calculateState(props, isFocused);
30
+
31
+ const composedStyles = [props.styles?.default?.style, props.styles?.[state]?.style, props.style];
32
+ const composedProps = {
33
+ ...props.styles?.default,
34
+ ...props.styles?.[state],
35
+ ...props,
36
+ };
37
+ const Component = props.render ?? TextInput;
38
+ return (
39
+ <Component
40
+ {...composedProps}
41
+ onChange={undefined}
42
+ onChangeText={props.onChange}
43
+ onFocus={(e) => {
44
+ setIsFocused(true);
45
+ props.onFocus?.(e);
46
+ }}
47
+ onBlur={(e) => {
48
+ setIsFocused(false);
49
+ props.onBlur?.(e);
50
+ }}
51
+ readOnly={props.isDisabled || props.readOnly}
52
+ style={composedStyles}
53
+ />
54
+ );
20
55
  }
@@ -1,7 +1,5 @@
1
- import { InputProps } from "./input";
1
+ import { InputPrimitiveBaseProps } from "./input";
2
2
 
3
3
  export type InputState = "default" | "focused" | "disabled";
4
4
 
5
- export interface InputStyles {
6
- root: Partial<Record<InputState, InputProps["style"]>>;
7
- }
5
+ export type InputStyles = Partial<Record<InputState, InputPrimitiveBaseProps>>;
@@ -3,9 +3,9 @@ import { SelectOption, SelectState, SelectStyles } from "./types";
3
3
  import { LayoutRectangle } from "react-native";
4
4
 
5
5
  export interface SelectContext {
6
- value: string | null;
6
+ value?: string;
7
7
  onChange?: (value: string) => void;
8
- placeholder: string | null;
8
+ placeholder?: string;
9
9
 
10
10
  isOpen: boolean;
11
11
  setIsOpen: Dispatch<React.SetStateAction<boolean>>;
@@ -14,7 +14,7 @@ export interface SelectContext {
14
14
  options: Array<SelectOption>;
15
15
  setOptions: Dispatch<React.SetStateAction<Array<SelectOption>>>;
16
16
 
17
- disabled: boolean;
17
+ isDisabled: boolean;
18
18
 
19
19
  state: SelectState;
20
20
  styles: SelectStyles | null;
@@ -6,7 +6,7 @@ import { SelectOverlay } from "./select-overlay";
6
6
  import { SelectContent } from "./select-content";
7
7
  import { SelectOption } from "./select-option";
8
8
 
9
- export const Select = {
9
+ export const SelectPrimitive = {
10
10
  Root: SelectRoot,
11
11
  Trigger: SelectTrigger,
12
12
  Value: SelectValue,
@@ -16,7 +16,7 @@ export const Select = {
16
16
  Option: SelectOption,
17
17
  };
18
18
 
19
- export type { SelectRootProps } from "./select-root";
19
+ export type { SelectRootProps, SelectRootBaseProps } from "./select-root";
20
20
  export type { SelectTriggerProps } from "./select-trigger";
21
21
  export type { SelectValueProps } from "./select-value";
22
22
  export type { SelectPortalProps } from "./select-portal";
@@ -1,6 +1,6 @@
1
- import React, { useEffect } from "react";
1
+ import React from "react";
2
2
  import { StyleProp, View, ViewStyle } from "react-native";
3
- import { calculateComposedStyles } from "../../utils/calculate-styles";
3
+ import { calculateComposedStyles } from "@/utils/calculate-styles";
4
4
  import { useSelect } from "./context";
5
5
 
6
6
  export interface SelectContentProps {
@@ -1,20 +1,41 @@
1
1
  import { StyleProp, Text, TextStyle } from "react-native";
2
- import { calculateComposedStyles } from "../../utils/calculate-styles";
2
+ import { calculateComposedStyles } from "@/utils/calculate-styles";
3
3
  import { useSelect } from "./context";
4
- import { useEffect } from "react";
4
+ import { useEffect, useState } from "react";
5
+ import { SelectOptionState, SelectState } from "./types";
5
6
 
6
7
  export interface SelectOptionProps {
7
8
  children: string;
8
9
  value: string;
9
10
 
10
- style?: StyleProp<TextStyle>;
11
+ onMouseEnter?: () => void;
12
+ onMouseLeave?: () => void;
11
13
 
12
14
  render?: (props: SelectOptionProps) => React.ReactElement;
15
+
16
+ style?: StyleProp<TextStyle>;
13
17
  }
14
18
 
19
+ const calculateState = (selectState: SelectState, hovered: boolean, selected: boolean): SelectOptionState => {
20
+ if (selectState === "disabled") {
21
+ return "disabled";
22
+ }
23
+ if (selected) {
24
+ return "selected";
25
+ }
26
+ if (hovered) {
27
+ return "hovered";
28
+ }
29
+ return "default";
30
+ };
31
+
15
32
  export function SelectOption(props: SelectOptionProps) {
33
+ const [isHovered, setIsHovered] = useState(false);
16
34
  const select = useSelect();
17
- const composedStyles = calculateComposedStyles(select.styles, select.state, "option", props.style);
35
+ const isSelected = select.value === props.value;
36
+
37
+ const optionState = calculateState(select.state, isHovered, isSelected);
38
+ const composedStyles = calculateComposedStyles(select.styles, optionState, "option", props.style);
18
39
 
19
40
  useEffect(() => {
20
41
  select.setOptions((prev) => {
@@ -33,6 +54,8 @@ export function SelectOption(props: SelectOptionProps) {
33
54
  select.onChange?.(props.value);
34
55
  select.setIsOpen(false);
35
56
  }}
57
+ onMouseEnter={() => setIsHovered(true)}
58
+ onMouseLeave={() => setIsHovered(false)}
36
59
  style={composedStyles}
37
60
  >
38
61
  {props.children}
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { useSelect } from "./context";
3
3
  import { Pressable, StyleProp, StyleSheet, ViewStyle } from "react-native";
4
- import { calculateComposedStyles } from "../../utils/calculate-styles";
4
+ import { calculateComposedStyles } from "@/utils/calculate-styles";
5
5
 
6
6
  export interface SelectOverlayProps {
7
7
  children?: React.ReactNode;
@@ -2,20 +2,22 @@ import React, { useState } from "react";
2
2
  import { LayoutRectangle, StyleProp, View, ViewStyle } from "react-native";
3
3
  import { SelectContext } from "./context";
4
4
  import { SelectOption, SelectState, SelectStyles } from "./types";
5
- import { calculateComposedStyles } from "../../utils/calculate-styles";
5
+ import { calculateComposedStyles } from "@/utils/calculate-styles";
6
6
 
7
7
  interface SelectRootInjectedProps {
8
8
  style?: StyleProp<ViewStyle>;
9
9
  }
10
10
 
11
- export interface SelectRootProps {
12
- children?: React.ReactNode;
13
-
14
- value: string | null;
15
- onChange: (value: string) => void;
11
+ export interface SelectRootBaseProps {
12
+ value?: string;
13
+ onChange?: (value: string) => void;
16
14
  placeholder?: string;
17
15
 
18
- disabled?: boolean;
16
+ isDisabled?: boolean;
17
+ }
18
+
19
+ export interface SelectRootProps extends SelectRootBaseProps {
20
+ children?: React.ReactNode;
19
21
 
20
22
  render?: (props: SelectRootInjectedProps) => React.ReactElement;
21
23
 
@@ -24,7 +26,7 @@ export interface SelectRootProps {
24
26
  }
25
27
 
26
28
  const calculateState = (props: SelectRootProps): SelectState => {
27
- if (props.disabled) {
29
+ if (props.isDisabled) {
28
30
  return "disabled";
29
31
  }
30
32
  return "default";
@@ -42,9 +44,9 @@ export function SelectRoot(props: SelectRootProps) {
42
44
  return (
43
45
  <SelectContext.Provider
44
46
  value={{
45
- value: props.value ?? null,
47
+ value: props.value,
46
48
  onChange: props.onChange,
47
- placeholder: props.placeholder ?? null,
49
+ placeholder: props.placeholder,
48
50
  isOpen,
49
51
  setIsOpen,
50
52
  triggerLayout,
@@ -52,7 +54,7 @@ export function SelectRoot(props: SelectRootProps) {
52
54
  options,
53
55
  setOptions,
54
56
  state,
55
- disabled: props.disabled ?? false,
57
+ isDisabled: props.isDisabled ?? false,
56
58
  styles: props.styles ?? null,
57
59
  }}
58
60
  >
@@ -1,8 +1,8 @@
1
1
  import React from "react";
2
2
  import { Pressable, StyleProp, ViewStyle } from "react-native";
3
3
  import { useSelect } from "./context";
4
- import { calculateComposedStyles } from "../../utils/calculate-styles";
5
- import { normalizeLayout } from "../../utils/normalize-layout";
4
+ import { calculateComposedStyles } from "@/utils/calculate-styles";
5
+ import { normalizeLayout } from "@/utils/normalize-layout";
6
6
 
7
7
  interface SelectTriggerInjectionProps {
8
8
  onPress?: () => void;
@@ -29,6 +29,7 @@ export function SelectTrigger(props: SelectTriggerProps) {
29
29
  const layout = normalizeLayout(e.nativeEvent.layout);
30
30
  select.setTriggerLayout(layout);
31
31
  }}
32
+ disabled={select.isDisabled}
32
33
  style={composedStyles}
33
34
  >
34
35
  {props.children}
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { StyleProp, Text, TextStyle } from "react-native";
3
3
  import { useSelect } from "./context";
4
+ import { calculateComposedStyles } from "@/utils/calculate-styles";
4
5
 
5
6
  export interface SelectValueProps {
6
7
  style?: StyleProp<TextStyle>;
@@ -12,7 +13,9 @@ export function SelectValue(props: SelectValueProps) {
12
13
  const select = useSelect();
13
14
 
14
15
  const selectedOption = select.options.find((option) => option.value === select.value);
16
+ const selectedOptionLabel = selectedOption?.label;
15
17
 
18
+ const composedStyles = calculateComposedStyles(select.styles, select.state, selectedOptionLabel ? "value" : "placeholder", props.style);
16
19
  const Component = props.render ?? Text;
17
- return <Component style={props.style}>{selectedOption?.label ?? select.placeholder}</Component>;
20
+ return <Component style={composedStyles}>{selectedOption?.label ?? select.placeholder}</Component>;
18
21
  }
@@ -6,14 +6,16 @@ import { SelectContentProps } from "./select-content";
6
6
  import { SelectOptionProps } from "./select-option";
7
7
 
8
8
  export type SelectState = "default" | "disabled";
9
+ export type SelectOptionState = SelectState | "hovered" | "selected";
9
10
 
10
11
  export interface SelectStyles {
11
12
  root?: Partial<Record<SelectState, SelectRootProps["style"]>>;
12
13
  trigger?: Partial<Record<SelectState, SelectTriggerProps["style"]>>;
13
14
  value?: Partial<Record<SelectState, SelectValueProps["style"]>>;
15
+ placeholder?: Partial<Record<SelectState, SelectValueProps["style"]>>;
14
16
  overlay?: Partial<Record<SelectState, SelectOverlayProps["style"]>>;
15
17
  content?: Partial<Record<SelectState, SelectContentProps["style"]>>;
16
- option?: Partial<Record<SelectState, SelectOptionProps["style"]>>;
18
+ option?: Partial<Record<SelectOptionState, SelectOptionProps["style"]>>;
17
19
  }
18
20
 
19
21
  export interface SelectOption {
@@ -0,0 +1,45 @@
1
+ import { Colors } from "../types";
2
+
3
+ export const lightColors: Colors = {
4
+ background: "hsla(223.81, 100%, 100%, 1)",
5
+ foreground: "hsla(223.81, 0%, 3.94%, 1)",
6
+
7
+ primary: "hsla(223.81, 0%, 9.05%, 1)",
8
+ primaryForeground: "hsla(223.81, 0%, 98.03%, 1)",
9
+
10
+ secondary: "hsla(223.81, 0%, 96.06%, 1)",
11
+ secondaryForeground: "hsla(223.81, 0%, 9.05%, 1)",
12
+
13
+ muted: "hsla(223.81, 0%, 96.06%, 1)",
14
+ mutedForeground: "hsla(223.81, 0%, 45.15%, 1)",
15
+
16
+ border: "hsla(223.81, 0%, 89.82%, 1)",
17
+ surface: "hsla(223.81, 100%, 100%, 1)",
18
+
19
+ danger: "hsla(0, 84.2%, 60.2%, 1)",
20
+ success: "hsla(173, 58%, 39%, 1)",
21
+ warning: "hsla(43, 74%, 66%, 1)",
22
+ info: "hsla(197, 37%, 24%, 1)",
23
+ };
24
+
25
+ export const darkColors: Colors = {
26
+ background: "hsla(223.81, 0%, 3.94%, 1)",
27
+ foreground: "hsla(223.81, 0%, 98.03%, 1)",
28
+
29
+ primary: "hsla(223.81, 0%, 89.82%, 1)",
30
+ primaryForeground: "hsla(223.81, 0%, 9.05%, 1)",
31
+
32
+ secondary: "hsla(223.81, 0%, 14.94%, 1)",
33
+ secondaryForeground: "hsla(223.81, 0%, 98.03%, 1)",
34
+
35
+ muted: "hsla(223.81, 0%, 14.94%, 1)",
36
+ mutedForeground: "hsla(223.81, 0%, 63.02%, 1)",
37
+
38
+ border: "hsla(223.81, 0%, 15.51%, 1)",
39
+ surface: "hsla(223.81, 0%, 9.05%, 1)",
40
+
41
+ danger: "hsla(0, 62.8%, 30.6%, 1)",
42
+ success: "hsla(160, 60%, 45%, 1)",
43
+ warning: "hsla(30, 80%, 55%, 1)",
44
+ info: "hsla(220, 70%, 50%, 1)",
45
+ };
@@ -0,0 +1,11 @@
1
+ import { ThemeAssets } from "../types";
2
+ import { darkColors, lightColors } from "./colors";
3
+
4
+ export const defaultThemeAssets: ThemeAssets = {
5
+ colors: {
6
+ light: lightColors,
7
+ dark: darkColors,
8
+ },
9
+ radius: 10,
10
+ fontFamily: "System",
11
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./provider";
2
+ export * from "./types";
@@ -0,0 +1,56 @@
1
+ import { createContext, PropsWithChildren, useContext, useEffect, useState } from "react";
2
+ import { Colors, ColorScheme, Radius, ThemeName } from "./types";
3
+ import { themes } from "./themes";
4
+ import { useColorScheme } from "react-native";
5
+
6
+ interface ThemeContext {
7
+ colors: Colors;
8
+ radius: Radius;
9
+ fontFamily: string;
10
+ colorScheme: ColorScheme;
11
+ setColorScheme: (scheme: ColorScheme) => void;
12
+ setTheme: (themeName: ThemeName) => void;
13
+ themeName: ThemeName;
14
+ }
15
+
16
+ const ThemeContext = createContext<ThemeContext | null>(null);
17
+
18
+ export const ThemeProvider = (props: PropsWithChildren) => {
19
+ const [themeName, setTheme] = useState<ThemeName>("default");
20
+
21
+ const systemColorScheme = useColorScheme();
22
+ const [colorScheme, setColorScheme] = useState<ColorScheme>(systemColorScheme ?? "light");
23
+
24
+ const themesAssets = themes[themeName];
25
+ const colors = themesAssets.colors[colorScheme];
26
+
27
+ useEffect(() => {
28
+ if (systemColorScheme) {
29
+ setColorScheme(systemColorScheme);
30
+ }
31
+ }, [systemColorScheme]);
32
+
33
+ return (
34
+ <ThemeContext.Provider
35
+ value={{
36
+ themeName,
37
+ setTheme,
38
+ colorScheme,
39
+ setColorScheme,
40
+ colors,
41
+ radius: themesAssets.radius,
42
+ fontFamily: themesAssets.fontFamily,
43
+ }}
44
+ >
45
+ {props.children}
46
+ </ThemeContext.Provider>
47
+ );
48
+ };
49
+
50
+ export const useTheme = () => {
51
+ const context = useContext(ThemeContext);
52
+ if (!context) {
53
+ throw new Error("useTheme must be used within a ThemeProvider");
54
+ }
55
+ return context;
56
+ };
@@ -0,0 +1,6 @@
1
+ import { defaultThemeAssets } from "./default";
2
+ import { ThemeAssets, ThemeName } from "./types";
3
+
4
+ export const themes: Record<ThemeName, ThemeAssets> = {
5
+ default: defaultThemeAssets,
6
+ };
@@ -0,0 +1,30 @@
1
+ export type ThemeName = "default";
2
+ export type ColorScheme = "light" | "dark";
3
+
4
+ type Color = `hsla(${number}, ${number}%, ${number}%, ${number})`;
5
+
6
+ export interface Colors {
7
+ background: Color;
8
+ foreground: Color;
9
+ primary: Color;
10
+ primaryForeground: Color;
11
+ secondary: Color;
12
+ secondaryForeground: Color;
13
+ muted: Color;
14
+ mutedForeground: Color;
15
+ border: Color;
16
+ surface: Color;
17
+ success: Color;
18
+ warning: Color;
19
+ danger: Color;
20
+ info: Color;
21
+ }
22
+
23
+ export type Radius = number;
24
+ export type FontFamily = string;
25
+
26
+ export interface ThemeAssets {
27
+ colors: Record<ColorScheme, Colors>;
28
+ radius: Radius;
29
+ fontFamily: FontFamily;
30
+ }
@@ -0,0 +1,10 @@
1
+ export const hslaSetAlpha = (hsla: string, alpha: number): string => {
2
+ const parts = hsla.replace(/^hsla?\(|\s+|\)$/g, "").split(",");
3
+ if (parts.length < 3) {
4
+ throw new Error("Invalid HSLA color format");
5
+ }
6
+ const h = parseInt(parts[0], 10);
7
+ const s = parseInt(parts[1], 10);
8
+ const l = parseInt(parts[2], 10);
9
+ return `hsla(${h}, ${s}%, ${l}%, ${alpha})`;
10
+ };