maru_design2 2.24.4 → 2.25.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.25.0](https://github.com/42maru-ai/maru-design2/compare/v2.24.4...v2.25.0) (2025-04-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * :sparkles: Textarea - add minRows, maxRows [#540](https://github.com/42maru-ai/maru-design2/issues/540) ([b907d40](https://github.com/42maru-ai/maru-design2/commit/b907d40834eb48baea693d94364940605478963a))
9
+ * :sparkles: Textarea - add resizable [#553](https://github.com/42maru-ai/maru-design2/issues/553) ([594031e](https://github.com/42maru-ai/maru-design2/commit/594031e12f14870708e691b1f59fc863c948aa20))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * :bug: Textarea - fix condition & update storybook ([7f14d1a](https://github.com/42maru-ai/maru-design2/commit/7f14d1a95f53956e2a5a6fcbcbdfc5a3ed9d440c))
15
+ * :bug: Textarea - fix init scrollHeight ([c6aa3cc](https://github.com/42maru-ai/maru-design2/commit/c6aa3ccf3f2b2e1b1947bf5a70943044ad2e9f40))
16
+
3
17
  ## [2.24.4](https://github.com/42maru-ai/maru-design2/compare/v2.24.3...v2.24.4) (2025-03-31)
4
18
 
5
19
 
@@ -21,5 +21,18 @@ export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTex
21
21
  */
22
22
  wrapperRef?: React.RefObject<HTMLDivElement>;
23
23
  className?: string;
24
+ /**
25
+ * 최소 높이. 없으면 props.rows 이거나 1로 설정.
26
+ */
27
+ minRows?: number;
28
+ /**
29
+ * 최대 높이. 없으면 무한대 늘어남.
30
+ */
31
+ maxRows?: number;
32
+ /**
33
+ * 바닥 선분을 마우스로 드래그해서 크기를 조절할 수 있는지 여부
34
+ * - 기본값은 false
35
+ */
36
+ resizable?: boolean;
24
37
  }
25
38
  export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import React__default, { useState, useMemo, forwardRef, useEffect, useRef, createElement, Component, useLayoutEffect, createRef } from 'react';
3
+ import React__default, { useState, useMemo, forwardRef, useEffect, useRef, useCallback, createElement, Component, useLayoutEffect, createRef } from 'react';
4
4
  import * as ReactDOM from 'react-dom';
5
5
  import ReactDOM__default, { findDOMNode } from 'react-dom';
6
6
 
@@ -8891,7 +8891,7 @@ function Slider(_a) {
8891
8891
  }));
8892
8892
  }
8893
8893
 
8894
- var css_248z$b = ".maru-textarea-wrapper .maru-textarea {\n display: flex;\n justify-content: center;\n align-items: flex-start;\n flex-direction: row;\n padding: 8px 8px 8px 12px;\n border: 1px solid #e4e4e4;\n border-radius: 6px;\n gap: 8px;\n}\n.maru-textarea-wrapper .maru-textarea > textarea {\n width: 100%;\n border: unset;\n outline: unset;\n background-color: transparent;\n font-family: Pretendard;\n font-style: normal;\n line-height: 150%;\n font-size: 14px;\n letter-spacing: -0.56px;\n font-weight: 400;\n color: #5c5c5c;\n resize: none;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::-webkit-input-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::-moz-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea:-ms-input-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::-ms-input-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea:focus-visible, .maru-textarea-wrapper .maru-textarea:focus-within {\n border-color: #5c5c5c;\n background-color: #fafafa;\n}\n.maru-textarea-wrapper .maru-textarea.errored {\n outline: 1px solid #e53e3e;\n border-color: #e53e3e;\n}\n.maru-textarea-wrapper .maru-textarea:hover {\n outline: 1px solid #e4e4e4;\n border-color: #e4e4e4;\n background-color: #fafafa;\n}\n.maru-textarea-wrapper .maru-textarea.disabled {\n background-color: #fafafa;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::-webkit-input-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::-moz-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea:-ms-input-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::-ms-input-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled:hover {\n outline: unset;\n}\n.maru-textarea-wrapper .maru-textarea-end-node-wrapper {\n display: flex;\n justify-content: center;\n align-items: center;\n flex-direction: row;\n}\n.maru-textarea-wrapper .maru-textarea-end-node-wrapper > button {\n padding: unset;\n margin: unset;\n border: unset;\n background-color: unset;\n cursor: pointer;\n width: 16px;\n height: 16px;\n}\n.maru-textarea-wrapper .maru-textarea-footer {\n display: block;\n margin-top: 4px;\n}\n.maru-textarea-wrapper .maru-textarea-footer > span {\n font-family: Pretendard;\n font-style: normal;\n line-height: 150%;\n font-size: 12px;\n letter-spacing: -0.48px;\n font-weight: 500;\n color: #e53e3e;\n}";
8894
+ var css_248z$b = ".maru-textarea-wrapper .maru-textarea-resizable-wrapper {\n position: relative;\n}\n.maru-textarea-wrapper .maru-textarea {\n display: flex;\n justify-content: center;\n align-items: flex-start;\n flex-direction: row;\n padding: 8px 8px 8px 12px;\n border: 1px solid #e4e4e4;\n border-radius: 6px;\n gap: 8px;\n}\n.maru-textarea-wrapper .maru-textarea > textarea {\n width: 100%;\n border: unset;\n outline: unset;\n background-color: transparent;\n font-family: Pretendard;\n font-style: normal;\n line-height: 150%;\n font-size: 14px;\n letter-spacing: -0.56px;\n font-weight: 400;\n color: #5c5c5c;\n resize: none;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::-webkit-input-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::-moz-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea:-ms-input-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::-ms-input-placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea > textarea::placeholder {\n font-weight: 300;\n color: #8c8c8c;\n}\n.maru-textarea-wrapper .maru-textarea:focus-visible, .maru-textarea-wrapper .maru-textarea:focus-within {\n border-color: #5c5c5c;\n background-color: #fafafa;\n}\n.maru-textarea-wrapper .maru-textarea.errored {\n outline: 1px solid #e53e3e;\n border-color: #e53e3e;\n}\n.maru-textarea-wrapper .maru-textarea:hover {\n outline: 1px solid #e4e4e4;\n border-color: #e4e4e4;\n background-color: #fafafa;\n}\n.maru-textarea-wrapper .maru-textarea.disabled {\n background-color: #fafafa;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::-webkit-input-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::-moz-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea:-ms-input-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::-ms-input-placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled > textarea::placeholder {\n color: #bdbdbd;\n}\n.maru-textarea-wrapper .maru-textarea.disabled:hover {\n outline: unset;\n}\n.maru-textarea-wrapper .maru-textarea-end-node-wrapper {\n display: flex;\n justify-content: center;\n align-items: center;\n flex-direction: row;\n}\n.maru-textarea-wrapper .maru-textarea-end-node-wrapper > button {\n padding: unset;\n margin: unset;\n border: unset;\n background-color: unset;\n cursor: pointer;\n width: 16px;\n height: 16px;\n}\n.maru-textarea-wrapper .resizer {\n position: absolute;\n bottom: 0;\n left: 0;\n height: 5px;\n width: 100%;\n cursor: ns-resize;\n background: transparent;\n}\n.maru-textarea-wrapper .maru-textarea-footer {\n display: block;\n margin-top: 4px;\n}\n.maru-textarea-wrapper .maru-textarea-footer > span {\n font-family: Pretendard;\n font-style: normal;\n line-height: 150%;\n font-size: 12px;\n letter-spacing: -0.48px;\n font-weight: 500;\n color: #e53e3e;\n}";
8895
8895
  styleInject(css_248z$b);
8896
8896
 
8897
8897
  var Textarea = /*#__PURE__*/forwardRef(function (_a, textareaRef) {
@@ -8900,15 +8900,40 @@ var Textarea = /*#__PURE__*/forwardRef(function (_a, textareaRef) {
8900
8900
  hiddenClearButton = _a.hiddenClearButton,
8901
8901
  wrapperRef = _a.wrapperRef,
8902
8902
  className = _a.className,
8903
- props = __rest(_a, ["invalid", "onClear", "hiddenClearButton", "wrapperRef", "className"]);
8904
- var _b = useState(false),
8905
- showClearButton = _b[0],
8906
- setShowClearButton = _b[1];
8903
+ minRows = _a.minRows,
8904
+ maxRows = _a.maxRows,
8905
+ _b = _a.resizable,
8906
+ resizable = _b === void 0 ? false : _b,
8907
+ props = __rest(_a, ["invalid", "onClear", "hiddenClearButton", "wrapperRef", "className", "minRows", "maxRows", "resizable"]);
8908
+ var _c = useState(false),
8909
+ showClearButton = _c[0],
8910
+ setShowClearButton = _c[1];
8907
8911
  var combineRef = useCombineRef(textareaRef);
8912
+ var minHeight = useCallback(function (lineHeight) {
8913
+ return minRows ? minRows * lineHeight : 0;
8914
+ }, [minRows]);
8915
+ var maxHeight = useCallback(function (lineHeight) {
8916
+ return maxRows ? maxRows * lineHeight : Infinity;
8917
+ }, [maxRows]);
8918
+ /**
8919
+ * textarea의 높이를 조절하는 함수
8920
+ * - rows는 기본적으로 초기 높이 설정하는데에만 쓰임
8921
+ * - 동적 높이 조정은 css height 속성 변경으로 조정해야함
8922
+ */
8908
8923
  var resizeHeight = function resizeHeight() {
8909
8924
  if (combineRef.current) {
8925
+ // 항상 그때의 scrollHeight를 가져오도록 높이를 초기화
8910
8926
  combineRef.current.style.height = 'auto';
8911
- combineRef.current.style.height = "".concat(combineRef.current.scrollHeight, "px");
8927
+ var lineHeight = parseInt(window.getComputedStyle(combineRef.current).lineHeight || '0', 10);
8928
+ var scrollHeight = combineRef.current.scrollHeight;
8929
+ if (lineHeight) {
8930
+ var min = minHeight(lineHeight);
8931
+ var max = maxHeight(lineHeight);
8932
+ var newHeight = Math.min(Math.max(scrollHeight, min), max);
8933
+ combineRef.current.style.height = "".concat(newHeight, "px");
8934
+ } else {
8935
+ combineRef.current.style.height = "".concat(scrollHeight, "px");
8936
+ }
8912
8937
  }
8913
8938
  };
8914
8939
  useEffect(function () {
@@ -8955,33 +8980,65 @@ var Textarea = /*#__PURE__*/forwardRef(function (_a, textareaRef) {
8955
8980
  onClear === null || onClear === void 0 ? void 0 : onClear();
8956
8981
  resizeHeight();
8957
8982
  };
8983
+ var handleResizerMouseDown = function handleResizerMouseDown(e) {
8984
+ var combine = combineRef === null || combineRef === void 0 ? void 0 : combineRef.current;
8985
+ if (resizable && combine) {
8986
+ var startY_1 = e.clientY;
8987
+ var startHeight_1 = combine.clientHeight;
8988
+ document.body.style.userSelect = 'none';
8989
+ var lineHeight = parseInt(window.getComputedStyle(combine).lineHeight || '0', 10);
8990
+ var min_1 = minHeight(lineHeight);
8991
+ var max_1 = maxHeight(lineHeight);
8992
+ var handleMouseMove_1 = function handleMouseMove_1(moveEvent) {
8993
+ var newHeight = startHeight_1 + moveEvent.clientY - startY_1;
8994
+ if (min_1 <= newHeight && newHeight <= max_1) {
8995
+ combine.style.height = "".concat(newHeight, "px");
8996
+ }
8997
+ };
8998
+ var handleMouseUp_1 = function handleMouseUp_1() {
8999
+ document.body.style.userSelect = '';
9000
+ document.removeEventListener('mousemove', handleMouseMove_1);
9001
+ document.removeEventListener('mouseup', handleMouseUp_1);
9002
+ };
9003
+ document.addEventListener('mousemove', handleMouseMove_1);
9004
+ document.addEventListener('mouseup', handleMouseUp_1);
9005
+ }
9006
+ };
8958
9007
  return jsxs("div", __assign({
8959
9008
  ref: wrapperRef,
8960
9009
  className: classNames('maru-textarea-wrapper', className)
8961
9010
  }, {
8962
9011
  children: [jsxs("div", __assign({
8963
- className: classNames('maru-textarea', {
8964
- errored: invalid === null || invalid === void 0 ? void 0 : invalid.on,
8965
- disabled: props.disabled
8966
- })
9012
+ className: "maru-textarea-resizable-wrapper"
8967
9013
  }, {
8968
- children: [jsx("textarea", __assign({}, props, {
8969
- ref: combineRef,
8970
- onChange: handleTextareaChange
8971
- })), jsx("div", __assign({
8972
- className: "maru-textarea-end-node-wrapper"
9014
+ children: [jsxs("div", __assign({
9015
+ className: classNames('maru-textarea', {
9016
+ errored: invalid === null || invalid === void 0 ? void 0 : invalid.on,
9017
+ disabled: props.disabled
9018
+ })
8973
9019
  }, {
8974
- children: showClearButton && !props.disabled && jsx("button", __assign({
8975
- type: "button",
8976
- onClick: handleClearClick,
8977
- tabIndex: -1
9020
+ children: [jsx("textarea", __assign({}, props, {
9021
+ ref: combineRef,
9022
+ onChange: handleTextareaChange,
9023
+ rows: props.rows || minRows || 1
9024
+ })), jsx("div", __assign({
9025
+ className: "maru-textarea-end-node-wrapper"
8978
9026
  }, {
8979
- children: jsx(Icon, {
8980
- name: ICONS.X_s1615,
8981
- color: "#5c5c5c"
8982
- })
8983
- }))
8984
- }))]
9027
+ children: showClearButton && !props.disabled && jsx("button", __assign({
9028
+ type: "button",
9029
+ onClick: handleClearClick,
9030
+ tabIndex: -1
9031
+ }, {
9032
+ children: jsx(Icon, {
9033
+ name: ICONS.X_s1615,
9034
+ color: "#5c5c5c"
9035
+ })
9036
+ }))
9037
+ }))]
9038
+ })), resizable && jsx("div", {
9039
+ className: "resizer",
9040
+ onMouseDown: handleResizerMouseDown
9041
+ })]
8985
9042
  })), invalid && invalid.on && jsx("div", __assign({
8986
9043
  className: "maru-textarea-footer"
8987
9044
  }, {
@@ -8996,7 +9053,10 @@ Textarea.defaultProps = {
8996
9053
  onClear: undefined,
8997
9054
  hiddenClearButton: undefined,
8998
9055
  wrapperRef: undefined,
8999
- className: undefined
9056
+ className: undefined,
9057
+ minRows: undefined,
9058
+ maxRows: undefined,
9059
+ resizable: false
9000
9060
  };
9001
9061
  Textarea.displayName = 'Textarea';
9002
9062
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maru_design2",
3
- "version": "2.24.4",
3
+ "version": "2.25.0",
4
4
  "main": "./dist/index.js",
5
5
  "author": "zena-42maru",
6
6
  "license": "MIT",