podo-ui 0.9.0 → 0.9.3

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 +1 @@
1
- {"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../react/atom/avatar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAEjC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAElD;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAmFjC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../react/atom/avatar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAGjD,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAEjC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAElD;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAmFhC,CAAC;AAIH,eAAe,MAAM,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo, useCallback } from 'react';
2
3
  import styles from './avatar.module.scss';
3
- const Avatar = ({ type = 'icon', src, icon = 'icon-user', text, size = 56, activityRing = false, className = '', alt = 'Avatar', onClick, }) => {
4
+ const Avatar = memo(({ type = 'icon', src, icon = 'icon-user', text, size = 56, activityRing = false, className = '', alt = 'Avatar', onClick, }) => {
4
5
  const wrapperClasses = [
5
6
  styles.wrapper,
6
7
  activityRing && styles.activityRing,
@@ -20,7 +21,7 @@ const Avatar = ({ type = 'icon', src, icon = 'icon-user', text, size = 56, activ
20
21
  // Format text to show only first 2 characters
21
22
  const displayText = text ? text.slice(0, 2) : '';
22
23
  // Calculate font size based on avatar size and type
23
- const getFontSize = (contentType) => {
24
+ const getFontSize = useCallback((contentType) => {
24
25
  if (contentType === 'icon') {
25
26
  // 아이콘: size={56}일 때 44px 기준 (약 78.5%)
26
27
  const iconRatio = 0.785;
@@ -31,7 +32,7 @@ const Avatar = ({ type = 'icon', src, icon = 'icon-user', text, size = 56, activ
31
32
  const textRatio = 0.43;
32
33
  return Math.round(size * textRatio);
33
34
  }
34
- };
35
+ }, [size]);
35
36
  const renderContent = () => {
36
37
  if (type === 'image' && src) {
37
38
  return _jsx("img", { src: src, alt: alt, className: styles.image });
@@ -50,5 +51,6 @@ const Avatar = ({ type = 'icon', src, icon = 'icon-user', text, size = 56, activ
50
51
  height: size,
51
52
  fontSize: type === 'text' ? getFontSize('text') : getFontSize('icon')
52
53
  }, children: renderContent() }) }));
53
- };
54
+ });
55
+ Avatar.displayName = 'Avatar';
54
56
  export default Avatar;
@@ -1 +1 @@
1
- {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../react/atom/button.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,SAAS,GACT,cAAc,GACd,MAAM,GACN,MAAM,GACN,SAAS,GACT,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjE,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC;IACjE,kBAAkB;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,WAAW;IACX,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACxC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,QAAA,MAAM,MAAM,wHA6CX,CAAC;AAIF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../react/atom/button.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,SAAS,GACT,cAAc,GACd,MAAM,GACN,MAAM,GACN,SAAS,GACT,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjE,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC;IACjE,kBAAkB;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,WAAW;IACX,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACxC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,QAAA,MAAM,MAAM,wHA+CX,CAAC;AAIF,eAAe,MAAM,CAAC"}
@@ -11,7 +11,7 @@ const Button = forwardRef(({ theme = 'default', variant = 'solid', size = 'sm',
11
11
  ]
12
12
  .filter(Boolean)
13
13
  .join(' ');
14
- return (_jsxs("button", { ref: ref, className: buttonClass || undefined, disabled: disabled || loading, ...rest, children: [loading ? (_jsx("i", { className: "icon-loading" })) : (icon && _jsx("i", { className: icon })), children, rightIcon && !loading && _jsx("i", { className: rightIcon })] }));
14
+ return (_jsxs("button", { ref: ref, className: buttonClass || undefined, disabled: disabled || loading, "aria-busy": loading ? true : undefined, "aria-disabled": disabled ? true : undefined, ...rest, children: [loading ? (_jsx("i", { className: "icon-loading" })) : (icon && _jsx("i", { className: icon })), children, rightIcon && !loading && _jsx("i", { className: rightIcon })] }));
15
15
  });
16
16
  Button.displayName = 'Button';
17
17
  export default Button;
@@ -1 +1 @@
1
- {"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../react/atom/chip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnE,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CA4B7B,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../react/atom/chip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAEpC,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnE,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CA4B5B,CAAC;AAIH,eAAe,IAAI,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- const Chip = ({ children, theme = 'default', type = 'default', size = 'md', round = false, icon, onDelete, className = '', }) => {
2
+ import { memo } from 'react';
3
+ const Chip = memo(({ children, theme = 'default', type = 'default', size = 'md', round = false, icon, onDelete, className = '', }) => {
3
4
  const chipClasses = [
4
5
  'chip',
5
6
  theme !== 'default' && theme,
@@ -11,5 +12,6 @@ const Chip = ({ children, theme = 'default', type = 'default', size = 'md', roun
11
12
  .filter(Boolean)
12
13
  .join(' ');
13
14
  return (_jsxs("div", { className: chipClasses, children: [icon && _jsx("i", { className: `icon ${icon}` }), children, onDelete && _jsx("button", { "aria-label": "\uC0AD\uC81C", onClick: onDelete })] }));
14
- };
15
+ });
16
+ Chip.displayName = 'Chip';
15
17
  export default Chip;
@@ -6,6 +6,10 @@ export interface InputWrapperProps extends React.ComponentProps<'input'> {
6
6
  withIcon?: string;
7
7
  withRightIcon?: string;
8
8
  unit?: string;
9
+ /** Accessible label for screen readers */
10
+ 'aria-label'?: string;
11
+ /** ID of element describing the input */
12
+ 'aria-describedby'?: string;
9
13
  }
10
14
  declare const Input: React.FC<InputWrapperProps>;
11
15
  export default Input;
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../react/atom/input.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC;IACtE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,QAAA,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAqDtC,CAAC;AAEF,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../react/atom/input.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC;IACtE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,QAAA,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA4CtC,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -1,29 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useCallback } from 'react';
3
- import { z } from 'zod';
2
+ import { useEffect } from 'react';
4
3
  import styles from './input.module.scss';
5
- const Input = ({ validator, value, className, withIcon, withRightIcon, unit, type = 'text', ...rest }) => {
6
- const [message, setMessage] = useState('');
7
- const [statusClass, setStatusClass] = useState('');
8
- const validateHandler = useCallback(() => {
9
- setMessage('');
10
- setStatusClass('');
11
- if (validator && value) {
12
- try {
13
- validator.parse(value);
14
- setStatusClass('success');
15
- }
16
- catch (e) {
17
- if (e instanceof z.ZodError) {
18
- setMessage(e.errors[0].message);
19
- setStatusClass('danger');
20
- }
21
- }
22
- }
23
- }, [validator, value]);
4
+ import { useValidation } from '../hooks/useValidation';
5
+ const Input = ({ validator, value, className, withIcon, withRightIcon, unit, type = 'text', id, ...rest }) => {
6
+ const inputId = id || `input-${Math.random().toString(36).slice(2, 9)}`;
7
+ const errorId = `${inputId}-error`;
8
+ const { message, statusClass, validate } = useValidation(validator);
24
9
  useEffect(() => {
25
- validateHandler();
26
- }, [validateHandler, value]);
27
- return (_jsxs("div", { className: `${styles.style} ${className || ''}`, children: [_jsxs("div", { className: `${className || ''} ${withIcon ? 'with-icon' : ''} ${withRightIcon ? 'with-right-icon' : ''}`, children: [withIcon && _jsx("i", { className: withIcon }), _jsx("input", { type: type, ...rest, value: value ?? '', className: `${statusClass} ${className || ''}` }), withRightIcon && _jsx("i", { className: withRightIcon }), unit && _jsx("span", { className: "unit", children: unit })] }), validator && message !== '' && (_jsx("div", { className: "validator", children: message }))] }));
10
+ validate(value);
11
+ }, [validate, value]);
12
+ return (_jsxs("div", { className: `${styles.style} ${className || ''}`, children: [_jsxs("div", { className: `${className || ''} ${withIcon ? 'with-icon' : ''} ${withRightIcon ? 'with-right-icon' : ''}`, children: [withIcon && _jsx("i", { className: withIcon }), _jsx("input", { id: inputId, type: type, ...rest, value: value ?? '', className: `${statusClass} ${className || ''}`, "aria-invalid": statusClass === 'danger' ? true : undefined, "aria-describedby": message ? errorId : rest['aria-describedby'] }), withRightIcon && _jsx("i", { className: withRightIcon }), unit && _jsx("span", { className: "unit", children: unit })] }), validator && message !== '' && (_jsx("div", { id: errorId, className: "validator", role: "alert", children: message }))] }));
28
13
  };
29
14
  export default Input;
@@ -3,6 +3,10 @@ export interface TextareaWrapperProps extends React.ComponentProps<'textarea'> {
3
3
  value: string;
4
4
  className?: string;
5
5
  validator?: z.ZodType<unknown>;
6
+ /** Accessible label for screen readers */
7
+ 'aria-label'?: string;
8
+ /** ID of element describing the textarea */
9
+ 'aria-describedby'?: string;
6
10
  }
7
11
  declare const Textarea: React.FC<TextareaWrapperProps>;
8
12
  export default Textarea;
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../react/atom/textarea.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,oBAAqB,SAAQ,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;CAChC;AAED,QAAA,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAsC5C,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../react/atom/textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,WAAW,oBAAqB,SAAQ,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,QAAA,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA6B5C,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -1,26 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { z } from 'zod';
4
2
  import styles from './textarea.module.scss';
5
- const Textarea = ({ validator, value, className, ...rest }) => {
6
- const [message, setMessage] = useState('');
7
- const [statusClass, setStatusClass] = useState('');
8
- const validateHandler = () => {
9
- setMessage('');
10
- setStatusClass('');
11
- if (validator && value.length > 0) {
12
- try {
13
- validator.parse(value);
14
- setStatusClass('success');
15
- }
16
- catch (e) {
17
- if (e instanceof z.ZodError) {
18
- setMessage(e.errors[0].message);
19
- setStatusClass('danger');
20
- }
21
- }
22
- }
23
- };
24
- return (_jsxs("div", { className: `${styles.style} ${className}`, children: [_jsx("textarea", { ...rest, value: value, className: `${statusClass} ${className}`, onKeyUp: validateHandler }), validator && message !== '' && (_jsx("div", { className: "validator", children: message }))] }));
3
+ import { useValidation } from '../hooks/useValidation';
4
+ const Textarea = ({ validator, value, className, id, ...rest }) => {
5
+ const textareaId = id || `textarea-${Math.random().toString(36).slice(2, 9)}`;
6
+ const errorId = `${textareaId}-error`;
7
+ const { message, statusClass, validate } = useValidation(validator);
8
+ return (_jsxs("div", { className: `${styles.style} ${className}`, children: [_jsx("textarea", { id: textareaId, ...rest, value: value, className: `${statusClass} ${className}`, onKeyUp: () => validate(value), "aria-invalid": statusClass === 'danger' ? true : undefined, "aria-describedby": message ? errorId : rest['aria-describedby'] }), validator && message !== '' && (_jsx("div", { id: errorId, className: "validator", role: "alert", children: message }))] }));
25
9
  };
26
10
  export default Textarea;
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ export interface ValidationResult {
3
+ message: string;
4
+ statusClass: string;
5
+ validate: (value: string | number | undefined) => void;
6
+ reset: () => void;
7
+ }
8
+ /**
9
+ * Custom hook for Zod validation
10
+ * @param validator - Zod schema for validation
11
+ * @returns Validation state and handlers
12
+ */
13
+ export declare const useValidation: (validator?: z.ZodType<unknown>) => ValidationResult;
14
+ export default useValidation;
15
+ //# sourceMappingURL=useValidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useValidation.d.ts","sourceRoot":"","sources":["../../../react/hooks/useValidation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACvD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAC7B,gBA+BF,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { z } from 'zod';
3
+ /**
4
+ * Custom hook for Zod validation
5
+ * @param validator - Zod schema for validation
6
+ * @returns Validation state and handlers
7
+ */
8
+ export const useValidation = (validator) => {
9
+ const [message, setMessage] = useState('');
10
+ const [statusClass, setStatusClass] = useState('');
11
+ const reset = useCallback(() => {
12
+ setMessage('');
13
+ setStatusClass('');
14
+ }, []);
15
+ const validate = useCallback((value) => {
16
+ reset();
17
+ if (!validator || !value) {
18
+ return;
19
+ }
20
+ try {
21
+ validator.parse(value);
22
+ setStatusClass('success');
23
+ }
24
+ catch (e) {
25
+ if (e instanceof z.ZodError) {
26
+ setMessage(e.errors[0].message);
27
+ setStatusClass('danger');
28
+ }
29
+ }
30
+ }, [validator, reset]);
31
+ return { message, statusClass, validate, reset };
32
+ };
33
+ export default useValidation;
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../react/molecule/table.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB;IAClB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACvE,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,yBAAyB;IACzB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,kBAAkB;IAClB,UAAU,EAAE,CAAC,EAAE,CAAC;IAChB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;IAC1C,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iBAAiB;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,yBAAyB;IACzB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iBAAS,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAChD,OAAO,EACP,UAAU,EACV,MAAM,EACN,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,UAAU,EACV,SAAS,GACV,EAAE,UAAU,CAAC,CAAC,CAAC,2CAgDf;kBAzDQ,KAAK;;;AA6Dd,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../react/molecule/table.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAE3C,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB;IAClB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACvE,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,yBAAyB;IACzB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,kBAAkB;IAClB,UAAU,EAAE,CAAC,EAAE,CAAC;IAChB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;IAC1C,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iBAAiB;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,yBAAyB;IACzB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iBAAS,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAChD,OAAO,EACP,UAAU,EACV,MAAM,EACN,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,UAAU,EACV,SAAS,GACV,EAAE,UAAU,CAAC,CAAC,CAAC,2CA+Df;kBAxEQ,KAAK;;;AA4Ed,eAAe,KAAK,CAAC"}
@@ -1,18 +1,28 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback } from 'react';
2
3
  function Table({ columns, dataSource, rowKey, list, border, fill, onRowClick, className, }) {
3
- const getRowKey = (record, index) => {
4
+ const getRowKey = useCallback((record, index) => {
4
5
  if (typeof rowKey === 'function') {
5
6
  return rowKey(record);
6
7
  }
7
8
  return String(record[rowKey] ?? index);
8
- };
9
+ }, [rowKey]);
10
+ const handleRowClick = useCallback((record, index) => {
11
+ onRowClick?.(record, index);
12
+ }, [onRowClick]);
13
+ const handleKeyDown = useCallback((e, record, index) => {
14
+ if (e.key === 'Enter' || e.key === ' ') {
15
+ e.preventDefault();
16
+ onRowClick?.(record, index);
17
+ }
18
+ }, [onRowClick]);
9
19
  const tableClass = [list && 'list', border && 'border', fill && 'fill', className]
10
20
  .filter(Boolean)
11
21
  .join(' ');
12
- return (_jsxs("table", { className: tableClass || undefined, children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((col) => (_jsx("th", { style: {
22
+ return (_jsxs("table", { className: tableClass || undefined, children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((col) => (_jsx("th", { scope: "col", style: {
13
23
  width: col.width,
14
24
  textAlign: col.align,
15
- }, children: col.title }, col.key))) }) }), _jsx("tbody", { children: dataSource.map((record, index) => (_jsx("tr", { onClick: () => onRowClick?.(record, index), style: onRowClick ? { cursor: 'pointer' } : undefined, children: columns.map((col) => (_jsx("td", { style: { textAlign: col.align }, children: col.render
25
+ }, children: col.title }, col.key))) }) }), _jsx("tbody", { children: dataSource.map((record, index) => (_jsx("tr", { onClick: () => handleRowClick(record, index), style: onRowClick ? { cursor: 'pointer' } : undefined, role: onRowClick ? 'button' : undefined, tabIndex: onRowClick ? 0 : undefined, onKeyDown: onRowClick ? (e) => handleKeyDown(e, record, index) : undefined, children: columns.map((col) => (_jsx("td", { style: { textAlign: col.align }, children: col.render
16
26
  ? col.render(record[col.key], record, index)
17
27
  : record[col.key] }, col.key))) }, getRowKey(record, index)))) })] }));
18
28
  }
@@ -40,9 +40,9 @@ export const ToastProvider = ({ children }) => {
40
40
  const hideToast = useCallback((id) => {
41
41
  setToasts((prev) => prev.filter((toast) => toast.id !== id));
42
42
  }, []);
43
- const getToastsByPosition = (position) => {
43
+ const getToastsByPosition = useCallback((position) => {
44
44
  return toasts.filter((toast) => toast.position === position);
45
- };
45
+ }, [toasts]);
46
46
  const positions = [
47
47
  'top-left',
48
48
  'top-center',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podo-ui",
3
- "version": "0.9.0",
3
+ "version": "0.9.3",
4
4
  "type": "module",
5
5
  "author": "hada0127 <work@tarucy.net>",
6
6
  "license": "MIT",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "homepage": "https://podoui.com",
15
15
  "main": "dist/index.js",
16
+ "module": "dist/index.js",
16
17
  "types": "dist/index.d.ts",
17
18
  "files": [
18
19
  "dist",
@@ -23,12 +24,14 @@
23
24
  "global.d.ts",
24
25
  "mixin.scss",
25
26
  "vite-fonts.scss",
26
- "README.md"
27
+ "README.md",
28
+ "README.ko.md"
27
29
  ],
28
30
  "exports": {
29
31
  ".": {
30
32
  "types": "./dist/index.d.ts",
31
33
  "import": "./dist/index.js",
34
+ "require": "./dist/index.js",
32
35
  "default": "./dist/index.js"
33
36
  },
34
37
  "./react/atom/*": {
@@ -79,7 +82,10 @@
79
82
  "prepublishOnly": "npm run build:all",
80
83
  "lint": "eslint .",
81
84
  "icon": "node ./cli/icon-scss.js",
82
- "deploy": "vite build && wrangler pages deploy dist/client"
85
+ "deploy": "vite build && wrangler pages deploy dist/client",
86
+ "test": "vitest",
87
+ "test:run": "vitest run",
88
+ "test:coverage": "vitest run --coverage"
83
89
  },
84
90
  "peerDependencies": {
85
91
  "react": "^18.0.0 || ^19.0.0",
@@ -105,6 +111,9 @@
105
111
  "devDependencies": {
106
112
  "@eslint/js": "^9.8.0",
107
113
  "@playwright/test": "^1.56.1",
114
+ "@testing-library/jest-dom": "^6.6.3",
115
+ "@testing-library/react": "^16.1.0",
116
+ "@testing-library/user-event": "^14.5.2",
108
117
  "@types/node": "22.10.2",
109
118
  "@types/react": "^18.3.3",
110
119
  "@types/react-dom": "^18.3.0",
@@ -125,8 +134,10 @@
125
134
  "scss": "^0.2.4",
126
135
  "typescript": "^5.5.3",
127
136
  "typescript-eslint": "^8.0.0",
137
+ "jsdom": "^25.0.1",
128
138
  "vike": "^0.4.182",
129
139
  "vike-react": "^0.4.18",
130
- "vite": "^5.4.0"
140
+ "vite": "^5.4.0",
141
+ "vitest": "^2.1.8"
131
142
  }
132
143
  }
@@ -87,101 +87,9 @@
87
87
  }
88
88
 
89
89
  /*
90
- Auto mode: Follow system preference
90
+ Dark mode color variables (shared mixin)
91
91
  */
92
- @media (prefers-color-scheme: dark) {
93
- :root:not([data-color-mode='light']) {
94
- --color-primary: #7c3aed;
95
- --color-primary-hover: #8b5cf6;
96
- --color-primary-pressed: #7c3aed;
97
- --color-primary-focus: #8b5cf6;
98
- --color-primary-fill: #111827;
99
- --color-primary-reverse: #ffffff;
100
- --color-primary-outline: rgba(158, 115, 254, 0.3);
101
- --color-default: #34343a;
102
- --color-default-hover: #3f3f46;
103
- --color-default-pressed: #34343a;
104
- --color-default-focus: #3f3f46;
105
- --color-default-fill: #34343a;
106
- --color-default-reverse: #ffffff;
107
- --color-default-outline: rgba(63, 63, 70, 0.3);
108
- --color-default-deep: #a1a1aa;
109
- --color-default-deep-hover: #d1d1d7;
110
- --color-default-deep-pressed: #a1a1aa;
111
- --color-default-deep-focus: #d1d1d7;
112
- --color-default-deep-fill: #52525b;
113
- --color-default-deep-reverse: #2c2c31;
114
- --color-default-deep-outline: rgba(209, 209, 215, 0.3);
115
- --color-info: #0a73eb;
116
- --color-info-hover: #1890ff;
117
- --color-info-pressed: #0a73eb;
118
- --color-info-focus: #1890ff;
119
- --color-info-fill: #1c1c20;
120
- --color-info-reverse: #ffffff;
121
- --color-info-outline: rgba(24, 144, 255, 0.3);
122
- --color-link: #0284c7;
123
- --color-link-hover: #0ea5e9;
124
- --color-link-pressed: #0284c7;
125
- --color-link-focus: #0ea5e9;
126
- --color-link-fill: #1c1c20;
127
- --color-link-reverse: #ffffff;
128
- --color-link-outline: rgba(14, 165, 233, 0.3);
129
- --color-success: #0d9488;
130
- --color-success-hover: #1bb0a2;
131
- --color-success-pressed: #0d9488;
132
- --color-success-focus: #1bb0a2;
133
- --color-success-fill: #1c1c20;
134
- --color-success-reverse: #ffffff;
135
- --color-success-outline: rgba(27, 176, 162, 0.3);
136
- --color-warning: #e8840f;
137
- --color-warning-hover: #f19b0b;
138
- --color-warning-pressed: #e8840f;
139
- --color-warning-focus: #f19b0b;
140
- --color-warning-fill: #1c1c20;
141
- --color-warning-reverse: #ffffff;
142
- --color-warning-outline: rgba(241, 155, 11, 0.3);
143
- --color-danger: #f04646;
144
- --color-danger-hover: #f25959;
145
- --color-danger-pressed: #f04646;
146
- --color-danger-focus: #f25959;
147
- --color-danger-fill: #1c1c20;
148
- --color-danger-reverse: #ffffff;
149
- --color-danger-outline: rgba(242, 89, 89, 0.3);
150
- --color-bg-modal: #2c2c31;
151
- --color-bg-disabled: #2c2c31;
152
- --color-bg-toggle: #52525b;
153
- --color-bg-indicator: rgba(255, 255, 255, 0.36);
154
- --color-bg-block: rgba(0, 0, 0, 0.09);
155
- --color-bg-reverse-wb: #000000;
156
- --color-bg-reverse-bw: #ffffff;
157
- --color-bg-wt: #ffffff;
158
- --color-bg-bk: #000000;
159
- --color-bg-elevation: #09090b;
160
- --color-bg-elevation-1: #18181b;
161
- --color-bg-elevation-2: #242429;
162
- --color-bg-elevation-3: #2c2c31;
163
- --color-border: #52525b;
164
- --color-border-hover: #71717a;
165
- --color-border-pressed: #52525b;
166
- --color-border-focus: #71717a;
167
- --color-border-disabled: rgba(255, 255, 255, 0.09);
168
- --color-border-alpha: rgba(255, 255, 255, 0.18);
169
- --color-text-header: #f4f4f5;
170
- --color-text-body: #e4e4e7;
171
- --color-text-sub: #a1a1aa;
172
- --color-text-action: #d1d1d7;
173
- --color-text-action-hover: #f4f4f5;
174
- --color-text-action-pressed: #d1d1d7;
175
- --color-text-action-focus: #f4f4f5;
176
- --color-text-action-disabled: #52525b;
177
- --color-text-action-reverse: #ffffff;
178
- }
179
- }
180
-
181
- html[data-color-mode='dark'] {
182
- /*
183
- Dark mode colors
184
- */
92
+ @mixin dark-mode-vars {
185
93
  --color-primary: #7c3aed;
186
94
  --color-primary-hover: #8b5cf6;
187
95
  --color-primary-pressed: #7c3aed;
@@ -267,3 +175,19 @@ html[data-color-mode='dark'] {
267
175
  --color-text-action-disabled: #52525b;
268
176
  --color-text-action-reverse: #ffffff;
269
177
  }
178
+
179
+ /*
180
+ Auto mode: Follow system preference
181
+ */
182
+ @media (prefers-color-scheme: dark) {
183
+ :root:not([data-color-mode='light']) {
184
+ @include dark-mode-vars;
185
+ }
186
+ }
187
+
188
+ /*
189
+ Manual dark mode
190
+ */
191
+ html[data-color-mode='dark'] {
192
+ @include dark-mode-vars;
193
+ }
Binary file
@@ -145,4 +145,7 @@ $icon-name: (
145
145
  login: \e97a,
146
146
  margin-horizontal: \e97b,
147
147
  margin-vertical: \e97c,
148
+ button: \e97d,
149
+ desktop: \e9a0,
150
+ mobile: \e9a1,
148
151
  );