kahuna-base-react-components 0.2.19 → 0.2.21

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.
package/dist/types.d.ts CHANGED
@@ -94,7 +94,7 @@ interface KInputProps {
94
94
  iconSize?: string;
95
95
  checked?: boolean;
96
96
  }
97
- declare const KInput: React.FC<KInputProps>;
97
+ declare const KInput$1: React.FC<KInputProps>;
98
98
 
99
99
  interface KSelectOption {
100
100
  label: string;
@@ -184,4 +184,38 @@ interface SliderLabelProps {
184
184
  }
185
185
  declare const KSliderLabel: React.FC<SliderLabelProps>;
186
186
 
187
- export { KButton, KDropdown, KInput, KLogo, KSelectDate, KSlider, KSliderLabel, KSpan, KTitleSpan, KTooltip };
187
+ interface KCodeInputProps {
188
+ onChange: (value: string) => void;
189
+ length?: number;
190
+ borderRadius?: number;
191
+ disabled?: boolean;
192
+ padding?: string;
193
+ gap?: number;
194
+ fontSize?: string;
195
+ fontWeight?: string;
196
+ color?: string;
197
+ lineHeight?: string;
198
+ allowedCharacters?: "numeric" | "alphaNumeric" | "alpha";
199
+ width?: number;
200
+ height?: number;
201
+ autoFocus?: boolean;
202
+ isPassword?: boolean;
203
+ background?: string;
204
+ hoverBackground?: string;
205
+ focusedBackground?: string;
206
+ filledBackground?: string;
207
+ border?: string;
208
+ hoverBorder?: string;
209
+ focusedBorder?: string;
210
+ filledBorder?: string;
211
+ boxShadow?: string;
212
+ hoverBoxShadow?: string;
213
+ focusedBoxShadow?: string;
214
+ filledBoxShadow?: string;
215
+ fitInContainer?: boolean;
216
+ isCodeCorrect?: boolean;
217
+ autoBlur?: boolean;
218
+ }
219
+ declare const KInput: React.FC<KCodeInputProps>;
220
+
221
+ export { KButton, KInput as KCodeInput, KDropdown, KInput$1 as KInput, KLogo, KSelectDate, KSlider, KSliderLabel, KSpan, KTitleSpan, KTooltip };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kahuna-base-react-components",
3
- "version": "0.2.19",
3
+ "version": "0.2.21",
4
4
  "description": "Kahuna Base React Components",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -0,0 +1,84 @@
1
+ import { Meta, StoryFn } from "@storybook/react"
2
+ import KCodeInput, { KCodeInputProps } from "./KCodeInput"
3
+ // @ts-ignore
4
+ import { useEffect, useState } from "react"
5
+
6
+ export default {
7
+ title: "ReactComponentLibrary/KCodeInput",
8
+ component: KCodeInput,
9
+ parameters: {
10
+ layout: "centered"
11
+ }
12
+ } as Meta<typeof KCodeInput>
13
+
14
+ const KCodeInputWrapper: React.FC<KCodeInputProps> = (args) => {
15
+ const [code, setCode] = useState<string>("");
16
+
17
+ useEffect(() => {
18
+ console.log("code: ", code);
19
+ }, [code]);
20
+
21
+ return (
22
+ <div className="w-[300px] p-4">
23
+ <KCodeInput
24
+ {...args}
25
+ onChange={(value:string) => {
26
+ //console.log("value: ", value);
27
+ setCode(value);
28
+ //console.log("Value updated to: ", option.value);
29
+ }}
30
+ />
31
+ </div>
32
+ );
33
+ };
34
+
35
+
36
+ const Template: StoryFn<typeof KCodeInput> = (args) => <KCodeInputWrapper {...args} />
37
+
38
+ export const KCodeInputPrimary = Template.bind({})
39
+ KCodeInputPrimary.args = {
40
+ length: 6,
41
+ allowedCharacters: 'alpha',
42
+ isPassword: false,
43
+ disabled: false,
44
+ autoFocus:true,
45
+ gap:2,
46
+ width: 150,
47
+ background: "red",
48
+ hoverBackground: "green",
49
+ focusedBackground: "blue",
50
+ filledBackground: "black",
51
+ fitInContainer: true,
52
+ border: "1px solid black",
53
+ hoverBorder: "1px solid yellow",
54
+ focusedBorder: "1px solid white",
55
+ filledBorder: "1px solid gray",
56
+ fontSize: "15px",
57
+ padding: "10px",
58
+ color: "white",
59
+ }
60
+
61
+ export const KCodeInputSecondary = Template.bind({})
62
+ KCodeInputSecondary.args = {
63
+ isCodeCorrect: true,
64
+ boxShadow: "1px 2px 10px black",
65
+ hoverBoxShadow: "1px 2px 10px red",
66
+ focusedBoxShadow: "1px 2px 10px yellow",
67
+ filledBoxShadow: "1px 2px 10px purple",
68
+ fontSize: "30px",
69
+ padding: "10px",
70
+ color: "orange",
71
+ autoBlur: true,
72
+ autoFocus: true,
73
+ width: 50,
74
+ height: 90,
75
+ fitInContainer: true,
76
+ gap: 12
77
+ }
78
+
79
+ export const KCodeInputDefault = Template.bind({})
80
+ KCodeInputDefault.args = {
81
+ isCodeCorrect: false,
82
+ allowedCharacters:"numeric",
83
+ autoBlur:true
84
+ }
@@ -0,0 +1,260 @@
1
+ import React, { useEffect, useState, KeyboardEvent, useRef } from "react"
2
+ import "../../main.css"
3
+
4
+ export interface KCodeInputProps {
5
+ onChange: (value: string) => void
6
+ length?: number
7
+ borderRadius?: number
8
+ disabled?: boolean
9
+ padding?: string
10
+ gap?: number
11
+ fontSize?: string
12
+ fontWeight?: string
13
+ color?: string
14
+ lineHeight?: string
15
+ allowedCharacters?: "numeric" | "alphaNumeric" | "alpha"
16
+ width?: number
17
+ height?: number
18
+ autoFocus?: boolean
19
+ isPassword?: boolean
20
+ background?: string
21
+ hoverBackground?: string
22
+ focusedBackground?: string
23
+ filledBackground?: string
24
+ border?: string
25
+ hoverBorder?: string
26
+ focusedBorder?: string
27
+ filledBorder?: string
28
+ boxShadow?: string
29
+ hoverBoxShadow?: string
30
+ focusedBoxShadow?: string
31
+ filledBoxShadow?: string
32
+ fitInContainer?: boolean
33
+ isCodeCorrect?: boolean
34
+ autoBlur?: boolean
35
+ }
36
+
37
+ const KInput: React.FC<KCodeInputProps> = (props) => {
38
+ const autoFocus = props.autoFocus || false
39
+ const autoBlur = props.autoBlur || false
40
+ const borderRadius = props.borderRadius || 10
41
+ const disabled = props.disabled || false
42
+ const length = props.length || 6
43
+ const padding = props.padding || "6px"
44
+ const gap = props.gap || 6
45
+ const allowedCharacters = props.allowedCharacters || "numeric"
46
+ const password = props.isPassword || false
47
+ const fitInContainer = props.fitInContainer || false
48
+ const width = props.width || "60px"
49
+ const height = props.height || "60px"
50
+ const fontSize = props.fontSize || "24px"
51
+ const fontWeight = props.fontWeight || 500
52
+ const lineHeight = props.lineHeight || "32px"
53
+ const defaultBorder = props.border || ""
54
+ const hoverBorder = props.hoverBorder || "1px solid #F3F3F3"
55
+ const focusedBorder = props.focusedBorder || "1px solid #F3F3F3"
56
+ const filledBorder = props.filledBorder || "1px solid #B7B7B7"
57
+ const defaultBoxShadow = props.boxShadow || ""
58
+ const hoverBoxShadow = props.hoverBoxShadow || ""
59
+ const focusedBoxShadow = props.focusedBoxShadow || " 0px 1px 2px 0px rgba(228, 229, 231, 0.24)"
60
+ const filledBoxShadow = props.filledBoxShadow || " 0px 1px 2px 0px rgba(228, 229, 231, 0.24)"
61
+ const defaultBackground = props.background || "#F5F5F5"
62
+ const hoverBackground = props.hoverBackground || defaultBackground
63
+ const focusedBackground = props.focusedBackground || "#FFF"
64
+ const filledBackground = props.filledBackground || "#FFF"
65
+ const color = props.color || "#000"
66
+ const isCodeCorrect = props.isCodeCorrect !== undefined ? props.isCodeCorrect : true
67
+
68
+ const [focusedIndex, setFocusedIndex] = useState<number>(autoFocus ? 0 : -1)
69
+ const inputRefs = useRef<HTMLInputElement[]>([])
70
+
71
+ const [allCharactersWritten, setAllCharactersWritten] = useState<boolean>(false)
72
+
73
+ const [values, setValues] = useState<string[]>(Array(length).fill(""))
74
+ const [hoveredIndexes, setHoveredIndexes] = useState<boolean[]>(Array(length).fill(false))
75
+ const [focusedIndexes, setFocusedIndexes] = useState<boolean[]>(Array(length).fill(false))
76
+
77
+ const handleMouseEnter = (index: number) => {
78
+ setHoveredIndexes((prev) => prev.map((hovered, i) => (i === index ? true : hovered)))
79
+ }
80
+
81
+ const handleMouseLeave = (index: number) => {
82
+ setHoveredIndexes((prev) => prev.map((hovered, i) => (i === index ? false : hovered)))
83
+ }
84
+
85
+ const handleFocus = (index: number) => {
86
+ setFocusedIndexes((prev) => prev.map((focused, i) => (i === index ? true : focused)))
87
+ }
88
+ const handleBlur = (index: number) => {
89
+ setFocusedIndexes((prev) => prev.map((focused, i) => (i === index ? false : focused)))
90
+ }
91
+
92
+ useEffect(() => {
93
+ if (inputRefs.current[focusedIndex]) {
94
+ inputRefs.current[focusedIndex].focus()
95
+ }
96
+ }, [focusedIndex])
97
+
98
+ useEffect(() => {
99
+ if (disabled) {
100
+ setFocusedIndexes((prev) => prev.map((focused, i) => false))
101
+ setHoveredIndexes((prev) => prev.map((hovered, i) => false))
102
+ setValues(Array(length).fill(""))
103
+ setAllCharactersWritten(false)
104
+ }
105
+ }, [disabled])
106
+
107
+ const handleClick = (index: number) => {
108
+ if (values[index]) {
109
+ setFocusedIndex(index)
110
+ } else if (!values[index]) {
111
+ const firstEmptyInputIndex = values.findIndex((value) => value === "")
112
+ setFocusedIndex(firstEmptyInputIndex)
113
+ if (inputRefs.current[focusedIndex]) {
114
+ inputRefs.current[focusedIndex].focus()
115
+ }
116
+ }
117
+ }
118
+
119
+ const handleChange = (text: string, index: number) => {
120
+ const patterns: Record<string, RegExp> = {
121
+ numeric: /^\d*$/,
122
+ alpha: /^[a-zA-Z]*$/,
123
+ alphaNumeric: /^[a-zA-Z0-9]*$/
124
+ }
125
+
126
+ if (patterns[allowedCharacters]?.test(text)) {
127
+ const newValues = [...values]
128
+ if (text.length === 1) {
129
+ newValues[index] = text
130
+ } else if (text.length === 2) {
131
+ newValues[index] = newValues[index] === text[0] ? text[1] : text[0]
132
+ }
133
+ setValues(newValues)
134
+ if (text && index < length - 1) {
135
+ setFocusedIndex(index + 1)
136
+ }
137
+ }
138
+ }
139
+
140
+ const handleDelete = (event: React.KeyboardEvent<HTMLInputElement>, index: number) => {
141
+ if (event?.key === "Backspace") {
142
+ const newValues = [...values]
143
+ newValues[index] = ""
144
+ if (index > 0) {
145
+ setFocusedIndex(index - 1)
146
+ }
147
+ setValues(newValues)
148
+ }
149
+ }
150
+
151
+ const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
152
+ event.preventDefault()
153
+ const pastedText = event.clipboardData.getData("text").replace(/\s+/g, "")
154
+ const patterns: Record<string, RegExp> = {
155
+ numeric: /^\d*$/,
156
+ alpha: /^[a-zA-Z]*$/,
157
+ alphaNumeric: /^[a-zA-Z0-9]*$/
158
+ }
159
+ if (patterns[allowedCharacters]?.test(pastedText) && pastedText.length > 0) {
160
+ const newValues = [...values]
161
+ const currentIndex = focusedIndex
162
+ const pastedCharacters = pastedText.includes(" ") ? pastedText.split(" ") : pastedText.split("")
163
+ pastedCharacters.forEach((character, index) => {
164
+ const i = currentIndex + index
165
+ if (i < length) {
166
+ newValues[i] = character
167
+ }
168
+ })
169
+ setValues(newValues)
170
+ setFocusedIndex(
171
+ pastedCharacters.length + currentIndex >= length ? length - 1 : pastedCharacters.length + currentIndex
172
+ )
173
+ }
174
+ }
175
+
176
+ useEffect(() => {
177
+ const allDone = values.every((value) => value.length > 0)
178
+ setAllCharactersWritten(allDone)
179
+ props.onChange(values.join(""))
180
+ }, [values])
181
+
182
+ useEffect(() => {
183
+ if (allCharactersWritten && autoBlur) {
184
+ inputRefs.current[focusedIndex].blur()
185
+ }
186
+ }, [allCharactersWritten])
187
+
188
+ const renderCharacterComponent = (index: number) => {
189
+ const isHovered = hoveredIndexes[index]
190
+ const isFocused = focusedIndexes[index]
191
+ const isFilled = values[index]
192
+
193
+ const background = isFilled
194
+ ? filledBackground
195
+ : isFocused
196
+ ? focusedBackground
197
+ : isHovered
198
+ ? hoverBackground
199
+ : defaultBackground
200
+
201
+ const boxShadow = isFilled
202
+ ? filledBoxShadow
203
+ : isFocused
204
+ ? focusedBoxShadow
205
+ : isHovered
206
+ ? hoverBoxShadow
207
+ : defaultBoxShadow
208
+
209
+ const border = isFilled ? filledBorder : isFocused ? focusedBorder : isHovered ? hoverBorder : defaultBorder
210
+
211
+ return (
212
+ <input
213
+ key={`k-code-input-${index}`}
214
+ value={values[index]}
215
+ className={`k-code-input-character-container`}
216
+ style={{
217
+ padding,
218
+ background,
219
+ borderRadius,
220
+ height,
221
+ border: allCharactersWritten && !isCodeCorrect ? "1px solid #FF5865" : border,
222
+ boxShadow,
223
+ fontSize,
224
+ fontWeight,
225
+ lineHeight,
226
+ color,
227
+ width: !fitInContainer ? width : `calc((100% - ${(length - 1) * gap}px) / ${length})`
228
+ }}
229
+ required
230
+ type={password ? "password" : "text"}
231
+ onChange={(event) => {
232
+ handleChange(event.target.value, index)
233
+ }}
234
+ onClick={(event) => {
235
+ handleClick(index)
236
+ }}
237
+ onKeyDown={(event) => {
238
+ handleDelete(event, index)
239
+ }}
240
+ onPaste={(event) => {
241
+ handlePaste(event)
242
+ }}
243
+ ref={(el: HTMLInputElement) => (inputRefs.current[index] = el)}
244
+ disabled={disabled}
245
+ onMouseEnter={() => handleMouseEnter(index)}
246
+ onMouseLeave={() => handleMouseLeave(index)}
247
+ onFocus={() => handleFocus(index)}
248
+ onBlur={() => handleBlur(index)}
249
+ />
250
+ )
251
+ }
252
+
253
+ return (
254
+ <div className="flex flex-row items-center justify-between" style={{ width: "100%", gap }}>
255
+ {Array.from({ length }, (_, index) => renderCharacterComponent(index))}
256
+ </div>
257
+ )
258
+ }
259
+
260
+ export default KInput
@@ -0,0 +1 @@
1
+ export {default} from './KCodeInput';
@@ -0,0 +1,65 @@
1
+ import { Meta, StoryFn } from "@storybook/react"
2
+ import KTextArea from "./KTextArea"
3
+ // @ts-ignore
4
+ import TracksIcon from "../../assets/tracks.svg"
5
+ import { KeyboardEvent } from "react"
6
+
7
+ export default {
8
+ title: "ReactComponentLibrary/KTextArea",
9
+ component: KTextArea,
10
+ parameters: {
11
+ layout: "centered"
12
+ }
13
+ } as Meta<typeof KTextArea>
14
+
15
+ const Template: StoryFn<typeof KTextArea> = (args) => <KTextArea {...args} />
16
+
17
+
18
+ export const KTextAreaPrimary = Template.bind({})
19
+ KTextAreaPrimary.args = {
20
+ onChange: (value: string) => {
21
+ console.log("value:", value)
22
+ },
23
+ onKeyDown: (event: KeyboardEvent) => {
24
+ if (event.key === "Enter") {
25
+ console.log("Enter is clicked and our value is:", event.currentTarget)
26
+ }
27
+ },
28
+ rows: 4,
29
+ placeholder: "Placeholder...",
30
+ hoverBackground: "white"
31
+ }
32
+
33
+ export const KTextAreaLeftIcon = Template.bind({})
34
+ KTextAreaLeftIcon.args = {
35
+ onChange: (value: string) => {},
36
+ placeholder: "Placeholder...",
37
+ leftIcon: TracksIcon,
38
+ leftIconClick: () => {
39
+ alert("left icon clicked")
40
+ }
41
+ }
42
+
43
+ export const KTextAreaRightIcon = Template.bind({})
44
+ KTextAreaRightIcon.args = {
45
+ onChange: (value: string) => {},
46
+ placeholder: "Placeholder...",
47
+ rightIcon: TracksIcon,
48
+ rightIconClick: () => {
49
+ alert("right icon clicked")
50
+ }
51
+ }
52
+
53
+ export const KTextAreaLeftRightIcon = Template.bind({})
54
+ KTextAreaLeftRightIcon.args = {
55
+ onChange: (value: string) => {},
56
+ placeholder: "Placeholder...",
57
+ leftIcon: TracksIcon,
58
+ rightIcon: TracksIcon,
59
+ leftIconClick: () => {
60
+ alert("left icon clicked")
61
+ },
62
+ rightIconClick: () => {
63
+ alert("right icon clicked")
64
+ }
65
+ }
@@ -0,0 +1,130 @@
1
+ import React, { useEffect, useState, KeyboardEvent } from "react"
2
+ import "../../main.css"
3
+
4
+ export interface KTextAreaProps {
5
+ value: string
6
+ onChange: (value: string) => void
7
+ rows?: number
8
+ onBlur?: (value: string) => void
9
+ onKeyDown?: (event: KeyboardEvent) => void
10
+ width?: number
11
+ height?: number
12
+ leftIcon?: string
13
+ rightIcon?: string
14
+ background?: string
15
+ activeBackground?: string
16
+ borderRadius?: number
17
+ disabled?: boolean
18
+ placeholder?: string
19
+ shadowDisabled?: boolean
20
+ leftIconClick?: () => void
21
+ rightIconClick?: () => void
22
+ accentColor?: string
23
+ hoverBackground?: string
24
+ padding?: string
25
+ gap?: string
26
+ border?: string
27
+ boxShadow?: string
28
+ fontSize?: string
29
+ iconSize?: string
30
+ checked?: boolean
31
+ }
32
+
33
+ const KTextArea: React.FC<KTextAreaProps> = (props) => {
34
+ const [background, setBackground] = useState("#F5F5F5")
35
+ const [hover, setHover] = useState(false)
36
+
37
+ useEffect(() => {
38
+ const emptyBackground = props.background || "#F5F5F5"
39
+ const activeBackground = props.activeBackground || "#FFF"
40
+
41
+ const background = props.value ? activeBackground : emptyBackground
42
+ setBackground(background)
43
+ }, [props.value])
44
+
45
+ const width = props.width || "100%"
46
+ const height = props.height || 60
47
+ const borderRadius = props.borderRadius || 10
48
+ const boxShadow = props.shadowDisabled
49
+ ? ""
50
+ : props.boxShadow
51
+ ? props.boxShadow
52
+ : "0 0 0 1px rgba(17, 17, 17, 0.04), 0 1px 1px 0 rgba(17, 17, 17, 0.04)"
53
+ const accentColor = props.accentColor || ""
54
+ const disabled = props.disabled || false
55
+ const hoverBackground = props.hoverBackground || background
56
+ const padding = props.padding || "8px"
57
+ const gap = props.gap || "12px"
58
+ const border = props.border || "none"
59
+ const fontSize = props.fontSize || "14px"
60
+ const iconSize = props.iconSize || "20px"
61
+ const rows = props.rows || 2
62
+
63
+ return (
64
+ <div
65
+ onMouseEnter={() => setHover(true)}
66
+ onMouseLeave={() => setHover(false)}
67
+ className={"k-input-container"}
68
+ style={{ background: hover ? hoverBackground : background, borderRadius, boxShadow, padding, gap, border }}
69
+ >
70
+ {props.leftIcon && (
71
+ <img
72
+ src={props.leftIcon}
73
+ style={{
74
+ width: iconSize,
75
+ height: iconSize
76
+ }}
77
+ alt={"l-icon"}
78
+ className={props.leftIconClick && "cursor-pointer"}
79
+ onClick={() => {
80
+ if (props.leftIconClick) props.leftIconClick()
81
+ }}
82
+ />
83
+ )}
84
+
85
+ <textarea
86
+ className={"k-input"}
87
+ style={{
88
+ background: hover ? hoverBackground : background,
89
+ width,
90
+ height,
91
+ accentColor,
92
+ fontSize
93
+ }}
94
+ rows={rows}
95
+ value={props.value}
96
+ placeholder={props.placeholder || ""}
97
+ disabled={disabled}
98
+ onBlur={(event) => {
99
+ console.log("onBulur", event.target.value)
100
+ if (props.onBlur) props.onBlur(event.target.value)
101
+ }}
102
+ onChange={(event) => {
103
+ console.log("OnChange", event.target.value)
104
+ props.onChange(event.target.value)
105
+ }}
106
+ onKeyDown={(event) => {
107
+ console.log("OnKeyDown", event)
108
+ if (props.onKeyDown) props.onKeyDown(event)
109
+ }}
110
+ />
111
+
112
+ {props.rightIcon && (
113
+ <img
114
+ src={props.rightIcon}
115
+ style={{
116
+ width: iconSize,
117
+ height: iconSize
118
+ }}
119
+ alt={"r-icon"}
120
+ className={props.rightIconClick && "cursor-pointer"}
121
+ onClick={() => {
122
+ if (props.rightIconClick) props.rightIconClick()
123
+ }}
124
+ />
125
+ )}
126
+ </div>
127
+ )
128
+ }
129
+
130
+ export default KTextArea
@@ -0,0 +1 @@
1
+ export {default} from './KTextArea';
package/src/index.ts CHANGED
@@ -8,7 +8,9 @@ import KSlider from "./components/KSlider"
8
8
  import KSelectDate from "./components/KSelectDate"
9
9
  import KTooltip from "./components/KTooltip"
10
10
  import KSliderLabel from "./components/KSliderLabel"
11
+ import KCodeInput from "./components/KCodeInput"
12
+
11
13
 
12
14
  export {
13
- KButton, KSpan, KLogo, KTitleSpan, KInput, KDropdown, KSlider, KSelectDate, KTooltip, KSliderLabel
15
+ KButton, KSpan, KLogo, KTitleSpan, KInput, KDropdown, KSlider, KSelectDate, KTooltip, KSliderLabel, KCodeInput
14
16
  }
package/src/main.css CHANGED
@@ -238,4 +238,14 @@
238
238
  }
239
239
  .k-slider-label-input::-webkit-slider-thumb:active {
240
240
  cursor: grabbing;
241
+ }
242
+ .k-code-input-character-container {
243
+ outline: none !important;
244
+ text-align: center;
245
+ font-family: "Inter";
246
+ caret-color: #B7B7B7;
247
+ }
248
+ .k-code-input-character-container:disabled {
249
+ background: #F7F7F7 !important;
250
+ border: none !important;
241
251
  }