gbs-add-block 1.2.4 → 1.2.6

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,150 +1,123 @@
1
- import React, { useState, useRef, useEffect } from "react";
2
- import type { TextAreaProps } from "./types";
3
-
4
- export const TextArea = ({
5
- label,
6
- placeholder = "Enter your text here...",
7
- value,
8
- onChange,
9
- error,
10
- helperText,
11
- rows = 4,
12
- maxLength,
13
- required = false,
14
- disabled = false,
15
- variant = "default",
16
- className = "",
17
- ...props
18
- }: TextAreaProps) => {
19
- const [isFocused, setIsFocused] = useState(false);
20
- const [charCount, setCharCount] = useState(0);
21
- const textareaRef = useRef(null);
22
-
23
- useEffect(() => {
24
- if (value) {
25
- setCharCount(value.length);
26
- }
27
- }, [value]);
28
-
29
- const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
30
- const newValue = e.target.value;
31
- setCharCount(newValue.length);
32
- if (onChange) {
33
- onChange(e);
34
- }
35
- };
36
-
37
- const handleFocus = () => {
38
- setIsFocused(true);
39
- };
40
-
41
- const handleBlur = () => {
42
- setIsFocused(false);
43
- };
44
-
45
- const getVariantClasses = () => {
46
- switch (variant) {
47
- case "minimal":
48
- return {
49
- container: "relative",
50
- textarea: `w-full px-0 py-3 text-gray-900 placeholder-gray-400 bg-transparent border-0 border-b-2 border-gray-200 resize-none focus:border-blue-500 focus:outline-none ${
51
- disabled ? "opacity-50 cursor-not-allowed" : ""
52
- }`,
53
- label: `absolute left-0 transition-all duration-200 pointer-events-none ${
54
- isFocused || value
55
- ? "-top-6 text-sm text-blue-500"
56
- : "top-3 text-base text-gray-400"
57
- }`,
58
- };
59
- default:
60
- return {
61
- container: "relative",
62
- textarea: `w-full px-4 py-3 text-gray-900 placeholder-gray-400 bg-white border-2 border-gray-200 rounded-lg resize-none focus:border-blue-500 focus:ring-blue-500/20 focus:outline-none transition-all duration-200 shadow-sm hover:border-gray-300 ${
63
- error
64
- ? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
65
- : ""
66
- } ${disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : ""}`,
67
- label: `block text-sm font-medium text-gray-700 mb-2 ${
68
- required ? "after:content-['*'] after:text-red-500 after:ml-1" : ""
69
- }`,
70
- };
71
- }
72
- };
73
-
74
- const variantClasses = getVariantClasses();
75
-
76
- return (
77
- <div className={`${variantClasses.container} ${className}`}>
78
- {label && <label className={variantClasses.label}>{label}</label>}
79
-
80
- <div className="relative">
81
- <textarea
82
- ref={textareaRef}
83
- value={value}
84
- onChange={handleChange}
85
- onFocus={handleFocus}
86
- onBlur={handleBlur}
87
- placeholder={
88
- variant === "minimal" && isFocused
89
- ? placeholder
90
- : variant === "minimal"
91
- ? ""
92
- : placeholder
93
- }
94
- rows={rows}
95
- maxLength={maxLength}
96
- required={required}
97
- disabled={disabled}
98
- className={variantClasses.textarea}
99
- {...props}
100
- />
101
-
102
- {/* Floating label for minimal variant */}
103
- {variant === "minimal" && label && (
104
- <label className={variantClasses.label}>{label}</label>
105
- )}
106
- </div>
107
-
108
- {/* Bottom section with helper text, error, and character count */}
109
- <div className="flex justify-between items-start mt-2">
110
- <div className="flex-1">
111
- {error && (
112
- <p className="text-sm text-red-600 flex items-center">
113
- <svg
114
- className="w-4 h-4 mr-1 flex-shrink-0"
115
- fill="currentColor"
116
- viewBox="0 0 20 20"
117
- >
118
- <path
119
- fillRule="evenodd"
120
- d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
121
- clipRule="evenodd"
122
- />
123
- </svg>
124
- {error}
125
- </p>
126
- )}
127
- {!error && helperText && (
128
- <p className="text-sm text-gray-500">{helperText}</p>
129
- )}
130
- </div>
131
-
132
- {maxLength && (
133
- <div className="ml-4 flex-shrink-0">
134
- <span
135
- className={`text-sm ${
136
- charCount > maxLength * 0.9
137
- ? "text-amber-600"
138
- : charCount === maxLength
139
- ? "text-red-600"
140
- : "text-gray-400"
141
- }`}
142
- >
143
- {charCount}/{maxLength}
144
- </span>
145
- </div>
146
- )}
147
- </div>
148
- </div>
149
- );
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import { twMerge } from "tailwind-merge";
3
+ import type { TextAreaProps } from "./types";
4
+
5
+ export const TextArea = ({
6
+ label,
7
+ placeholder = "Enter your text here...",
8
+ value,
9
+ onChange,
10
+ error,
11
+ helperText,
12
+ rows = 4,
13
+ maxLength,
14
+ required = false,
15
+ disabled = false,
16
+ variant = "default",
17
+ className = "",
18
+ ...props
19
+ }: TextAreaProps) => {
20
+ const [isFocused, setIsFocused] = useState(false);
21
+ const [charCount, setCharCount] = useState(0);
22
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
23
+
24
+ useEffect(() => {
25
+ if (typeof value === "string") {
26
+ setCharCount(value.length);
27
+ }
28
+ }, [value]);
29
+
30
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
31
+ setCharCount(e.target.value.length);
32
+ if (onChange) {
33
+ onChange(e);
34
+ }
35
+ };
36
+
37
+ const isFloating = variant === "minimal" && (isFocused || Boolean(value));
38
+
39
+ const variantClasses = {
40
+ default: {
41
+ textarea:
42
+ "border border-slate-300 bg-white text-slate-900 shadow-sm hover:border-slate-400 focus:border-sky-500 focus:ring-sky-500/20 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100",
43
+ label: "mb-2 block text-sm font-medium text-slate-700 dark:text-slate-200",
44
+ },
45
+ minimal: {
46
+ textarea:
47
+ "border-0 border-b-2 border-slate-200 bg-transparent px-0 shadow-none focus:border-sky-500 dark:border-slate-700 dark:text-slate-100",
48
+ label: twMerge(
49
+ "absolute left-0 pointer-events-none transition-all duration-200",
50
+ isFloating ? "-top-5 text-xs text-sky-600" : "top-3 text-sm text-slate-400"
51
+ ),
52
+ },
53
+ glass: {
54
+ textarea:
55
+ "border border-white/40 bg-white/70 text-slate-900 shadow-sm backdrop-blur hover:border-white/70 focus:border-sky-500 focus:ring-sky-500/20 dark:border-white/10 dark:bg-slate-950/70 dark:text-slate-100",
56
+ label: "mb-2 block text-sm font-medium text-slate-700 dark:text-slate-200",
57
+ },
58
+ }[variant];
59
+
60
+ return (
61
+ <div className={twMerge("relative w-full", className)}>
62
+ {label && variant !== "minimal" && (
63
+ <label className={variantClasses.label}>
64
+ {label}
65
+ {required && <span className="ml-1 text-rose-500">*</span>}
66
+ </label>
67
+ )}
68
+
69
+ <div className="relative">
70
+ <textarea
71
+ ref={textareaRef}
72
+ value={value}
73
+ onChange={handleChange}
74
+ onFocus={() => setIsFocused(true)}
75
+ onBlur={() => setIsFocused(false)}
76
+ placeholder={variant === "minimal" ? (isFocused ? placeholder : "") : placeholder}
77
+ rows={rows}
78
+ maxLength={maxLength}
79
+ required={required}
80
+ disabled={disabled}
81
+ className={twMerge(
82
+ "w-full resize-none rounded-lg px-3 py-3 text-sm outline-none transition-all duration-200 focus:ring-2 disabled:cursor-not-allowed disabled:bg-slate-50 disabled:opacity-60",
83
+ variantClasses.textarea,
84
+ error ? "border-rose-500 focus:border-rose-500 focus:ring-rose-500/20" : ""
85
+ )}
86
+ {...props}
87
+ />
88
+
89
+ {variant === "minimal" && label && (
90
+ <label
91
+ className={variantClasses.label}
92
+ onClick={() => textareaRef.current?.focus()}
93
+ >
94
+ {label}
95
+ {required && <span className="ml-1 text-rose-500">*</span>}
96
+ </label>
97
+ )}
98
+ </div>
99
+
100
+ <div className="mt-2 flex items-start justify-between gap-3">
101
+ <div className="min-w-0 flex-1">
102
+ {error ? (
103
+ <p className="text-xs font-medium text-rose-600">{error}</p>
104
+ ) : helperText ? (
105
+ <p className="text-xs text-slate-500">{helperText}</p>
106
+ ) : null}
107
+ </div>
108
+
109
+ {maxLength && (
110
+ <span
111
+ className={twMerge(
112
+ "shrink-0 text-xs text-slate-400",
113
+ charCount > maxLength * 0.9 ? "text-amber-600" : "",
114
+ charCount === maxLength ? "text-rose-600" : ""
115
+ )}
116
+ >
117
+ {charCount}/{maxLength}
118
+ </span>
119
+ )}
120
+ </div>
121
+ </div>
122
+ );
150
123
  };
@@ -1,35 +1,61 @@
1
- export const primary = {
2
- "error-border": "border border-red-600",
3
- "error-primary": "text-xs text-red-500 pl-1",
4
- };
5
-
6
- export const popUp = {
7
- "pop-up-style":
8
- "w-full absolute overflow-y-auto border border-slate-200 rounded-lg mt-[2px] scrollbar bg-white z-90 scrollbar h-40 dark:bg-black dark:text-white animate-fade-down animate-once animate-duration-200",
9
- "pop-up-style-calender":
10
- "w-full absolute overflow-y-auto border border-slate-200 rounded-lg mt-[2px] scrollbar bg-white z-90 scrollbar h-[370px] dark:bg-black dark:text-white animate-fade-down animate-once animate-duration-200",
11
- };
12
-
13
- export const iconClass = {
14
- "grey-common": "h-4 w-4 stroke-gray-500 fill-none dark:stroke-white",
15
- "error-icon": "h-4 w-4 stroke-red-500 fill-none dark:stroke-red-500",
16
- };
17
-
18
- export const inputStyles = {
19
- default: "bg-gray-100 p-2 rounded-lg focus:outline-black",
20
- error: "border border-red-600",
21
- otp: "w-8 h-10 m-1 border border-gray-600 rounded-lg text-center text-black",
22
- passwordToggle: "absolute inset-y-0 right-0 flex items-center pr-2",
23
- otpContainer: "otp-container",
24
- };
25
-
26
- export const selectStyle = {
27
- "select-button":
28
- "flex items-center px-4 py-2 w-full justify-between rounded-lg font-medium text-md",
29
- "filter-button":
30
- "flex items-center w-full px-2 py-1 text-left hover:bg-blue-100 gap-2 rounded-lg mt-1 text-xs dark:hover:bg-blue-600",
31
- "input-parent":
32
- "text-sm flex w-full -mt-1 gap-1 items-center sticky top-0 border-b rounded-lg border-slate-200 z-50 bg-white dark:bg-black",
33
- "selectedDisplay-Button":
34
- "absolute right-8 top-0 h-full flex items-center px-2 z-20 absolute right-4 top-1/2 transform -translate-y-1/2 z-10",
1
+ import { getComponentPreset } from "./theme";
2
+
3
+ const preset = getComponentPreset();
4
+
5
+ export const primary = {
6
+ "error-border": "border border-rose-500 focus-within:border-rose-500",
7
+ "error-primary": `${preset.text.error} mt-1 pl-1`,
8
+ };
9
+
10
+ export const popUp = {
11
+ "pop-up-style":
12
+ `${preset.surface.overlay} w-full absolute overflow-y-auto rounded-lg mt-1 scrollbar z-[90] h-44 text-sm text-slate-700 dark:text-slate-100 animate-fade-down animate-once animate-duration-200`,
13
+ "pop-up-style-calender":
14
+ `${preset.surface.overlay} w-full absolute overflow-y-auto rounded-lg mt-1 scrollbar z-[90] h-[370px] text-slate-700 dark:text-slate-100 animate-fade-down animate-once animate-duration-200`,
15
+ };
16
+
17
+ export const iconClass = {
18
+ "grey-common": "h-4 w-4 stroke-slate-500 fill-none dark:stroke-slate-300",
19
+ "error-icon": "h-4 w-4 stroke-rose-500 fill-none dark:stroke-rose-400",
20
+ };
21
+
22
+ export const inputStyles = {
23
+ default: `${preset.control} ${preset.radius} ${preset.focusRing} px-3 py-2 text-sm shadow-sm`,
24
+ error: "border-rose-500 focus-visible:ring-rose-500",
25
+ otp: `${preset.control} ${preset.radius} ${preset.focusRing} m-1 h-10 w-9 text-center text-sm font-semibold`,
26
+ passwordToggle: "absolute inset-y-0 right-0 flex items-center pr-2",
27
+ otpContainer: "otp-container flex items-center gap-1",
28
+ };
29
+
30
+ export const selectStyle = {
31
+ "select-button":
32
+ `${preset.control} ${preset.radius} ${preset.focusRing} flex min-h-10 w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm font-medium shadow-sm`,
33
+ "filter-button":
34
+ "mt-1 flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-slate-700 transition-colors hover:bg-sky-50 hover:text-sky-700 dark:text-slate-100 dark:hover:bg-sky-950",
35
+ "input-parent":
36
+ "sticky top-0 z-50 flex w-full items-center gap-1 border-b border-slate-200 bg-white px-1 py-1 text-sm dark:border-slate-700 dark:bg-slate-950",
37
+ "selectedDisplay-Button":
38
+ "absolute right-8 top-1/2 z-20 flex -translate-y-1/2 items-center rounded-md p-1 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800",
39
+ };
40
+
41
+ export const buttonStyles = {
42
+ base: `${preset.radius} ${preset.focusRing} ${preset.disabled} inline-flex items-center justify-center gap-2 font-medium transition-colors`,
43
+ sizes: {
44
+ sm: "h-8 px-3 text-xs",
45
+ md: "h-10 px-4 text-sm",
46
+ lg: "h-11 px-5 text-base",
47
+ },
48
+ variants: {
49
+ primary: "bg-slate-950 text-white hover:bg-slate-800 dark:bg-white dark:text-slate-950 dark:hover:bg-slate-200",
50
+ secondary: "bg-sky-600 text-white hover:bg-sky-700",
51
+ outline: "border border-slate-300 bg-white text-slate-900 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900",
52
+ ghost: "text-slate-700 hover:bg-slate-100 dark:text-slate-100 dark:hover:bg-slate-800",
53
+ danger: "bg-rose-600 text-white hover:bg-rose-700",
54
+ },
55
+ };
56
+
57
+ export const cardStyles = {
58
+ base: `${preset.surface.raised} rounded-lg p-4`,
59
+ interactive:
60
+ "transition-shadow hover:shadow-md hover:shadow-slate-900/10",
35
61
  };
@@ -198,3 +198,9 @@ export const trash: any = [
198
198
  fill: "none",
199
199
  },
200
200
  ];
201
+
202
+ export const menuIcon: any = [
203
+ { type: "path", d: "M4 12h16", key: "m1" },
204
+ { type: "path", d: "M4 6h16", key: "m2" },
205
+ { type: "path", d: "M4 18h16", key: "m3" },
206
+ ];
@@ -0,0 +1,40 @@
1
+ export type ComponentPresetId = "gbs-basic";
2
+
3
+ export type ComponentSize = "sm" | "md" | "lg";
4
+
5
+ export type ComponentVariant =
6
+ | "primary"
7
+ | "secondary"
8
+ | "outline"
9
+ | "ghost"
10
+ | "danger";
11
+
12
+ export const defaultPresetId: ComponentPresetId = "gbs-basic";
13
+
14
+ export const componentPresets = {
15
+ "gbs-basic": {
16
+ id: "gbs-basic",
17
+ radius: "rounded-lg",
18
+ focusRing:
19
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 focus-visible:ring-offset-2",
20
+ disabled: "disabled:cursor-not-allowed disabled:opacity-55",
21
+ text: {
22
+ label: "text-sm font-medium text-slate-700",
23
+ helper: "text-xs text-slate-500",
24
+ error: "text-xs font-medium text-rose-600",
25
+ },
26
+ surface: {
27
+ raised:
28
+ "border border-slate-200 bg-white shadow-sm shadow-slate-900/5 dark:border-slate-700 dark:bg-slate-950",
29
+ subtle:
30
+ "border border-slate-200 bg-slate-50 dark:border-slate-700 dark:bg-slate-900",
31
+ overlay:
32
+ "border border-slate-200 bg-white shadow-xl shadow-slate-900/10 dark:border-slate-700 dark:bg-slate-950",
33
+ },
34
+ control:
35
+ "border border-slate-300 bg-white text-slate-900 transition-colors placeholder:text-slate-400 hover:border-slate-400 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100",
36
+ },
37
+ } as const;
38
+
39
+ export const getComponentPreset = (presetId: ComponentPresetId = defaultPresetId) =>
40
+ componentPresets[presetId] || componentPresets[defaultPresetId];