@neoptocom/neopto-ui 1.2.1 → 1.3.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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var React2 = require('react');
4
+ var React3 = require('react');
5
5
  var reactDom = require('react-dom');
6
6
 
7
7
  function _interopNamespace(e) {
@@ -22,7 +22,7 @@ function _interopNamespace(e) {
22
22
  return Object.freeze(n);
23
23
  }
24
24
 
25
- var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
25
+ var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
26
26
 
27
27
  var __defProp = Object.defineProperty;
28
28
  var __export = (target, all) => {
@@ -107,9 +107,9 @@ function BackgroundBlur({
107
107
  zIndex = 40,
108
108
  className = ""
109
109
  }) {
110
- const [shouldRender, setShouldRender] = React2.useState(false);
111
- const [isVisible, setIsVisible] = React2.useState(false);
112
- React2.useEffect(() => {
110
+ const [shouldRender, setShouldRender] = React3.useState(false);
111
+ const [isVisible, setIsVisible] = React3.useState(false);
112
+ React3.useEffect(() => {
113
113
  if (open) {
114
114
  setShouldRender(true);
115
115
  requestAnimationFrame(() => {
@@ -264,7 +264,7 @@ function Card({
264
264
  }
265
265
  );
266
266
  }
267
- var Input = React2__namespace.forwardRef(
267
+ var Input = React3__namespace.forwardRef(
268
268
  ({ className, disabled, variant = "default", ...props }, ref) => {
269
269
  const isInline = variant === "inline";
270
270
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -275,7 +275,7 @@ var Input = React2__namespace.forwardRef(
275
275
  className: [
276
276
  "w-full bg-transparent outline-none transition-colors",
277
277
  isInline ? "" : "h-12 px-4 rounded-full",
278
- "text-sm placeholder:text-[var(--muted-fg)]",
278
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
279
279
  !isInline && "border",
280
280
  disabled ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]") : [
281
281
  "text-[var(--muted-fg)]",
@@ -292,8 +292,36 @@ var Input = React2__namespace.forwardRef(
292
292
  }
293
293
  );
294
294
  Input.displayName = "Input";
295
+ var Textarea = React3__namespace.forwardRef(
296
+ ({ className, disabled, variant = "default", ...props }, ref) => {
297
+ const isInline = variant === "inline";
298
+ return /* @__PURE__ */ jsxRuntime.jsx(
299
+ "textarea",
300
+ {
301
+ ref,
302
+ disabled,
303
+ className: [
304
+ "w-full bg-transparent outline-none transition-colors resize-y",
305
+ isInline ? "" : "min-h-[96px] px-4 py-3 rounded-3xl",
306
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
307
+ !isInline && "border",
308
+ disabled ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]") : [
309
+ "text-[var(--muted-fg)]",
310
+ isInline ? "" : "border-[var(--muted-fg)]",
311
+ isInline ? "" : "hover:border-[var(--border)]",
312
+ "focus:text-[var(--fg)]",
313
+ isInline ? "" : "focus:border-[var(--color-brand)]"
314
+ ].join(" "),
315
+ className
316
+ ].join(" "),
317
+ ...props
318
+ }
319
+ );
320
+ }
321
+ );
322
+ Textarea.displayName = "Textarea";
295
323
  function useIsomorphicLayoutEffect(effect, deps) {
296
- const useEffectHook = typeof window !== "undefined" ? React2__namespace.useLayoutEffect : React2__namespace.useEffect;
324
+ const useEffectHook = typeof window !== "undefined" ? React3__namespace.useLayoutEffect : React3__namespace.useEffect;
297
325
  useEffectHook(effect, deps);
298
326
  }
299
327
  function Modal({
@@ -305,9 +333,9 @@ function Modal({
305
333
  zIndex = 50,
306
334
  showDecorations = true
307
335
  }) {
308
- const [mounted, setMounted] = React2__namespace.useState(false);
309
- const [isDark, setIsDark] = React2__namespace.useState(false);
310
- React2__namespace.useEffect(() => {
336
+ const [mounted, setMounted] = React3__namespace.useState(false);
337
+ const [isDark, setIsDark] = React3__namespace.useState(false);
338
+ React3__namespace.useEffect(() => {
311
339
  const checkDarkMode = () => {
312
340
  const hasDarkClass = document.documentElement.classList.contains("dark") || document.body.classList.contains("dark") || document.querySelector(".dark") !== null;
313
341
  setIsDark(hasDarkClass);
@@ -330,7 +358,7 @@ function Modal({
330
358
  document.body.style.overflow = original;
331
359
  };
332
360
  }, [open]);
333
- React2__namespace.useEffect(() => {
361
+ React3__namespace.useEffect(() => {
334
362
  if (!open) return;
335
363
  const onKey = (e) => {
336
364
  if (e.key === "Escape") onClose?.();
@@ -453,9 +481,9 @@ function Avatar({
453
481
  style,
454
482
  ...props
455
483
  }) {
456
- const [imgError, setImgError] = React2.useState(false);
457
- const initials = React2.useMemo(() => getInitials(name), [name]);
458
- const computedStyle = React2.useMemo(() => {
484
+ const [imgError, setImgError] = React3.useState(false);
485
+ const initials = React3.useMemo(() => getInitials(name), [name]);
486
+ const computedStyle = React3.useMemo(() => {
459
487
  const s = { ...style };
460
488
  if (color) s.backgroundColor = color;
461
489
  return s;
@@ -490,7 +518,7 @@ function AvatarGroup({
490
518
  overlapPx = 8,
491
519
  withRings = true
492
520
  }) {
493
- const avatars = React2__namespace.Children.toArray(children);
521
+ const avatars = React3__namespace.Children.toArray(children);
494
522
  const displayAvatars = typeof max === "number" ? avatars.slice(0, max) : avatars;
495
523
  const extraCount = typeof max === "number" && avatars.length > max ? avatars.length - max : 0;
496
524
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["flex items-center", className].filter(Boolean).join(" "), children: [
@@ -591,7 +619,7 @@ function getIconButtonClasses(variant = "ghost", size = "md", className) {
591
619
  };
592
620
  return [base, variants[variant], sizes[size], className].filter(Boolean).join(" ");
593
621
  }
594
- var IconButton = React2__namespace.forwardRef(
622
+ var IconButton = React3__namespace.forwardRef(
595
623
  ({
596
624
  variant,
597
625
  size,
@@ -638,25 +666,25 @@ function Autocomplete({
638
666
  id,
639
667
  ...props
640
668
  }) {
641
- const inputId = id ?? React2.useId();
669
+ const inputId = id ?? React3.useId();
642
670
  const listboxId = `${inputId}-listbox`;
643
- const [searchQuery, setSearchQuery] = React2.useState("");
644
- const [open, setOpen] = React2.useState(false);
645
- const [activeIndex, setActiveIndex] = React2.useState(-1);
646
- const rootRef = React2.useRef(null);
647
- const listRef = React2.useRef(null);
648
- const normalizedOptions = React2.useMemo(() => {
671
+ const [searchQuery, setSearchQuery] = React3.useState("");
672
+ const [open, setOpen] = React3.useState(false);
673
+ const [activeIndex, setActiveIndex] = React3.useState(-1);
674
+ const rootRef = React3.useRef(null);
675
+ const listRef = React3.useRef(null);
676
+ const normalizedOptions = React3.useMemo(() => {
649
677
  if (Array.isArray(options) && typeof options[0] === "string") {
650
678
  return options.map((str) => ({ label: str, value: str }));
651
679
  }
652
680
  return options;
653
681
  }, [options]);
654
- const filtered = React2.useMemo(() => {
682
+ const filtered = React3.useMemo(() => {
655
683
  const q = searchQuery.trim().toLowerCase();
656
684
  if (!q) return normalizedOptions;
657
685
  return normalizedOptions.filter((o) => o.label.toLowerCase().includes(q));
658
686
  }, [normalizedOptions, searchQuery]);
659
- const anyOptionHasImage = React2.useMemo(
687
+ const anyOptionHasImage = React3.useMemo(
660
688
  () => normalizedOptions.some((o) => !!o.image),
661
689
  [normalizedOptions]
662
690
  );
@@ -848,27 +876,27 @@ function Search({
848
876
  children,
849
877
  ...props
850
878
  }) {
851
- const inputId = id ?? React2.useId();
879
+ const inputId = id ?? React3.useId();
852
880
  const listboxId = `${inputId}-listbox`;
853
- const [searchQuery, setSearchQuery] = React2.useState("");
854
- const [open, setOpen] = React2.useState(false);
855
- const [activeIndex, setActiveIndex] = React2.useState(-1);
856
- const [filtersExpanded, setFiltersExpanded] = React2.useState(false);
857
- const rootRef = React2.useRef(null);
858
- const listRef = React2.useRef(null);
859
- const searchTimeoutRef = React2.useRef(null);
860
- const normalizedOptions = React2.useMemo(() => {
881
+ const [searchQuery, setSearchQuery] = React3.useState("");
882
+ const [open, setOpen] = React3.useState(false);
883
+ const [activeIndex, setActiveIndex] = React3.useState(-1);
884
+ const [filtersExpanded, setFiltersExpanded] = React3.useState(false);
885
+ const rootRef = React3.useRef(null);
886
+ const listRef = React3.useRef(null);
887
+ const searchTimeoutRef = React3.useRef(null);
888
+ const normalizedOptions = React3.useMemo(() => {
861
889
  if (Array.isArray(options) && typeof options[0] === "string") {
862
890
  return options.map((str) => ({ label: str, value: str }));
863
891
  }
864
892
  return options;
865
893
  }, [options]);
866
- React2.useMemo(
894
+ React3.useMemo(
867
895
  () => normalizedOptions.some((o) => !!o.image),
868
896
  [normalizedOptions]
869
897
  );
870
898
  const displayValue = selectedOption != null ? typeof selectedOption === "string" ? selectedOption : selectedOption.label : searchQuery;
871
- const debouncedSearch = React2.useCallback(
899
+ const debouncedSearch = React3.useCallback(
872
900
  (query) => {
873
901
  if (searchTimeoutRef.current) {
874
902
  clearTimeout(searchTimeoutRef.current);
@@ -937,7 +965,7 @@ function Search({
937
965
  const el = list.children[idx];
938
966
  el?.scrollIntoView({ block: "nearest" });
939
967
  }
940
- React2__namespace.useEffect(() => {
968
+ React3__namespace.useEffect(() => {
941
969
  return () => {
942
970
  if (searchTimeoutRef.current) {
943
971
  clearTimeout(searchTimeoutRef.current);
@@ -1044,7 +1072,7 @@ function getButtonClasses(variant = "primary", size = "md", fullWidth, className
1044
1072
  className
1045
1073
  ].filter(Boolean).join(" ");
1046
1074
  }
1047
- var Button = React2__namespace.forwardRef(
1075
+ var Button = React3__namespace.forwardRef(
1048
1076
  ({ variant, size, fullWidth, className, children, icon, ...props }, ref) => {
1049
1077
  return /* @__PURE__ */ jsxRuntime.jsx(
1050
1078
  "button",
@@ -1105,8 +1133,8 @@ function Counter({
1105
1133
  className = "",
1106
1134
  ...props
1107
1135
  }) {
1108
- const [count, setCount] = React2__namespace.useState(value);
1109
- React2__namespace.useEffect(() => {
1136
+ const [count, setCount] = React3__namespace.useState(value);
1137
+ React3__namespace.useEffect(() => {
1110
1138
  setCount(value);
1111
1139
  }, [value]);
1112
1140
  const handleIncrement = () => {
@@ -1149,7 +1177,7 @@ function Counter({
1149
1177
  ] });
1150
1178
  }
1151
1179
  var AnimatedBgCircle = ({ colors, delay = 0 }) => {
1152
- const uniqueId = React2.useMemo(() => Math.random().toString(36).substr(2, 9), []);
1180
+ const uniqueId = React3.useMemo(() => Math.random().toString(36).substr(2, 9), []);
1153
1181
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 64 64", fill: "none", className: "h-full transition-all duration-500 ease-in-out w-full", children: [
1154
1182
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1155
1183
  @keyframes colorCycle-${uniqueId} {
@@ -1181,7 +1209,7 @@ var AnimatedBgCircle = ({ colors, delay = 0 }) => {
1181
1209
  };
1182
1210
  var AnimatedBgCircle_default = AnimatedBgCircle;
1183
1211
  var AnimatedBgRectangle = ({ colors, delay = 0 }) => {
1184
- const uniqueId = React2.useMemo(() => Math.random().toString(36).substr(2, 9), []);
1212
+ const uniqueId = React3.useMemo(() => Math.random().toString(36).substr(2, 9), []);
1185
1213
  return /* @__PURE__ */ jsxRuntime.jsxs(
1186
1214
  "svg",
1187
1215
  {
@@ -1229,14 +1257,14 @@ var AgentButton = ({
1229
1257
  logoAlt = "Agent",
1230
1258
  animationColors = ["#7DADB9", "#3864F5", "#55468D", "#479A8D"]
1231
1259
  }) => {
1232
- const [showText, setShowText] = React2.useState(false);
1233
- const [delayedHasNotification, setDelayedHasNotification] = React2.useState(false);
1234
- const [isMounted, setIsMounted] = React2.useState(false);
1235
- React2.useEffect(() => {
1260
+ const [showText, setShowText] = React3.useState(false);
1261
+ const [delayedHasNotification, setDelayedHasNotification] = React3.useState(false);
1262
+ const [isMounted, setIsMounted] = React3.useState(false);
1263
+ React3.useEffect(() => {
1236
1264
  const timer = setTimeout(() => setIsMounted(true), 250);
1237
1265
  return () => clearTimeout(timer);
1238
1266
  }, []);
1239
- React2.useEffect(() => {
1267
+ React3.useEffect(() => {
1240
1268
  if (hasNotification) {
1241
1269
  const textTimer = setTimeout(() => setShowText(true), 500);
1242
1270
  setDelayedHasNotification(true);
@@ -1327,7 +1355,7 @@ var AgentButton = ({
1327
1355
  );
1328
1356
  };
1329
1357
  var AgentButton_default = AgentButton;
1330
- var MessageBubble = React2__namespace.forwardRef(
1358
+ var MessageBubble = React3__namespace.forwardRef(
1331
1359
  ({ direction, color, children, className, ...props }, ref) => {
1332
1360
  const borderRadiusClass = direction === "left" ? "[border-radius:16px_16px_16px_2px]" : direction === "right" ? "[border-radius:16px_16px_2px_16px]" : "rounded-2xl";
1333
1361
  const backgroundColor = color || "var(--muted)";
@@ -1372,5 +1400,6 @@ exports.Modal = Modal;
1372
1400
  exports.Search = Search;
1373
1401
  exports.Separator = Separator;
1374
1402
  exports.Skeleton = Skeleton;
1403
+ exports.Textarea = Textarea;
1375
1404
  exports.Typo = Typo;
1376
1405
  exports.assets = assets_exports;
package/dist/index.d.cts CHANGED
@@ -67,6 +67,15 @@ declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttribu
67
67
  variant?: "default" | "inline";
68
68
  } & React.RefAttributes<HTMLInputElement>>;
69
69
 
70
+ type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
71
+ /** Textarea visual variant */
72
+ variant?: "default" | "inline";
73
+ };
74
+ declare const Textarea: React.ForwardRefExoticComponent<Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size"> & {
75
+ /** Textarea visual variant */
76
+ variant?: "default" | "inline";
77
+ } & React.RefAttributes<HTMLTextAreaElement>>;
78
+
70
79
  type ModalProps = {
71
80
  /** Whether the modal is open */
72
81
  open: boolean;
@@ -322,4 +331,4 @@ type SeparatorProps = {
322
331
  };
323
332
  declare function Separator({ className }: SeparatorProps): react_jsx_runtime.JSX.Element;
324
333
 
325
- export { AgentButton, type AgentButtonProps, AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, MessageBubble, type MessageBubbleProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Separator, type SeparatorProps, Skeleton, type SkeletonProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
334
+ export { AgentButton, type AgentButtonProps, AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, MessageBubble, type MessageBubbleProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Separator, type SeparatorProps, Skeleton, type SkeletonProps, Textarea, type TextareaProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,15 @@ declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttribu
67
67
  variant?: "default" | "inline";
68
68
  } & React.RefAttributes<HTMLInputElement>>;
69
69
 
70
+ type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
71
+ /** Textarea visual variant */
72
+ variant?: "default" | "inline";
73
+ };
74
+ declare const Textarea: React.ForwardRefExoticComponent<Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size"> & {
75
+ /** Textarea visual variant */
76
+ variant?: "default" | "inline";
77
+ } & React.RefAttributes<HTMLTextAreaElement>>;
78
+
70
79
  type ModalProps = {
71
80
  /** Whether the modal is open */
72
81
  open: boolean;
@@ -322,4 +331,4 @@ type SeparatorProps = {
322
331
  };
323
332
  declare function Separator({ className }: SeparatorProps): react_jsx_runtime.JSX.Element;
324
333
 
325
- export { AgentButton, type AgentButtonProps, AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, MessageBubble, type MessageBubbleProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Separator, type SeparatorProps, Skeleton, type SkeletonProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
334
+ export { AgentButton, type AgentButtonProps, AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, MessageBubble, type MessageBubbleProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Separator, type SeparatorProps, Skeleton, type SkeletonProps, Textarea, type TextareaProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import * as React2 from 'react';
2
+ import * as React3 from 'react';
3
3
  import { useState, useEffect, useMemo, useId, useRef, useCallback } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
 
@@ -243,7 +243,7 @@ function Card({
243
243
  }
244
244
  );
245
245
  }
246
- var Input = React2.forwardRef(
246
+ var Input = React3.forwardRef(
247
247
  ({ className, disabled, variant = "default", ...props }, ref) => {
248
248
  const isInline = variant === "inline";
249
249
  return /* @__PURE__ */ jsx(
@@ -254,7 +254,7 @@ var Input = React2.forwardRef(
254
254
  className: [
255
255
  "w-full bg-transparent outline-none transition-colors",
256
256
  isInline ? "" : "h-12 px-4 rounded-full",
257
- "text-sm placeholder:text-[var(--muted-fg)]",
257
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
258
258
  !isInline && "border",
259
259
  disabled ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]") : [
260
260
  "text-[var(--muted-fg)]",
@@ -271,8 +271,36 @@ var Input = React2.forwardRef(
271
271
  }
272
272
  );
273
273
  Input.displayName = "Input";
274
+ var Textarea = React3.forwardRef(
275
+ ({ className, disabled, variant = "default", ...props }, ref) => {
276
+ const isInline = variant === "inline";
277
+ return /* @__PURE__ */ jsx(
278
+ "textarea",
279
+ {
280
+ ref,
281
+ disabled,
282
+ className: [
283
+ "w-full bg-transparent outline-none transition-colors resize-y",
284
+ isInline ? "" : "min-h-[96px] px-4 py-3 rounded-3xl",
285
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
286
+ !isInline && "border",
287
+ disabled ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]") : [
288
+ "text-[var(--muted-fg)]",
289
+ isInline ? "" : "border-[var(--muted-fg)]",
290
+ isInline ? "" : "hover:border-[var(--border)]",
291
+ "focus:text-[var(--fg)]",
292
+ isInline ? "" : "focus:border-[var(--color-brand)]"
293
+ ].join(" "),
294
+ className
295
+ ].join(" "),
296
+ ...props
297
+ }
298
+ );
299
+ }
300
+ );
301
+ Textarea.displayName = "Textarea";
274
302
  function useIsomorphicLayoutEffect(effect, deps) {
275
- const useEffectHook = typeof window !== "undefined" ? React2.useLayoutEffect : React2.useEffect;
303
+ const useEffectHook = typeof window !== "undefined" ? React3.useLayoutEffect : React3.useEffect;
276
304
  useEffectHook(effect, deps);
277
305
  }
278
306
  function Modal({
@@ -284,9 +312,9 @@ function Modal({
284
312
  zIndex = 50,
285
313
  showDecorations = true
286
314
  }) {
287
- const [mounted, setMounted] = React2.useState(false);
288
- const [isDark, setIsDark] = React2.useState(false);
289
- React2.useEffect(() => {
315
+ const [mounted, setMounted] = React3.useState(false);
316
+ const [isDark, setIsDark] = React3.useState(false);
317
+ React3.useEffect(() => {
290
318
  const checkDarkMode = () => {
291
319
  const hasDarkClass = document.documentElement.classList.contains("dark") || document.body.classList.contains("dark") || document.querySelector(".dark") !== null;
292
320
  setIsDark(hasDarkClass);
@@ -309,7 +337,7 @@ function Modal({
309
337
  document.body.style.overflow = original;
310
338
  };
311
339
  }, [open]);
312
- React2.useEffect(() => {
340
+ React3.useEffect(() => {
313
341
  if (!open) return;
314
342
  const onKey = (e) => {
315
343
  if (e.key === "Escape") onClose?.();
@@ -469,7 +497,7 @@ function AvatarGroup({
469
497
  overlapPx = 8,
470
498
  withRings = true
471
499
  }) {
472
- const avatars = React2.Children.toArray(children);
500
+ const avatars = React3.Children.toArray(children);
473
501
  const displayAvatars = typeof max === "number" ? avatars.slice(0, max) : avatars;
474
502
  const extraCount = typeof max === "number" && avatars.length > max ? avatars.length - max : 0;
475
503
  return /* @__PURE__ */ jsxs("div", { className: ["flex items-center", className].filter(Boolean).join(" "), children: [
@@ -570,7 +598,7 @@ function getIconButtonClasses(variant = "ghost", size = "md", className) {
570
598
  };
571
599
  return [base, variants[variant], sizes[size], className].filter(Boolean).join(" ");
572
600
  }
573
- var IconButton = React2.forwardRef(
601
+ var IconButton = React3.forwardRef(
574
602
  ({
575
603
  variant,
576
604
  size,
@@ -916,7 +944,7 @@ function Search({
916
944
  const el = list.children[idx];
917
945
  el?.scrollIntoView({ block: "nearest" });
918
946
  }
919
- React2.useEffect(() => {
947
+ React3.useEffect(() => {
920
948
  return () => {
921
949
  if (searchTimeoutRef.current) {
922
950
  clearTimeout(searchTimeoutRef.current);
@@ -1023,7 +1051,7 @@ function getButtonClasses(variant = "primary", size = "md", fullWidth, className
1023
1051
  className
1024
1052
  ].filter(Boolean).join(" ");
1025
1053
  }
1026
- var Button = React2.forwardRef(
1054
+ var Button = React3.forwardRef(
1027
1055
  ({ variant, size, fullWidth, className, children, icon, ...props }, ref) => {
1028
1056
  return /* @__PURE__ */ jsx(
1029
1057
  "button",
@@ -1084,8 +1112,8 @@ function Counter({
1084
1112
  className = "",
1085
1113
  ...props
1086
1114
  }) {
1087
- const [count, setCount] = React2.useState(value);
1088
- React2.useEffect(() => {
1115
+ const [count, setCount] = React3.useState(value);
1116
+ React3.useEffect(() => {
1089
1117
  setCount(value);
1090
1118
  }, [value]);
1091
1119
  const handleIncrement = () => {
@@ -1306,7 +1334,7 @@ var AgentButton = ({
1306
1334
  );
1307
1335
  };
1308
1336
  var AgentButton_default = AgentButton;
1309
- var MessageBubble = React2.forwardRef(
1337
+ var MessageBubble = React3.forwardRef(
1310
1338
  ({ direction, color, children, className, ...props }, ref) => {
1311
1339
  const borderRadiusClass = direction === "left" ? "[border-radius:16px_16px_16px_2px]" : direction === "right" ? "[border-radius:16px_16px_2px_16px]" : "rounded-2xl";
1312
1340
  const backgroundColor = color || "var(--muted)";
@@ -1331,4 +1359,4 @@ function Separator({ className = "" }) {
1331
1359
  return /* @__PURE__ */ jsx("div", { className: `w-full my-1.5 h-px bg-[var(--border)] ${className}` });
1332
1360
  }
1333
1361
 
1334
- export { AgentButton_default as AgentButton, AnimatedBgCircle_default as AnimatedBgCircle, AnimatedBgRectangle_default as AnimatedBgRectangle, AppBackground, Autocomplete, Avatar, AvatarGroup, BackgroundBlur, Button, Card, Chip, Counter, Icon, IconButton, Input, MessageBubble, Modal, Search, Separator, Skeleton, Typo, assets_exports as assets };
1362
+ export { AgentButton_default as AgentButton, AnimatedBgCircle_default as AnimatedBgCircle, AnimatedBgRectangle_default as AnimatedBgRectangle, AppBackground, Autocomplete, Avatar, AvatarGroup, BackgroundBlur, Button, Card, Chip, Counter, Icon, IconButton, Input, MessageBubble, Modal, Search, Separator, Skeleton, Textarea, Typo, assets_exports as assets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoptocom/neopto-ui",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "description": "A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation. Requires Tailwind v4+.",
6
6
  "keywords": [
@@ -16,7 +16,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
16
16
  className={[
17
17
  "w-full bg-transparent outline-none transition-colors",
18
18
  isInline ? "" : "h-12 px-4 rounded-full",
19
- "text-sm placeholder:text-[var(--muted-fg)]",
19
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
20
20
  !isInline && "border",
21
21
  disabled
22
22
  ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]")
@@ -0,0 +1,38 @@
1
+ import * as React from "react";
2
+
3
+ export type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
4
+ /** Textarea visual variant */
5
+ variant?: "default" | "inline";
6
+ };
7
+
8
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
9
+ ({ className, disabled, variant = "default", ...props }, ref) => {
10
+ const isInline = variant === "inline";
11
+
12
+ return (
13
+ <textarea
14
+ ref={ref}
15
+ disabled={disabled}
16
+ className={[
17
+ "w-full bg-transparent outline-none transition-colors resize-y",
18
+ isInline ? "" : "min-h-[96px] px-4 py-3 rounded-3xl",
19
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
20
+ !isInline && "border",
21
+ disabled
22
+ ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]")
23
+ : [
24
+ "text-[var(--muted-fg)]",
25
+ isInline ? "" : "border-[var(--muted-fg)]",
26
+ isInline ? "" : "hover:border-[var(--border)]",
27
+ "focus:text-[var(--fg)]",
28
+ isInline ? "" : "focus:border-[var(--color-brand)]"
29
+ ].join(" "),
30
+ className
31
+ ].join(" ")}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+ );
37
+ Textarea.displayName = "Textarea";
38
+
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export { AppBackground } from "./components/AppBackground";
6
6
  export { BackgroundBlur } from "./components/BackgroundBlur";
7
7
  export { Card } from "./components/Card";
8
8
  export * from "./components/Input";
9
+ export * from "./components/Textarea";
9
10
  export * from "./components/Modal";
10
11
  export { default as Typo } from "./components/Typo";
11
12
  export { default as Avatar } from "./components/Avatar";
@@ -27,6 +28,7 @@ export type { AppBackgroundProps } from "./components/AppBackground";
27
28
  export type { BackgroundBlurProps } from "./components/BackgroundBlur";
28
29
  export type { CardProps } from "./components/Card";
29
30
  export type { InputProps } from "./components/Input";
31
+ export type { TextareaProps } from "./components/Textarea";
30
32
  export type { ModalProps } from "./components/Modal";
31
33
  export type { TypoProps, TypoVariant, TypoWeight } from "./components/Typo";
32
34
  export type { AvatarProps } from "./components/Avatar";
@@ -0,0 +1,90 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Textarea } from "../components/Textarea";
3
+
4
+ const meta: Meta<typeof Textarea> = {
5
+ title: "Components/Textarea",
6
+ component: Textarea,
7
+ parameters: {
8
+ layout: "padded",
9
+ },
10
+ };
11
+ export default meta;
12
+ type Story = StoryObj<typeof Textarea>;
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <div className="flex flex-col gap-4 w-96">
17
+ <Textarea placeholder="Enter your message..." />
18
+ <Textarea placeholder="With default value..." defaultValue="This is some default text that spans multiple lines and shows how the textarea handles content." />
19
+ </div>
20
+ ),
21
+ };
22
+
23
+ export const Inline: Story = {
24
+ render: () => (
25
+ <div className="flex flex-col gap-4 w-96">
26
+ <Textarea variant="inline" placeholder="Inline textarea" />
27
+ <Textarea variant="inline" placeholder="Inline with value" defaultValue="Inline textarea with some content" />
28
+ <Textarea variant="inline" placeholder="Disabled inline" disabled />
29
+ </div>
30
+ )
31
+ };
32
+
33
+ export const States: Story = {
34
+ render: () => (
35
+ <div className="flex flex-col gap-4 w-96">
36
+ <div>
37
+ <label className="block text-sm font-medium mb-2">Default</label>
38
+ <Textarea placeholder="Type something..." />
39
+ </div>
40
+ <div>
41
+ <label className="block text-sm font-medium mb-2">With Value</label>
42
+ <Textarea defaultValue="This textarea has some content already filled in." />
43
+ </div>
44
+ <div>
45
+ <label className="block text-sm font-medium mb-2">Disabled</label>
46
+ <Textarea placeholder="Disabled textarea" disabled />
47
+ </div>
48
+ <div>
49
+ <label className="block text-sm font-medium mb-2">Disabled with Value</label>
50
+ <Textarea defaultValue="Disabled with content" disabled />
51
+ </div>
52
+ </div>
53
+ ),
54
+ };
55
+
56
+ export const Sizes: Story = {
57
+ render: () => (
58
+ <div className="flex flex-col gap-4 w-96">
59
+ <div>
60
+ <label className="block text-sm font-medium mb-2">Small (3 rows)</label>
61
+ <Textarea placeholder="Small textarea..." rows={3} />
62
+ </div>
63
+ <div>
64
+ <label className="block text-sm font-medium mb-2">Medium (5 rows)</label>
65
+ <Textarea placeholder="Medium textarea..." rows={5} />
66
+ </div>
67
+ <div>
68
+ <label className="block text-sm font-medium mb-2">Large (8 rows)</label>
69
+ <Textarea placeholder="Large textarea..." rows={8} />
70
+ </div>
71
+ </div>
72
+ ),
73
+ };
74
+
75
+ export const WithCustomStyling: Story = {
76
+ render: () => (
77
+ <div className="flex flex-col gap-4 w-96">
78
+ <Textarea
79
+ placeholder="Custom height..."
80
+ style={{ minHeight: "200px" }}
81
+ />
82
+ <Textarea
83
+ placeholder="Fixed height (no resize)..."
84
+ className="resize-none"
85
+ rows={4}
86
+ />
87
+ </div>
88
+ ),
89
+ };
90
+