@geoinsight/react-components 0.1.0 → 0.2.0

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.
@@ -1,3 +1,118 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
2
+
3
+ :root {
4
+ box-sizing: border-box;
5
+ font-family: Poppins;
6
+ font-size: var(--font-size-16);
7
+
8
+ --font-size-10: 10px;
9
+ --font-size-12: 12px;
10
+ --font-size-14: 14px;
11
+ --font-size-16: 16px;
12
+ --font-size-18: 18px;
13
+ --font-size-20: 20px;
14
+ --font-size-24: 24px;
15
+ --font-size-32: 32px;
16
+ --font-size-40: 40px;
17
+
18
+ --spacing-4: 0.25rem;
19
+ --spacing-8: 0.5rem;
20
+ --spacing-16: 1rem;
21
+ --spacing-20: 1.25rem;
22
+ --spacing-24: 1.5rem;
23
+ --spacing-32: 2rem;
24
+ --spacing-40: 2.5rem;
25
+ --spacing-48: 3rem;
26
+ --spacing-64: 4rem;
27
+ --spacing-128: 8rem;
28
+
29
+ --color-black: #000000;
30
+ --color-white: #ffffff;
31
+
32
+ --color-neutral-200: #f2f2f2;
33
+ --color-neutral-300: #e0e0e0;
34
+ --color-neutral-400: #afafaf;
35
+ --color-neutral-500: #818181;
36
+ --color-neutral-600: #6c6c6c;
37
+ --color-neutral-700: #403b3a;
38
+ --color-neutral-800: #201e1d;
39
+
40
+ --color-success: #20e52f;
41
+ --color-danger: #e52f20;
42
+
43
+ --transition-bg-cubic-bezier: background-color 500ms
44
+ cubic-bezier(0.1, 0.2, 0.3, 0.4);
45
+ --transition-color-cubic-bezier: color 500ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
46
+ --transition-text-decoration-cubic-bezier: text-decoration 500ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
47
+ --transition-box-shadow-cubic-bezier: box-shadow 500ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
48
+
49
+
50
+ }
51
+
52
+ [data-theme="dark"] {
53
+ --color-neutral-900: var(--color-black);
54
+ --color-neutral-100: var(--color-white);
55
+ --color-primary: var(--color-primary-500);
56
+ --color-secondary: var(--color-primary-600);
57
+
58
+ color: var(--color-neutral-100) !important;
59
+ background-color: var(--color-neutral-900) !important;
60
+ }
61
+
62
+ [data-theme="light"] {
63
+ --color-neutral-900: var(--color-white);
64
+ --color-neutral-100: var(--color-black);
65
+ --color-primary: var(--color-primary-600);
66
+ --color-secondary: var(--color-primary-500);
67
+ color: var(--color-neutral-900) !important;
68
+ background-color: var(--color-neutral-100) !important;
69
+ }
70
+
71
+ @media (prefers-color-scheme: dark) {
72
+ html {
73
+ color-scheme: dark;
74
+ }
75
+ body {
76
+ color: var(--color-neutral-100);
77
+ background-color: var(--color-neutral-900);
78
+ }
79
+ }
80
+
81
+ [palette-theme="water"] {
82
+ --color-primary-100: #d6efff;
83
+ --color-primary-200: #ade0ff;
84
+ --color-primary-300: #85d0ff;
85
+ --color-primary-400: #5cc0ff;
86
+ --color-primary-500: #33b1ff;
87
+ --color-primary-600: #009eff;
88
+ --color-primary-700: #008ae0;
89
+ --color-primary-800: #0071b8;
90
+ --color-primary-900: #00588f;
91
+ }
92
+
93
+ [palette-theme="earth"]{
94
+ --color-primary-100: #f6e3cb;
95
+ --color-primary-200: #edc897;
96
+ --color-primary-300: #eabe86;
97
+ --color-primary-400: #e3ac63;
98
+ --color-primary-500: #dd9940;
99
+ --color-primary-600: #ca8323;
100
+ --color-primary-700: #ad701f;
101
+ --color-primary-800: #8b5918;
102
+ --color-primary-900: #684312;
103
+ }
104
+
105
+ [palette-theme="forest"] {
106
+ --color-primary-100: #bee8b0;
107
+ --color-primary-200: #a4df90;
108
+ --color-primary-300: #8ad671;
109
+ --color-primary-400: #70cd51;
110
+ --color-primary-500: #54b435;
111
+ --color-primary-600: #4a9e2e;
112
+ --color-primary-700: #3b7e25;
113
+ --color-primary-800: #2c5f1c;
114
+ --color-primary-900: #1e3f12;
115
+ }
1
116
  .button {
2
117
  align-items: center;
3
118
  border-radius: var(--spacing-32);
@@ -86,6 +201,7 @@
86
201
  }
87
202
 
88
203
  .input {
204
+ background-color: var(--color-white);
89
205
  border-radius: var(--spacing-8);
90
206
  border: 2px solid var(--color-primary);
91
207
  color: var(--color-black);
@@ -109,3 +225,58 @@
109
225
  font-size: 12px;
110
226
  color: var(--color-danger);
111
227
  }
228
+
229
+ .textarea {
230
+ margin-bottom: var(--spacing-8);
231
+ position: relative;
232
+ }
233
+
234
+ .textarea__input {
235
+ background-color: var(--color-white);
236
+ box-sizing: border-box;
237
+ border-radius: var(--spacing-8);
238
+ border: 2px solid var(--color-primary);
239
+ cursor: text;
240
+ color: var(--color-black);
241
+ outline: none;
242
+ padding: var(--spacing-8) var(--spacing-16);
243
+ resize: vertical;
244
+ width: 100%;
245
+ }
246
+
247
+ .textarea__input:hover {
248
+ border: 3px solid var(--color-primary-700);
249
+ }
250
+
251
+ .textarea__button {
252
+ background: var(--color-primary);
253
+ border: 2px solid var(--color-secondary);
254
+ box-shadow: 2px 4px 2px 1px rgba(0, 0, 0, 0.25);
255
+ border-radius: 0px 0px var(--spacing-8) var(--spacing-8);
256
+ bottom: 0;
257
+ cursor: pointer;
258
+ left: 0;
259
+ padding-bottom: -20px;
260
+ position: absolute;
261
+ width: 100%;
262
+ }
263
+
264
+ .textarea__button > svg {
265
+ color: var(--color-neutral-100);
266
+ }
267
+
268
+ .textarea__button:disabled {
269
+ cursor: unset;
270
+ opacity: 0.5;
271
+ pointer-events: none;
272
+ }
273
+
274
+ .textarea__button:hover {
275
+ background: var(--color-secondary);
276
+ color: var(--color-neutral-100);
277
+ }
278
+
279
+ .textarea__button--show {
280
+ background: var(--color-primary-700);
281
+ color: var(--color-neutral-100);
282
+ }
package/dist/cjs/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var clsx = require('clsx');
5
+ var react = require('react');
6
+ var tb = require('react-icons/tb');
5
7
 
6
8
  function Button({ children = "Click me", className = "", icon = undefined, isNewWindow = false, mode = "primary", size = "medium", as = "button", ...rest }) {
7
9
  return as === "link" ? (jsxRuntime.jsxs("a", { ...(isNewWindow && { target: "_blank" }), className: clsx(className, "button", mode === "secondary" ? `button button__${mode}` : "button__link", `button__${size}`), ...rest, children: [children, icon] })) : (jsxRuntime.jsxs("button", { className: clsx(className, "button", `button__${mode}`, `button__${size}`), ...rest, children: [children, icon] }));
@@ -11,5 +13,76 @@ function Input({ className = "", classNameGroup = "", errorMessage = "", inputRe
11
13
  return (jsxRuntime.jsxs("div", { className: `input-group ${classNameGroup}`, style: styleGroup, children: [jsxRuntime.jsx("input", { ref: inputRef, className: `input ${errorMessage ? "input--error" : ""} ${className}`, ...rest }), errorMessage && jsxRuntime.jsx("span", { className: "error", children: errorMessage })] }));
12
14
  }
13
15
 
16
+ function TextArea({ className = "", disabled = true, hasToggleButton = true, hideHeight = "5rem", placeholder = "", showHeight = "10rem", style = {}, textareaClassName = "", ...rest }) {
17
+ const ref = react.useRef();
18
+ const [isShow, setIsShow] = react.useState(false);
19
+ const handleClickToggle = () => {
20
+ if (ref && ref.current) {
21
+ if (isShow) {
22
+ ref.current.style.height = hideHeight;
23
+ }
24
+ else {
25
+ ref.current.style.height = showHeight; //(48 + ref.current.scrollHeight) + 'px';
26
+ }
27
+ setIsShow((prev) => !prev);
28
+ }
29
+ };
30
+ return (jsxRuntime.jsxs("div", { className: clsx("textarea", className), style: style, children: [jsxRuntime.jsx("textarea", { ref: ref, className: clsx("textarea__input", textareaClassName), style: {
31
+ height: hasToggleButton ? hideHeight : showHeight,
32
+ }, placeholder: placeholder, disabled: disabled, ...rest }), hasToggleButton && (jsxRuntime.jsx("button", { className: clsx("textarea__button", isShow && "textarea__button--show"), onClick: handleClickToggle, disabled: disabled, children: jsxRuntime.jsx(tb.TbArrowsDiagonal2, { size: "1.5rem" }) }))] }));
33
+ }
34
+
35
+ const ThemeContext = react.createContext(undefined);
36
+ function ThemeProvider({ children }) {
37
+ const [dataTheme, setDataTheme] = react.useState();
38
+ const [paletteTheme, setPaletteTheme] = react.useState();
39
+ react.useEffect(() => {
40
+ if (window.matchMedia) {
41
+ if (localStorage.getItem("data-theme")) {
42
+ document.documentElement.setAttribute("data-theme", localStorage.getItem("data-theme"));
43
+ setDataTheme(localStorage.getItem("data-theme"));
44
+ }
45
+ else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
46
+ document.documentElement.setAttribute("data-theme", "dark");
47
+ setDataTheme("dark");
48
+ }
49
+ }
50
+ }, []);
51
+ const switchDataTheme = (mode) => {
52
+ document.documentElement.setAttribute("data-theme", mode);
53
+ setDataTheme(mode);
54
+ localStorage.setItem("data-theme", mode);
55
+ };
56
+ const switchPaletteTheme = (mode) => {
57
+ document.documentElement.setAttribute("palette-theme", mode);
58
+ setPaletteTheme(mode);
59
+ localStorage.setItem("palette-theme", mode);
60
+ };
61
+ return (jsxRuntime.jsx(ThemeContext.Provider, { value: { dataTheme, switchDataTheme, paletteTheme, switchPaletteTheme }, children: children }));
62
+ }
63
+ function useTheme({ dataTheme = undefined, paletteTheme = "water", } = {}) {
64
+ const context = react.useContext(ThemeContext);
65
+ // only happens if there is an initial mode value.
66
+ react.useEffect(() => {
67
+ if (dataTheme && !localStorage.getItem("data-theme")) {
68
+ context?.switchDataTheme(dataTheme);
69
+ }
70
+ else if (!dataTheme && localStorage.getItem("data-theme")) {
71
+ localStorage.removeItem("data-theme");
72
+ }
73
+ }, []);
74
+ react.useEffect(() => {
75
+ context?.switchPaletteTheme(paletteTheme);
76
+ }, []);
77
+ if (context === undefined) {
78
+ throw new Error("useTheme must be used within a ThemeProvider");
79
+ }
80
+ return context;
81
+ }
82
+
14
83
  exports.Button = Button;
15
84
  exports.Input = Input;
85
+ exports.TextArea = TextArea;
86
+ exports.ThemeContext = ThemeContext;
87
+ exports.ThemeProvider = ThemeProvider;
88
+ exports.useTheme = useTheme;
@@ -1,3 +1,118 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
2
+
3
+ :root {
4
+ box-sizing: border-box;
5
+ font-family: Poppins;
6
+ font-size: var(--font-size-16);
7
+
8
+ --font-size-10: 10px;
9
+ --font-size-12: 12px;
10
+ --font-size-14: 14px;
11
+ --font-size-16: 16px;
12
+ --font-size-18: 18px;
13
+ --font-size-20: 20px;
14
+ --font-size-24: 24px;
15
+ --font-size-32: 32px;
16
+ --font-size-40: 40px;
17
+
18
+ --spacing-4: 0.25rem;
19
+ --spacing-8: 0.5rem;
20
+ --spacing-16: 1rem;
21
+ --spacing-20: 1.25rem;
22
+ --spacing-24: 1.5rem;
23
+ --spacing-32: 2rem;
24
+ --spacing-40: 2.5rem;
25
+ --spacing-48: 3rem;
26
+ --spacing-64: 4rem;
27
+ --spacing-128: 8rem;
28
+
29
+ --color-black: #000000;
30
+ --color-white: #ffffff;
31
+
32
+ --color-neutral-200: #f2f2f2;
33
+ --color-neutral-300: #e0e0e0;
34
+ --color-neutral-400: #afafaf;
35
+ --color-neutral-500: #818181;
36
+ --color-neutral-600: #6c6c6c;
37
+ --color-neutral-700: #403b3a;
38
+ --color-neutral-800: #201e1d;
39
+
40
+ --color-success: #20e52f;
41
+ --color-danger: #e52f20;
42
+
43
+ --transition-bg-cubic-bezier: background-color 500ms
44
+ cubic-bezier(0.1, 0.2, 0.3, 0.4);
45
+ --transition-color-cubic-bezier: color 500ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
46
+ --transition-text-decoration-cubic-bezier: text-decoration 500ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
47
+ --transition-box-shadow-cubic-bezier: box-shadow 500ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
48
+
49
+
50
+ }
51
+
52
+ [data-theme="dark"] {
53
+ --color-neutral-900: var(--color-black);
54
+ --color-neutral-100: var(--color-white);
55
+ --color-primary: var(--color-primary-500);
56
+ --color-secondary: var(--color-primary-600);
57
+
58
+ color: var(--color-neutral-100) !important;
59
+ background-color: var(--color-neutral-900) !important;
60
+ }
61
+
62
+ [data-theme="light"] {
63
+ --color-neutral-900: var(--color-white);
64
+ --color-neutral-100: var(--color-black);
65
+ --color-primary: var(--color-primary-600);
66
+ --color-secondary: var(--color-primary-500);
67
+ color: var(--color-neutral-900) !important;
68
+ background-color: var(--color-neutral-100) !important;
69
+ }
70
+
71
+ @media (prefers-color-scheme: dark) {
72
+ html {
73
+ color-scheme: dark;
74
+ }
75
+ body {
76
+ color: var(--color-neutral-100);
77
+ background-color: var(--color-neutral-900);
78
+ }
79
+ }
80
+
81
+ [palette-theme="water"] {
82
+ --color-primary-100: #d6efff;
83
+ --color-primary-200: #ade0ff;
84
+ --color-primary-300: #85d0ff;
85
+ --color-primary-400: #5cc0ff;
86
+ --color-primary-500: #33b1ff;
87
+ --color-primary-600: #009eff;
88
+ --color-primary-700: #008ae0;
89
+ --color-primary-800: #0071b8;
90
+ --color-primary-900: #00588f;
91
+ }
92
+
93
+ [palette-theme="earth"]{
94
+ --color-primary-100: #f6e3cb;
95
+ --color-primary-200: #edc897;
96
+ --color-primary-300: #eabe86;
97
+ --color-primary-400: #e3ac63;
98
+ --color-primary-500: #dd9940;
99
+ --color-primary-600: #ca8323;
100
+ --color-primary-700: #ad701f;
101
+ --color-primary-800: #8b5918;
102
+ --color-primary-900: #684312;
103
+ }
104
+
105
+ [palette-theme="forest"] {
106
+ --color-primary-100: #bee8b0;
107
+ --color-primary-200: #a4df90;
108
+ --color-primary-300: #8ad671;
109
+ --color-primary-400: #70cd51;
110
+ --color-primary-500: #54b435;
111
+ --color-primary-600: #4a9e2e;
112
+ --color-primary-700: #3b7e25;
113
+ --color-primary-800: #2c5f1c;
114
+ --color-primary-900: #1e3f12;
115
+ }
1
116
  .button {
2
117
  align-items: center;
3
118
  border-radius: var(--spacing-32);
@@ -86,6 +201,7 @@
86
201
  }
87
202
 
88
203
  .input {
204
+ background-color: var(--color-white);
89
205
  border-radius: var(--spacing-8);
90
206
  border: 2px solid var(--color-primary);
91
207
  color: var(--color-black);
@@ -109,3 +225,58 @@
109
225
  font-size: 12px;
110
226
  color: var(--color-danger);
111
227
  }
228
+
229
+ .textarea {
230
+ margin-bottom: var(--spacing-8);
231
+ position: relative;
232
+ }
233
+
234
+ .textarea__input {
235
+ background-color: var(--color-white);
236
+ box-sizing: border-box;
237
+ border-radius: var(--spacing-8);
238
+ border: 2px solid var(--color-primary);
239
+ cursor: text;
240
+ color: var(--color-black);
241
+ outline: none;
242
+ padding: var(--spacing-8) var(--spacing-16);
243
+ resize: vertical;
244
+ width: 100%;
245
+ }
246
+
247
+ .textarea__input:hover {
248
+ border: 3px solid var(--color-primary-700);
249
+ }
250
+
251
+ .textarea__button {
252
+ background: var(--color-primary);
253
+ border: 2px solid var(--color-secondary);
254
+ box-shadow: 2px 4px 2px 1px rgba(0, 0, 0, 0.25);
255
+ border-radius: 0px 0px var(--spacing-8) var(--spacing-8);
256
+ bottom: 0;
257
+ cursor: pointer;
258
+ left: 0;
259
+ padding-bottom: -20px;
260
+ position: absolute;
261
+ width: 100%;
262
+ }
263
+
264
+ .textarea__button > svg {
265
+ color: var(--color-neutral-100);
266
+ }
267
+
268
+ .textarea__button:disabled {
269
+ cursor: unset;
270
+ opacity: 0.5;
271
+ pointer-events: none;
272
+ }
273
+
274
+ .textarea__button:hover {
275
+ background: var(--color-secondary);
276
+ color: var(--color-neutral-100);
277
+ }
278
+
279
+ .textarea__button--show {
280
+ background: var(--color-primary-700);
281
+ color: var(--color-neutral-100);
282
+ }
package/dist/esm/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
3
+ import { useRef, useState, createContext, useEffect, useContext } from 'react';
4
+ import { TbArrowsDiagonal2 } from 'react-icons/tb';
3
5
 
4
6
  function Button({ children = "Click me", className = "", icon = undefined, isNewWindow = false, mode = "primary", size = "medium", as = "button", ...rest }) {
5
7
  return as === "link" ? (jsxs("a", { ...(isNewWindow && { target: "_blank" }), className: clsx(className, "button", mode === "secondary" ? `button button__${mode}` : "button__link", `button__${size}`), ...rest, children: [children, icon] })) : (jsxs("button", { className: clsx(className, "button", `button__${mode}`, `button__${size}`), ...rest, children: [children, icon] }));
@@ -9,4 +11,71 @@ function Input({ className = "", classNameGroup = "", errorMessage = "", inputRe
9
11
  return (jsxs("div", { className: `input-group ${classNameGroup}`, style: styleGroup, children: [jsx("input", { ref: inputRef, className: `input ${errorMessage ? "input--error" : ""} ${className}`, ...rest }), errorMessage && jsx("span", { className: "error", children: errorMessage })] }));
10
12
  }
11
13
 
12
- export { Button, Input };
14
+ function TextArea({ className = "", disabled = true, hasToggleButton = true, hideHeight = "5rem", placeholder = "", showHeight = "10rem", style = {}, textareaClassName = "", ...rest }) {
15
+ const ref = useRef();
16
+ const [isShow, setIsShow] = useState(false);
17
+ const handleClickToggle = () => {
18
+ if (ref && ref.current) {
19
+ if (isShow) {
20
+ ref.current.style.height = hideHeight;
21
+ }
22
+ else {
23
+ ref.current.style.height = showHeight; //(48 + ref.current.scrollHeight) + 'px';
24
+ }
25
+ setIsShow((prev) => !prev);
26
+ }
27
+ };
28
+ return (jsxs("div", { className: clsx("textarea", className), style: style, children: [jsx("textarea", { ref: ref, className: clsx("textarea__input", textareaClassName), style: {
29
+ height: hasToggleButton ? hideHeight : showHeight,
30
+ }, placeholder: placeholder, disabled: disabled, ...rest }), hasToggleButton && (jsx("button", { className: clsx("textarea__button", isShow && "textarea__button--show"), onClick: handleClickToggle, disabled: disabled, children: jsx(TbArrowsDiagonal2, { size: "1.5rem" }) }))] }));
31
+ }
32
+
33
+ const ThemeContext = createContext(undefined);
34
+ function ThemeProvider({ children }) {
35
+ const [dataTheme, setDataTheme] = useState();
36
+ const [paletteTheme, setPaletteTheme] = useState();
37
+ useEffect(() => {
38
+ if (window.matchMedia) {
39
+ if (localStorage.getItem("data-theme")) {
40
+ document.documentElement.setAttribute("data-theme", localStorage.getItem("data-theme"));
41
+ setDataTheme(localStorage.getItem("data-theme"));
42
+ }
43
+ else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
44
+ document.documentElement.setAttribute("data-theme", "dark");
45
+ setDataTheme("dark");
46
+ }
47
+ }
48
+ }, []);
49
+ const switchDataTheme = (mode) => {
50
+ document.documentElement.setAttribute("data-theme", mode);
51
+ setDataTheme(mode);
52
+ localStorage.setItem("data-theme", mode);
53
+ };
54
+ const switchPaletteTheme = (mode) => {
55
+ document.documentElement.setAttribute("palette-theme", mode);
56
+ setPaletteTheme(mode);
57
+ localStorage.setItem("palette-theme", mode);
58
+ };
59
+ return (jsx(ThemeContext.Provider, { value: { dataTheme, switchDataTheme, paletteTheme, switchPaletteTheme }, children: children }));
60
+ }
61
+ function useTheme({ dataTheme = undefined, paletteTheme = "water", } = {}) {
62
+ const context = useContext(ThemeContext);
63
+ // only happens if there is an initial mode value.
64
+ useEffect(() => {
65
+ if (dataTheme && !localStorage.getItem("data-theme")) {
66
+ context?.switchDataTheme(dataTheme);
67
+ }
68
+ else if (!dataTheme && localStorage.getItem("data-theme")) {
69
+ localStorage.removeItem("data-theme");
70
+ }
71
+ }, []);
72
+ useEffect(() => {
73
+ context?.switchPaletteTheme(paletteTheme);
74
+ }, []);
75
+ if (context === undefined) {
76
+ throw new Error("useTheme must be used within a ThemeProvider");
77
+ }
78
+ return context;
79
+ }
80
+
81
+ export { Button, Input, TextArea, ThemeContext, ThemeProvider, useTheme };
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@geoinsight/react-components",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "This library is the main UI component library for geoinsight",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
7
+ "types": "dist/types/index.d.ts",
7
8
  "author": "Geoinsight",
8
9
  "license": "MIT",
9
10
  "scripts": {
@@ -5,6 +5,7 @@
5
5
  }
6
6
 
7
7
  .input {
8
+ background-color: var(--color-white);
8
9
  border-radius: var(--spacing-8);
9
10
  border: 2px solid var(--color-primary);
10
11
  color: var(--color-black);
@@ -0,0 +1,8 @@
1
+ .loading {
2
+ align-items: center;
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 100%;
6
+ justify-content: center;;
7
+ position: relative;
8
+ }
@@ -0,0 +1,20 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+
3
+ import { Loading } from "./index";
4
+ import { withColorScheme } from "../../decorators/withColorScheme";
5
+
6
+ const meta: Meta<typeof Loading> = {
7
+ title: "Example/Loading",
8
+ component: Loading,
9
+ decorators: [withColorScheme]
10
+ };
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof Loading>;
15
+
16
+ export const Primary: Story = {
17
+ args: {
18
+ img: ""
19
+ },
20
+ };
@@ -0,0 +1,14 @@
1
+ import { Loading as LoadingProps } from "./index.types";
2
+ import "./index.css";
3
+ import loading from "../../stories/assets/loading.gif";
4
+
5
+ export function Loading({ img, children }: LoadingProps) {
6
+ return (
7
+ <div className="loading">
8
+ <img src={img ? img : loading} />
9
+ {children}
10
+ </div>
11
+ );
12
+ }
13
+
14
+ export default Loading;
@@ -0,0 +1,12 @@
1
+ import React, { CSSProperties, Ref } from "react";
2
+
3
+ export interface Loading {
4
+ /**
5
+ * Image or gif used
6
+ */
7
+ img?: string;
8
+ /**
9
+ * Message that its sent
10
+ */
11
+ children?: React.ReactNode;
12
+ }
@@ -4,6 +4,7 @@
4
4
  }
5
5
 
6
6
  .textarea__input {
7
+ background-color: var(--color-white);
7
8
  box-sizing: border-box;
8
9
  border-radius: var(--spacing-8);
9
10
  border: 2px solid var(--color-primary);
@@ -0,0 +1,37 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+
3
+ import { LoadingProvider } from ".";
4
+ import { withColorScheme } from "../../decorators/withColorScheme";
5
+ import { withLoading } from "../../decorators/withLoading";
6
+ import spinner from "../../stories/assets/spinner_2.png";
7
+
8
+ const meta: Meta<typeof LoadingProvider> = {
9
+ title: "Context/Loading",
10
+ component: () => <div></div>,
11
+ decorators: [withColorScheme, withLoading],
12
+ };
13
+
14
+ export default meta;
15
+
16
+ type Story = StoryObj<typeof LoadingProvider>;
17
+
18
+ export const Basic: Story = {};
19
+
20
+ export const CustomMessage: Story = {
21
+ parameters: {
22
+ loadingOptions: {
23
+ message: "Updating form",
24
+ },
25
+ },
26
+ };
27
+
28
+ export const CustomImage: Story = {
29
+ parameters: {
30
+ loadingOptions: {
31
+ isRef: true,
32
+ },
33
+ },
34
+ args: {
35
+ img: spinner,
36
+ },
37
+ };
@@ -0,0 +1,111 @@
1
+ import React, {
2
+ ReactNode,
3
+ RefObject,
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useReducer,
8
+ } from "react";
9
+ import Loading from "../../components/loading";
10
+
11
+ type Action = {
12
+ type: "set_ref" | "start_loading" | "stop_loading" | "set_message";
13
+ ref?: RefObject<ReactNode>;
14
+ message?: string;
15
+ };
16
+ type State = {
17
+ isLoading: boolean;
18
+ message?: string | React.ReactNode;
19
+ ref?: RefObject<ReactNode>;
20
+ };
21
+ type Context = {
22
+ state: State;
23
+ setRef: (ref: RefObject<ReactNode>) => void;
24
+ setMessage: (message: string) => void;
25
+ startLoading: () => void;
26
+ stopLoading: () => void;
27
+ };
28
+ type LoadingProviderProps = { children: React.ReactNode };
29
+ type UseLoadingProps = {
30
+ ref?: RefObject<ReactNode>;
31
+ message?: string;
32
+ };
33
+
34
+ const LoadingContext = createContext<Context | undefined>(undefined);
35
+
36
+ function loadingReducer(state: State, action: Action) {
37
+ switch (action.type) {
38
+ case "set_ref": {
39
+ return { ...state, ref: action.ref };
40
+ }
41
+ case "start_loading": {
42
+ return { ...state, isLoading: true };
43
+ }
44
+ case "stop_loading": {
45
+ return { ...state, isLoading: false };
46
+ }
47
+ case "set_message": {
48
+ return { ...state, message: action.message };
49
+ }
50
+ default: {
51
+ throw new Error(`Unhandled action type: ${action.type}`);
52
+ }
53
+ }
54
+ }
55
+
56
+ function LoadingProvider({ children }: LoadingProviderProps) {
57
+ const [{ isLoading, message, ref }, dispatch] = useReducer(loadingReducer, {
58
+ isLoading: false,
59
+ message: "Loading",
60
+ ref: undefined,
61
+ });
62
+
63
+ const setRef = (ref: RefObject<ReactNode>) => {
64
+ dispatch({ type: "set_ref", ref });
65
+ };
66
+
67
+ const setMessage = (message: string) => {
68
+ dispatch({ type: "set_message", message });
69
+ };
70
+
71
+ const startLoading = () => {
72
+ dispatch({ type: "start_loading" });
73
+ };
74
+
75
+ const stopLoading = () => {
76
+ dispatch({ type: "stop_loading" });
77
+ };
78
+
79
+ return (
80
+ <LoadingContext.Provider
81
+ value={{
82
+ state: { ref, message, isLoading },
83
+ setMessage,
84
+ setRef,
85
+ startLoading,
86
+ stopLoading,
87
+ }}
88
+ >
89
+ {!ref && <Loading>{message}</Loading>}
90
+ {children}
91
+ </LoadingContext.Provider>
92
+ );
93
+ }
94
+
95
+ function useLoading({ ref, message }: UseLoadingProps = {}) {
96
+ const context = useContext(LoadingContext);
97
+
98
+ // only happens if there is an initial mode value.
99
+ useEffect(() => {
100
+ ref && context?.setRef(ref);
101
+ message && context?.setMessage(message);
102
+ }, []);
103
+
104
+ if (context === undefined) {
105
+ throw new Error("useLoading must be used within a LoadingProvider");
106
+ }
107
+
108
+ return context;
109
+ }
110
+
111
+ export { LoadingContext, LoadingProvider, useLoading };
@@ -0,0 +1,93 @@
1
+ import { PaletteTheme } from "palette-theme";
2
+ import React, {
3
+ Dispatch,
4
+ SetStateAction,
5
+ createContext,
6
+ useContext,
7
+ useEffect,
8
+ useState,
9
+ } from "react";
10
+
11
+ type DataTheme = "light" | "dark";
12
+
13
+ type State = {
14
+ dataTheme?: DataTheme;
15
+ switchDataTheme: (mode: DataTheme) => void;
16
+ paletteTheme?: PaletteTheme;
17
+ switchPaletteTheme: (mode: PaletteTheme) => void;
18
+ };
19
+ type ThemeProviderProps = { children: React.ReactNode };
20
+ type UseThemeProps = {
21
+ dataTheme?: DataTheme;
22
+ paletteTheme?: PaletteTheme;
23
+ };
24
+
25
+ const ThemeContext = createContext<State | undefined>(undefined);
26
+
27
+ function ThemeProvider({ children }: ThemeProviderProps) {
28
+ const [dataTheme, setDataTheme] = useState<DataTheme>();
29
+ const [paletteTheme, setPaletteTheme] = useState<PaletteTheme>();
30
+
31
+ useEffect(() => {
32
+ if (window.matchMedia) {
33
+ if (localStorage.getItem("data-theme")) {
34
+ document.documentElement.setAttribute(
35
+ "data-theme",
36
+ localStorage.getItem("data-theme") as string
37
+ );
38
+ setDataTheme(localStorage.getItem("data-theme") as DataTheme);
39
+ } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
40
+ document.documentElement.setAttribute("data-theme", "dark");
41
+ setDataTheme("dark");
42
+ }
43
+ }
44
+ }, []);
45
+
46
+ const switchDataTheme = (mode: DataTheme) => {
47
+ document.documentElement.setAttribute("data-theme", mode);
48
+ setDataTheme(mode);
49
+ localStorage.setItem("data-theme", mode);
50
+ };
51
+
52
+ const switchPaletteTheme = (mode: PaletteTheme) => {
53
+ document.documentElement.setAttribute("palette-theme", mode);
54
+ setPaletteTheme(mode);
55
+ localStorage.setItem("palette-theme", mode);
56
+ };
57
+
58
+ return (
59
+ <ThemeContext.Provider
60
+ value={{ dataTheme, switchDataTheme, paletteTheme, switchPaletteTheme }}
61
+ >
62
+ {children}
63
+ </ThemeContext.Provider>
64
+ );
65
+ }
66
+
67
+ function useTheme({
68
+ dataTheme = undefined,
69
+ paletteTheme = "water",
70
+ }: UseThemeProps = {}) {
71
+ const context = useContext(ThemeContext);
72
+
73
+ // only happens if there is an initial mode value.
74
+ useEffect(() => {
75
+ if (dataTheme && !localStorage.getItem("data-theme")) {
76
+ context?.switchDataTheme(dataTheme);
77
+ } else if (!dataTheme && localStorage.getItem("data-theme")) {
78
+ localStorage.removeItem("data-theme");
79
+ }
80
+ }, []);
81
+
82
+ useEffect(() => {
83
+ context?.switchPaletteTheme(paletteTheme);
84
+ }, []);
85
+
86
+ if (context === undefined) {
87
+ throw new Error("useTheme must be used within a ThemeProvider");
88
+ }
89
+
90
+ return context;
91
+ }
92
+
93
+ export { ThemeContext, ThemeProvider, useTheme };
@@ -1,34 +1,29 @@
1
1
  import { StoryContext, StoryFn } from "@storybook/react";
2
+ import { ThemeProvider, useTheme } from "../context/themeContext";
3
+ import "../styles/variables.css";
4
+ import { useEffect } from "react";
2
5
 
3
6
  export const withColorScheme = (Story: StoryFn, context: StoryContext) => {
7
+ return (
8
+ <ThemeProvider>
9
+ <Wrapped Story={Story} context={context} />
10
+ </ThemeProvider>
11
+ );
12
+ };
13
+
14
+ export const Wrapped = ({ Story, context } : { Story: StoryFn, context: StoryContext}) => {
4
15
  const { theme, palette } = context.globals;
5
- if (theme === "light") {
6
- return (
7
- <div
8
- data-theme="light"
9
- palette-theme={palette}
10
- style={{
11
- background: "var(--color-white)",
12
- height: "150px",
13
- padding: "16px",
14
- }}
15
- >
16
- <Story />
17
- </div>
18
- );
19
- }
16
+ const switchTheme = useTheme();
17
+
18
+ useEffect (()=> {
19
+ switchTheme.switchDataTheme(theme)
20
+ }, [theme]);
21
+
22
+ useEffect (()=> {
23
+ switchTheme.switchPaletteTheme(palette)
24
+ }, [palette]);
20
25
 
21
26
  return (
22
- <div
23
- data-theme="dark"
24
- palette-theme={palette}
25
- style={{
26
- background: "var(--color-black)",
27
- height: "150px",
28
- padding: "16px",
29
- }}
30
- >
31
27
  <Story />
32
- </div>
33
28
  );
34
29
  };
@@ -0,0 +1,31 @@
1
+ import { useRef } from "react";
2
+ import { StoryContext, StoryFn } from "@storybook/react";
3
+ import { LoadingProvider, useLoading } from "../context/loading";
4
+
5
+ const LoadingProviderMock = ({ Story, loadingOptions, loadingArgs }) => {
6
+ const ref = useRef();
7
+ const loading = useLoading({
8
+ ...(loadingOptions?.isRef && { ref: ref }),
9
+ ...(loadingOptions?.message && { message: loadingOptions.message }),
10
+ });
11
+
12
+ // initialize stuff here
13
+ return (
14
+ <>
15
+ {loading.state.ref && <img ref={ref} src={loadingArgs.img} />}
16
+ <Story />
17
+ </>
18
+ );
19
+ };
20
+
21
+ export const withLoading = (Story: StoryFn, context: StoryContext) => {
22
+ return (
23
+ <LoadingProvider>
24
+ <LoadingProviderMock
25
+ Story={Story}
26
+ loadingOptions={context.parameters.loadingOptions}
27
+ loadingArgs={context.args}
28
+ />
29
+ </LoadingProvider>
30
+ );
31
+ };
@@ -0,0 +1,9 @@
1
+ import { StoryContext, StoryFn } from "@storybook/react";
2
+
3
+ export const withWrapper = (Story: StoryFn, context: StoryContext) => {
4
+ return (
5
+ <div style={{height: "300px"}}>
6
+ <Story />
7
+ </div>
8
+ );
9
+ };
package/src/index.ts CHANGED
@@ -1,2 +1,7 @@
1
+ import "../src/styles/variables.css";
2
+
1
3
  export { Button } from "./components/button";
2
4
  export { Input } from "./components/input";
5
+ export { TextArea } from "./components/text-area";
6
+
7
+ export { ThemeContext, ThemeProvider, useTheme } from "./context/themeContext";
Binary file
Binary file
@@ -54,6 +54,9 @@
54
54
  --color-neutral-100: var(--color-white);
55
55
  --color-primary: var(--color-primary-500);
56
56
  --color-secondary: var(--color-primary-600);
57
+
58
+ color: var(--color-neutral-100) !important;
59
+ background-color: var(--color-neutral-900) !important;
57
60
  }
58
61
 
59
62
  [data-theme="light"] {
@@ -61,6 +64,18 @@
61
64
  --color-neutral-100: var(--color-black);
62
65
  --color-primary: var(--color-primary-600);
63
66
  --color-secondary: var(--color-primary-500);
67
+ color: var(--color-neutral-900) !important;
68
+ background-color: var(--color-neutral-100) !important;
69
+ }
70
+
71
+ @media (prefers-color-scheme: dark) {
72
+ html {
73
+ color-scheme: dark;
74
+ }
75
+ body {
76
+ color: var(--color-neutral-100);
77
+ background-color: var(--color-neutral-900);
78
+ }
64
79
  }
65
80
 
66
81
  [palette-theme="water"] {
@@ -0,0 +1,5 @@
1
+ declare module "palette-theme" {
2
+ import { themes } from "../utils/themes";
3
+
4
+ export type PaletteTheme = typeof themes[number];
5
+ }
@@ -1,5 +0,0 @@
1
- declare module "data-theme" {
2
- import { themes } from "../utils/themes";
3
-
4
- export type dataTheme = typeof themes[number];
5
- }