@360crewing/ui 0.1.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +46 -0
  4. package/dist/components/Avatar.d.ts +20 -0
  5. package/dist/components/Avatar.js +17 -0
  6. package/dist/components/Badge.d.ts +19 -0
  7. package/dist/components/Badge.js +10 -0
  8. package/dist/components/Breadcrumbs.d.ts +19 -0
  9. package/dist/components/Breadcrumbs.js +12 -0
  10. package/dist/components/Button.d.ts +38 -0
  11. package/dist/components/Button.js +50 -0
  12. package/dist/components/Card.d.ts +12 -0
  13. package/dist/components/Card.js +6 -0
  14. package/dist/components/Checkbox.d.ts +14 -0
  15. package/dist/components/Checkbox.js +9 -0
  16. package/dist/components/CheckboxField.d.ts +16 -0
  17. package/dist/components/CheckboxField.js +17 -0
  18. package/dist/components/CollapsibleFields.d.ts +9 -0
  19. package/dist/components/CollapsibleFields.js +8 -0
  20. package/dist/components/ContentLoader.d.ts +8 -0
  21. package/dist/components/ContentLoader.js +14 -0
  22. package/dist/components/Delimeter.d.ts +3 -0
  23. package/dist/components/Delimeter.js +6 -0
  24. package/dist/components/DetailItem.d.ts +8 -0
  25. package/dist/components/DetailItem.js +6 -0
  26. package/dist/components/DropdownButton.d.ts +15 -0
  27. package/dist/components/DropdownButton.js +29 -0
  28. package/dist/components/FileUpload.d.ts +32 -0
  29. package/dist/components/FileUpload.js +75 -0
  30. package/dist/components/FormActionButtons.d.ts +18 -0
  31. package/dist/components/FormActionButtons.js +10 -0
  32. package/dist/components/Icon.d.ts +20 -0
  33. package/dist/components/Icon.js +11 -0
  34. package/dist/components/IconButton.d.ts +14 -0
  35. package/dist/components/IconButton.js +9 -0
  36. package/dist/components/InformationPanel.d.ts +14 -0
  37. package/dist/components/InformationPanel.js +6 -0
  38. package/dist/components/LayoutBlock.d.ts +6 -0
  39. package/dist/components/LayoutBlock.js +5 -0
  40. package/dist/components/Page.d.ts +12 -0
  41. package/dist/components/Page.js +6 -0
  42. package/dist/components/Pagination.d.ts +19 -0
  43. package/dist/components/Pagination.js +35 -0
  44. package/dist/components/Popover.d.ts +27 -0
  45. package/dist/components/Popover.js +130 -0
  46. package/dist/components/SearchInput.d.ts +27 -0
  47. package/dist/components/SearchInput.js +44 -0
  48. package/dist/components/ShadowedBlock.d.ts +9 -0
  49. package/dist/components/ShadowedBlock.js +6 -0
  50. package/dist/components/SidebarMenu.d.ts +27 -0
  51. package/dist/components/SidebarMenu.js +16 -0
  52. package/dist/components/SkeletonLoader.d.ts +4 -0
  53. package/dist/components/SkeletonLoader.js +7 -0
  54. package/dist/components/StatusBadge.d.ts +20 -0
  55. package/dist/components/StatusBadge.js +11 -0
  56. package/dist/components/Table.d.ts +39 -0
  57. package/dist/components/Table.js +24 -0
  58. package/dist/components/Tabs.d.ts +34 -0
  59. package/dist/components/Tabs.js +95 -0
  60. package/dist/components/Tag.d.ts +20 -0
  61. package/dist/components/Tag.js +11 -0
  62. package/dist/components/TextField.d.ts +45 -0
  63. package/dist/components/TextField.js +53 -0
  64. package/dist/components/TextareaField.d.ts +18 -0
  65. package/dist/components/TextareaField.js +11 -0
  66. package/dist/components/Toggle.d.ts +10 -0
  67. package/dist/components/Toggle.js +9 -0
  68. package/dist/components/ToggleField.d.ts +16 -0
  69. package/dist/components/ToggleField.js +17 -0
  70. package/dist/components/Tooltip.d.ts +25 -0
  71. package/dist/components/Tooltip.js +128 -0
  72. package/dist/index.d.ts +64 -0
  73. package/dist/index.js +35 -0
  74. package/dist/styles/Avatar.css +47 -0
  75. package/dist/styles/Badge.css +172 -0
  76. package/dist/styles/Breadcrumbs.css +54 -0
  77. package/dist/styles/Button.css +416 -0
  78. package/dist/styles/Card.css +34 -0
  79. package/dist/styles/Checkbox.css +102 -0
  80. package/dist/styles/CheckboxField.css +75 -0
  81. package/dist/styles/CollapsibleFields.css +53 -0
  82. package/dist/styles/Delimeter.css +7 -0
  83. package/dist/styles/DetailItem.css +18 -0
  84. package/dist/styles/DropdownButton.css +82 -0
  85. package/dist/styles/Error.css +14 -0
  86. package/dist/styles/FileUpload.css +113 -0
  87. package/dist/styles/Icon.css +12 -0
  88. package/dist/styles/IconButton.css +68 -0
  89. package/dist/styles/InformationPanel.css +84 -0
  90. package/dist/styles/Page.css +46 -0
  91. package/dist/styles/Pagination.css +150 -0
  92. package/dist/styles/Popover.css +28 -0
  93. package/dist/styles/ShadowedBlock.css +13 -0
  94. package/dist/styles/SidebarMenu.css +151 -0
  95. package/dist/styles/StatusBadge.css +63 -0
  96. package/dist/styles/Table.css +126 -0
  97. package/dist/styles/Tabs.css +193 -0
  98. package/dist/styles/Tag.css +110 -0
  99. package/dist/styles/TextField.css +276 -0
  100. package/dist/styles/Toggle.css +105 -0
  101. package/dist/styles/ToggleField.css +73 -0
  102. package/dist/styles/Tooltip.css +30 -0
  103. package/dist/styles/tokens.css +361 -0
  104. package/dist/styles/typography.css +169 -0
  105. package/dist/styles.css +33 -0
  106. package/package.json +50 -0
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import "../styles/Tabs.css";
5
+ const Tabs = ({ items, value, defaultValue, onChange, variant = "underline", size = "md", isFullWidth, className, style, "aria-label": ariaLabel, }) => {
6
+ const isControlled = value !== undefined;
7
+ const initial = defaultValue ?? items[0]?.id;
8
+ const [internal, setInternal] = useState(initial);
9
+ const active = isControlled ? value : internal;
10
+ const triggerRefs = useRef({});
11
+ const listRef = useRef(null);
12
+ // Sliding underline indicator (variant=underline): measured offsetLeft/Width
13
+ // of the active trigger on active-change / layout / resize.
14
+ const [slider, setSlider] = useState({ left: 0, width: 0, ready: false });
15
+ // Transitions off until first measurement paints, else underline animates from (0,0).
16
+ const [animate, setAnimate] = useState(false);
17
+ const measure = useCallback(() => {
18
+ const list = listRef.current;
19
+ if (!list || !active)
20
+ return;
21
+ const btn = triggerRefs.current[active];
22
+ if (!btn)
23
+ return;
24
+ // offsetLeft/Width: stable across scroll, no sub-pixel rounding (unlike getBoundingClientRect).
25
+ setSlider({ left: btn.offsetLeft, width: btn.offsetWidth, ready: true });
26
+ }, [active]);
27
+ useLayoutEffect(() => { measure(); }, [measure, items.length, variant, size]);
28
+ // Enable transitions one frame after first measured paint so later changes glide.
29
+ useEffect(() => {
30
+ if (!slider.ready || animate)
31
+ return;
32
+ const raf = requestAnimationFrame(() => setAnimate(true));
33
+ return () => cancelAnimationFrame(raf);
34
+ }, [slider.ready, animate]);
35
+ // Re-measure on window/container resize to keep the indicator glued on reflow.
36
+ useEffect(() => {
37
+ const list = listRef.current;
38
+ if (!list || typeof ResizeObserver === "undefined")
39
+ return;
40
+ const ro = new ResizeObserver(() => measure());
41
+ ro.observe(list);
42
+ window.addEventListener("resize", measure);
43
+ return () => {
44
+ ro.disconnect();
45
+ window.removeEventListener("resize", measure);
46
+ };
47
+ }, [measure]);
48
+ const activate = useCallback((id) => {
49
+ if (!isControlled)
50
+ setInternal(id);
51
+ onChange?.(id);
52
+ }, [isControlled, onChange]);
53
+ const onTriggerKeyDown = useCallback((e) => {
54
+ const enabled = items.filter(i => !i.isDisabled);
55
+ if (enabled.length === 0)
56
+ return;
57
+ const currentIdx = Math.max(0, enabled.findIndex(i => i.id === active));
58
+ let nextIdx = null;
59
+ switch (e.key) {
60
+ case "ArrowRight":
61
+ case "ArrowDown":
62
+ nextIdx = (currentIdx + 1) % enabled.length;
63
+ break;
64
+ case "ArrowLeft":
65
+ case "ArrowUp":
66
+ nextIdx = (currentIdx - 1 + enabled.length) % enabled.length;
67
+ break;
68
+ case "Home":
69
+ nextIdx = 0;
70
+ break;
71
+ case "End":
72
+ nextIdx = enabled.length - 1;
73
+ break;
74
+ default:
75
+ return;
76
+ }
77
+ e.preventDefault();
78
+ const next = enabled[nextIdx].id;
79
+ activate(next);
80
+ triggerRefs.current[next]?.focus();
81
+ }, [items, active, activate]);
82
+ const activePanel = useMemo(() => items.find(i => i.id === active)?.content, [items, active]);
83
+ return (_jsxs("div", { className: classNames("ds-tabs", `ds-tabs--${variant}`, `ds-tabs--${size}`, isFullWidth && "ds-tabs--full-width", className), style: style, children: [_jsxs("div", { ref: listRef, className: "ds-tabs__list", role: "tablist", "aria-label": ariaLabel, children: [items.map(it => {
84
+ const isActive = it.id === active;
85
+ return (_jsxs("button", { ref: el => { triggerRefs.current[it.id] = el; }, type: "button", role: "tab", id: `tab-${it.id}`, "aria-selected": isActive, "aria-controls": `tabpanel-${it.id}`, tabIndex: isActive ? 0 : -1, disabled: it.isDisabled, className: classNames("ds-tabs__trigger", isActive && "is-active", it.isDisabled && "is-disabled"), onClick: () => !it.isDisabled && activate(it.id), onKeyDown: onTriggerKeyDown, children: [it.icon && (_jsx("span", { className: "ds-tabs__icon", "aria-hidden": "true", children: it.icon })), _jsx("span", { className: "ds-tabs__label", children: it.label }), it.badge !== undefined && (_jsx("span", { className: "ds-tabs__badge", children: it.badge }))] }, it.id));
86
+ }), variant === "underline" && (_jsx("span", { className: "ds-tabs__indicator", "aria-hidden": "true", style: {
87
+ transform: `translateX(${slider.left}px)`,
88
+ width: slider.width,
89
+ // Hidden until first measurement (avoid a frame at left:0/width:0).
90
+ opacity: slider.ready ? 1 : 0,
91
+ // Snap on mount; transitions enable next frame so clicks glide.
92
+ transition: animate ? undefined : "none",
93
+ } }))] }), activePanel !== undefined && active && (_jsx("div", { className: "ds-tabs__panel", role: "tabpanel", id: `tabpanel-${active}`, "aria-labelledby": `tab-${active}`, children: activePanel }))] }));
94
+ };
95
+ export default Tabs;
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import "../styles/Tag.css";
3
+ export type TagColor = "brand" | "accent" | "success" | "warning" | "error" | "neutral";
4
+ export type TagStyle = "filled" | "soft";
5
+ export interface TagProps {
6
+ color?: TagColor;
7
+ tagStyle?: TagStyle;
8
+ children?: React.ReactNode;
9
+ leadingIcon?: React.ReactNode;
10
+ onRemove?: () => void;
11
+ className?: string;
12
+ style?: React.CSSProperties;
13
+ title?: string;
14
+ }
15
+ /**
16
+ * Tag — small pill for category / status / metadata labels.
17
+ * Mirrors Figma `Tag` set (6 colors × Filled / Soft).
18
+ */
19
+ declare const Tag: React.FC<TagProps>;
20
+ export default Tag;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import "../styles/Tag.css";
4
+ /**
5
+ * Tag — small pill for category / status / metadata labels.
6
+ * Mirrors Figma `Tag` set (6 colors × Filled / Soft).
7
+ */
8
+ const Tag = ({ color = "neutral", tagStyle = "soft", children, leadingIcon, onRemove, className, style, title, }) => {
9
+ return (_jsxs("span", { className: classNames("ds-tag", `ds-tag--${color}`, `ds-tag--${tagStyle}`, className), style: style, title: title, children: [leadingIcon && (_jsx("span", { className: "ds-tag__icon", "aria-hidden": "true", children: leadingIcon })), _jsx("span", { className: "ds-tag__label", children: children }), onRemove && (_jsx("button", { type: "button", className: "ds-tag__remove", onClick: onRemove, "aria-label": "Remove", children: "\u00D7" }))] }));
10
+ };
11
+ export default Tag;
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import "../styles/TextField.css";
3
+ import "../styles/Error.css";
4
+ export type TextFieldSize = "sm" | "md" | "lg";
5
+ export type TextFieldState = "default" | "error" | "success";
6
+ export interface TextFieldProps {
7
+ label?: string;
8
+ placeholder?: string;
9
+ name?: string;
10
+ value?: string | number | null;
11
+ maxLength?: number;
12
+ type?: "number" | "decimal" | "text" | "password" | "email" | "tel" | "url" | "search";
13
+ error?: string | {
14
+ message: string;
15
+ } | null;
16
+ onChange?: (value: string) => void;
17
+ onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
18
+ onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
19
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
20
+ isDisabled?: boolean;
21
+ style?: React.CSSProperties;
22
+ inputMode?: "text" | "decimal" | "numeric" | "tel" | "search" | "email" | "url";
23
+ /** Legacy layout switch — only honored when `size` is NOT provided. */
24
+ labelType?: "column" | "row";
25
+ /** Legacy ::before pseudo-element content. */
26
+ beforeContent?: string;
27
+ /** Legacy ::after pseudo-element content. */
28
+ afterContent?: string;
29
+ required?: boolean;
30
+ optional?: boolean;
31
+ unit?: string;
32
+ className?: string;
33
+ autoFocus?: boolean;
34
+ showErrorText?: boolean;
35
+ size?: TextFieldSize;
36
+ state?: TextFieldState;
37
+ leadingIcon?: React.ReactNode;
38
+ trailingIcon?: React.ReactNode;
39
+ /** Inline action button on the right (clear, show-password, …). */
40
+ trailingAction?: React.ReactNode;
41
+ helpText?: string;
42
+ id?: string;
43
+ }
44
+ declare const TextField: React.FC<TextFieldProps>;
45
+ export default TextField;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { useId, useRef } from "react";
4
+ import "../styles/TextField.css";
5
+ import "../styles/Error.css";
6
+ const TextField = ({ label, placeholder = "", name, value, maxLength, type = "text", error = null, onChange, isDisabled = false, onKeyDown, onFocus, onBlur, style, inputMode, labelType = "row", beforeContent, afterContent, required = false, optional = false, unit, className, autoFocus, showErrorText = true, size, state, leadingIcon, trailingIcon, trailingAction, helpText, id, }) => {
7
+ const inputRef = useRef(null);
8
+ const reactId = useId();
9
+ const inputId = id ?? `ds-field-${reactId}`;
10
+ const useDs = !!size;
11
+ const errorMessage = error
12
+ ? (typeof error === "string" ? error : error.message)
13
+ : null;
14
+ const effectiveState = state ?? (errorMessage ? "error" : "default");
15
+ const actualInputType = (type === "number" || type === "decimal") ? "text" : type;
16
+ const handleInputChange = (e) => {
17
+ const newValue = e.target.value;
18
+ if (maxLength && newValue.length > maxLength) {
19
+ return;
20
+ }
21
+ if (type === "decimal" || type === "number") {
22
+ const normalized = newValue.replace(/,/g, ".");
23
+ if (!/^-?\d*\.?\d*$/.test(normalized)) {
24
+ return;
25
+ }
26
+ onChange?.(normalized);
27
+ return;
28
+ }
29
+ onChange?.(newValue);
30
+ };
31
+ const handleKeyDown = (e) => {
32
+ if (e.key === "Enter") {
33
+ onChange?.(String(value));
34
+ }
35
+ onKeyDown?.(e);
36
+ };
37
+ /* ----- DS rendering ----- */
38
+ if (useDs) {
39
+ const describedById = errorMessage
40
+ ? `${inputId}-error`
41
+ : helpText
42
+ ? `${inputId}-hint`
43
+ : undefined;
44
+ return (_jsxs("div", { className: classNames("ds-field", className), style: style, children: [label && (_jsxs("label", { htmlFor: inputId, className: "ds-field__label", children: [label, required && _jsx("span", { className: "ds-field__required", "aria-hidden": "true", children: "*" }), optional && _jsx("span", { className: "ds-field__optional", children: "(optional)" })] })), _jsxs("div", { className: classNames("ds-input", `ds-input--${size}`, effectiveState === "error" && "is-error", effectiveState === "success" && "is-success", isDisabled && "is-disabled"), children: [leadingIcon && (_jsx("span", { className: "ds-input__addon ds-input__addon--leading", "aria-hidden": "true", children: leadingIcon })), _jsx("input", { id: inputId, ref: inputRef, name: name, placeholder: placeholder, value: value !== null && value !== undefined ? String(value) : "", onChange: handleInputChange, className: "ds-input__control", disabled: isDisabled, type: actualInputType, inputMode: inputMode || (type === "decimal" ? "decimal" : (type === "number" ? "numeric" : undefined)), maxLength: maxLength, onKeyDown: handleKeyDown, onFocus: onFocus, onBlur: onBlur, required: required, autoFocus: autoFocus, "aria-invalid": effectiveState === "error" || undefined, "aria-describedby": describedById }), unit && _jsx("span", { className: "ds-input__suffix", children: unit }), trailingIcon && (_jsx("span", { className: "ds-input__addon ds-input__addon--trailing", "aria-hidden": "true", children: trailingIcon })), trailingAction] }), errorMessage && showErrorText ? (_jsx("div", { id: `${inputId}-error`, className: "ds-field__error", role: "alert", children: errorMessage })) : helpText ? (_jsx("div", { id: `${inputId}-hint`, className: "ds-field__hint", children: helpText })) : null] }));
45
+ }
46
+ /* ----- Legacy rendering (unchanged behaviour) ----- */
47
+ return (_jsxs("div", { className: `text-field ${className || ""}${required ? " mandatory" : ""}`.trim(), style: style, "data-label-type": labelType, "data-before-content": beforeContent, "data-after-content": afterContent, "data-unit": unit, children: [label && (_jsx("div", { className: "label", children: label })), _jsxs("div", { className: "input-container", children: [_jsx("input", { ref: inputRef, name: name, placeholder: placeholder, value: value !== null && value !== undefined ? String(value) : "", onChange: handleInputChange, className: `form-control ${errorMessage ? "error-border" : ""}`.trim(), disabled: isDisabled, style: {
48
+ backgroundColor: isDisabled
49
+ ? "var(--disabled-background)"
50
+ : undefined,
51
+ }, type: actualInputType, inputMode: inputMode || (type === "decimal" ? "decimal" : (type === "number" ? "numeric" : undefined)), maxLength: maxLength, onKeyDown: handleKeyDown, onFocus: onFocus, onBlur: onBlur, required: required, autoFocus: autoFocus }), showErrorText && errorMessage && (_jsx("div", { className: "error-text", children: errorMessage }))] })] }));
52
+ };
53
+ export default TextField;
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import "../styles/TextField.css";
3
+ import "../styles/Error.css";
4
+ export interface TextareaFieldProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "onChange"> {
5
+ label?: string;
6
+ placeholder?: string;
7
+ name?: string;
8
+ value?: string;
9
+ maxLength?: number;
10
+ error?: string | null | {
11
+ message: string;
12
+ };
13
+ onChange?: (value: string) => void;
14
+ isDisabled?: boolean;
15
+ className?: string;
16
+ }
17
+ declare const TextareaField: React.FC<TextareaFieldProps>;
18
+ export default TextareaField;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { observer } from "mobx-react-lite";
3
+ import "../styles/TextField.css";
4
+ import "../styles/Error.css";
5
+ const TextareaField = observer(({ label, placeholder = "", name, value, maxLength, error = null, onChange, isDisabled = false, className = "", }) => {
6
+ const handleInputChange = (e) => {
7
+ onChange?.(e.target.value);
8
+ };
9
+ return (_jsxs("div", { className: `textarea-field ${className}`.trim(), children: [label && _jsx("div", { className: "label", children: label }), _jsxs("div", { className: "input-container", children: [_jsx("textarea", { name: name, placeholder: placeholder, value: value ?? "", maxLength: maxLength, onChange: handleInputChange, disabled: isDisabled, className: `form-control ${error ? "error-border" : ""}`.trim() }), error && (_jsx("div", { className: "error-text", children: typeof error === "string" ? error : error.message }))] })] }));
10
+ });
11
+ export default TextareaField;
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import "../styles/Toggle.css";
3
+ export type ToggleSize = "sm" | "md";
4
+ export interface ToggleProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
5
+ size?: ToggleSize;
6
+ label?: React.ReactNode;
7
+ wrapperClassName?: string;
8
+ }
9
+ declare const Toggle: React.ForwardRefExoticComponent<ToggleProps & React.RefAttributes<HTMLInputElement>>;
10
+ export default Toggle;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { forwardRef } from "react";
4
+ import "../styles/Toggle.css";
5
+ const Toggle = forwardRef(({ size = "md", label, className, wrapperClassName, disabled, ...inputProps }, ref) => {
6
+ return (_jsxs("label", { className: classNames("ds-toggle", `ds-toggle--${size}`, disabled && "ds-toggle--disabled", wrapperClassName), children: [_jsx("input", { ...inputProps, ref: ref, type: "checkbox", role: "switch", disabled: disabled, className: classNames("ds-toggle__input", className) }), _jsx("span", { className: "ds-toggle__track", "aria-hidden": "true", children: _jsx("span", { className: "ds-toggle__thumb" }) }), label !== undefined && (_jsx("span", { className: "ds-toggle__label", children: label }))] }));
7
+ });
8
+ Toggle.displayName = "Toggle";
9
+ export default Toggle;
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import "../styles/ToggleField.css";
3
+ import "../styles/Error.css";
4
+ export interface ToggleFieldProps {
5
+ label?: string | React.ReactNode;
6
+ value?: boolean;
7
+ onChange?: (checked: boolean) => void;
8
+ className?: string;
9
+ isDisabled?: boolean;
10
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
11
+ error?: string | {
12
+ message: string;
13
+ } | null;
14
+ }
15
+ declare const ToggleField: React.FC<ToggleFieldProps>;
16
+ export default ToggleField;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { observer } from "mobx-react-lite";
3
+ import "../styles/ToggleField.css";
4
+ import "../styles/Error.css";
5
+ const ToggleField = observer(({ label, value = false, onChange, className, isDisabled = false, inputProps, error = null, }) => {
6
+ const withStop = (handler) => (e) => {
7
+ e.stopPropagation();
8
+ if (handler)
9
+ handler(e);
10
+ };
11
+ const handleChange = (e) => {
12
+ e.stopPropagation();
13
+ onChange?.(e.target.checked);
14
+ };
15
+ return (_jsxs("div", { className: `toggle-field ${className || ""} ${error ? "error" : ""}`, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onDoubleClick: (e) => e.stopPropagation(), children: [label && _jsx("span", { className: "label", children: label }), _jsx("input", { type: "checkbox", checked: value, disabled: isDisabled, ...inputProps, onChange: handleChange, onClick: withStop(inputProps?.onClick), onMouseDown: withStop(inputProps?.onMouseDown), onMouseUp: withStop(inputProps?.onMouseUp), onDoubleClick: withStop(inputProps?.onDoubleClick), onKeyDown: withStop(inputProps?.onKeyDown) }), error && (_jsx("div", { className: "error-text", children: typeof error === "string" ? error : error.message }))] }));
16
+ });
17
+ export default ToggleField;
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import "../styles/Tooltip.css";
3
+ export type TooltipSide = "top" | "right" | "bottom" | "left";
4
+ export type TooltipAlign = "start" | "center" | "end";
5
+ export interface TooltipProps {
6
+ /** Tooltip content. If empty, the wrapper is rendered as-is. */
7
+ label: React.ReactNode;
8
+ /**
9
+ * The reference element. Must be a single element that forwards refs and accepts
10
+ * `onMouseEnter` / `onMouseLeave` / `onFocus` / `onBlur` event props.
11
+ */
12
+ children: React.ReactElement;
13
+ side?: TooltipSide;
14
+ align?: TooltipAlign;
15
+ /** Open/close delay in ms. Open default 120, close default 80. */
16
+ openDelay?: number;
17
+ closeDelay?: number;
18
+ /** Force-open for controlled cases (testing, etc.). */
19
+ isOpen?: boolean;
20
+ /** When true, hides the visible tooltip but keeps the underlying `aria-describedby`. */
21
+ isDisabled?: boolean;
22
+ className?: string;
23
+ }
24
+ declare const Tooltip: React.FC<TooltipProps>;
25
+ export default Tooltip;
@@ -0,0 +1,128 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { cloneElement, isValidElement, useCallback, useEffect, useId, useLayoutEffect, useRef, useState, } from "react";
4
+ import "../styles/Tooltip.css";
5
+ const computePosition = (anchor, tip, side, align, gap) => {
6
+ let top = 0;
7
+ let left = 0;
8
+ if (side === "top") {
9
+ top = anchor.top - tip.height - gap;
10
+ }
11
+ else if (side === "bottom") {
12
+ top = anchor.bottom + gap;
13
+ }
14
+ else if (side === "left") {
15
+ left = anchor.left - tip.width - gap;
16
+ }
17
+ else {
18
+ left = anchor.right + gap;
19
+ }
20
+ if (side === "top" || side === "bottom") {
21
+ if (align === "start")
22
+ left = anchor.left;
23
+ else if (align === "end")
24
+ left = anchor.right - tip.width;
25
+ else
26
+ left = anchor.left + (anchor.width - tip.width) / 2;
27
+ }
28
+ else {
29
+ if (align === "start")
30
+ top = anchor.top;
31
+ else if (align === "end")
32
+ top = anchor.bottom - tip.height;
33
+ else
34
+ top = anchor.top + (anchor.height - tip.height) / 2;
35
+ }
36
+ // Keep on-screen
37
+ const margin = 4;
38
+ left = Math.min(Math.max(margin, left), window.innerWidth - tip.width - margin);
39
+ top = Math.min(Math.max(margin, top), window.innerHeight - tip.height - margin);
40
+ return { top: top + window.scrollY, left: left + window.scrollX };
41
+ };
42
+ const Tooltip = ({ label, children, side = "top", align = "center", openDelay = 120, closeDelay = 80, isOpen, isDisabled, className, }) => {
43
+ const id = useId();
44
+ const anchorRef = useRef(null);
45
+ const tipRef = useRef(null);
46
+ const [open, setOpen] = useState(false);
47
+ const [pos, setPos] = useState(null);
48
+ const openTimer = useRef(null);
49
+ const closeTimer = useRef(null);
50
+ const clearTimers = () => {
51
+ if (openTimer.current) {
52
+ window.clearTimeout(openTimer.current);
53
+ openTimer.current = null;
54
+ }
55
+ if (closeTimer.current) {
56
+ window.clearTimeout(closeTimer.current);
57
+ closeTimer.current = null;
58
+ }
59
+ };
60
+ useEffect(() => clearTimers, []);
61
+ const show = useCallback(() => {
62
+ if (isDisabled || !label)
63
+ return;
64
+ clearTimers();
65
+ openTimer.current = window.setTimeout(() => setOpen(true), openDelay);
66
+ }, [isDisabled, label, openDelay]);
67
+ const hide = useCallback(() => {
68
+ clearTimers();
69
+ closeTimer.current = window.setTimeout(() => setOpen(false), closeDelay);
70
+ }, [closeDelay]);
71
+ const actuallyOpen = isOpen ?? open;
72
+ useLayoutEffect(() => {
73
+ if (!actuallyOpen || !anchorRef.current || !tipRef.current)
74
+ return;
75
+ const a = anchorRef.current.getBoundingClientRect();
76
+ const t = tipRef.current.getBoundingClientRect();
77
+ setPos(computePosition(a, t, side, align, 8));
78
+ }, [actuallyOpen, side, align, label]);
79
+ useEffect(() => {
80
+ if (!actuallyOpen)
81
+ return;
82
+ const onScrollOrResize = () => {
83
+ if (!anchorRef.current || !tipRef.current)
84
+ return;
85
+ const a = anchorRef.current.getBoundingClientRect();
86
+ const t = tipRef.current.getBoundingClientRect();
87
+ setPos(computePosition(a, t, side, align, 8));
88
+ };
89
+ window.addEventListener("scroll", onScrollOrResize, true);
90
+ window.addEventListener("resize", onScrollOrResize);
91
+ return () => {
92
+ window.removeEventListener("scroll", onScrollOrResize, true);
93
+ window.removeEventListener("resize", onScrollOrResize);
94
+ };
95
+ }, [actuallyOpen, side, align]);
96
+ if (!isValidElement(children)) {
97
+ return children;
98
+ }
99
+ const triggerProps = {
100
+ ref: (el) => {
101
+ anchorRef.current = el;
102
+ const r = children.ref;
103
+ if (typeof r === "function")
104
+ r(el);
105
+ else if (r && typeof r === "object")
106
+ r.current = el;
107
+ },
108
+ onMouseEnter: (e) => {
109
+ children.props.onMouseEnter?.(e);
110
+ show();
111
+ },
112
+ onMouseLeave: (e) => {
113
+ children.props.onMouseLeave?.(e);
114
+ hide();
115
+ },
116
+ onFocus: (e) => {
117
+ children.props.onFocus?.(e);
118
+ show();
119
+ },
120
+ onBlur: (e) => {
121
+ children.props.onBlur?.(e);
122
+ hide();
123
+ },
124
+ "aria-describedby": label ? id : undefined,
125
+ };
126
+ return (_jsxs(_Fragment, { children: [cloneElement(children, triggerProps), actuallyOpen && label && (_jsx("div", { ref: tipRef, id: id, role: "tooltip", className: classNames("ds-tooltip", `ds-tooltip--${side}`, className), style: pos ? { top: pos.top, left: pos.left } : { visibility: "hidden" }, children: label }))] }));
127
+ };
128
+ export default Tooltip;
@@ -0,0 +1,64 @@
1
+ export { default as Button } from "./components/Button";
2
+ export type { ButtonProps } from "./components/Button";
3
+ export { default as ContentLoader } from "./components/ContentLoader";
4
+ export type { ContentLoaderProps } from "./components/ContentLoader";
5
+ export { default as SkeletonLoader } from "./components/SkeletonLoader";
6
+ export { default as TextField } from "./components/TextField";
7
+ export type { TextFieldProps } from "./components/TextField";
8
+ export { default as TextareaField } from "./components/TextareaField";
9
+ export type { TextareaFieldProps } from "./components/TextareaField";
10
+ export { default as CheckboxField } from "./components/CheckboxField";
11
+ export type { CheckboxFieldProps } from "./components/CheckboxField";
12
+ export { default as ToggleField } from "./components/ToggleField";
13
+ export type { ToggleFieldProps } from "./components/ToggleField";
14
+ export { default as Icon } from "./components/Icon";
15
+ export type { IconProps } from "./components/Icon";
16
+ export { default as Card } from "./components/Card";
17
+ export type { CardProps } from "./components/Card";
18
+ export { default as Page } from "./components/Page";
19
+ export type { PageProps } from "./components/Page";
20
+ export { default as Badge, BadgeWithDot } from "./components/Badge";
21
+ export type { BadgeProps, BadgeWithDotProps, BadgeColor, BadgeSize } from "./components/Badge";
22
+ export { default as StatusBadge } from "./components/StatusBadge";
23
+ export type { StatusBadgeProps, StatusBadgeStatus } from "./components/StatusBadge";
24
+ export { default as Checkbox } from "./components/Checkbox";
25
+ export type { CheckboxProps, CheckboxSize } from "./components/Checkbox";
26
+ export { default as Toggle } from "./components/Toggle";
27
+ export type { ToggleProps, ToggleSize } from "./components/Toggle";
28
+ export { default as Tag } from "./components/Tag";
29
+ export type { TagProps, TagColor, TagStyle } from "./components/Tag";
30
+ export { default as Avatar } from "./components/Avatar";
31
+ export type { AvatarProps, AvatarSize } from "./components/Avatar";
32
+ export { default as IconButton } from "./components/IconButton";
33
+ export type { IconButtonProps, IconButtonSize, IconButtonStyle } from "./components/IconButton";
34
+ export { default as Breadcrumbs } from "./components/Breadcrumbs";
35
+ export type { BreadcrumbsProps, BreadcrumbItem } from "./components/Breadcrumbs";
36
+ export { default as SidebarMenu } from "./components/SidebarMenu";
37
+ export type { SidebarMenuProps, SidebarMenuItem, SidebarMenuItemState } from "./components/SidebarMenu";
38
+ export { default as Delimeter } from "./components/Delimeter";
39
+ export { default as ShadowedBlock } from "./components/ShadowedBlock";
40
+ export type { ShadowedBlockProps } from "./components/ShadowedBlock";
41
+ export { default as LayoutBlock } from "./components/LayoutBlock";
42
+ export type { LayoutBlockProps } from "./components/LayoutBlock";
43
+ export { default as DetailItem } from "./components/DetailItem";
44
+ export type { DetailItemProps } from "./components/DetailItem";
45
+ export { default as InformationPanel } from "./components/InformationPanel";
46
+ export type { InformationPanelProps, InformationItem } from "./components/InformationPanel";
47
+ export { default as DropdownButton } from "./components/DropdownButton";
48
+ export type { DropdownButtonProps, DropdownOption } from "./components/DropdownButton";
49
+ export { default as CollapsibleFields } from "./components/CollapsibleFields";
50
+ export type { CollapsibleFieldsProps } from "./components/CollapsibleFields";
51
+ export { default as Pagination } from "./components/Pagination";
52
+ export type { PaginationProps } from "./components/Pagination";
53
+ export { default as FormActionButtons } from "./components/FormActionButtons";
54
+ export type { FormActionButtonsProps } from "./components/FormActionButtons";
55
+ export { default as Tabs } from "./components/Tabs";
56
+ export type { TabsProps, TabItem, TabsVariant, TabsSize } from "./components/Tabs";
57
+ export { default as Tooltip } from "./components/Tooltip";
58
+ export type { TooltipProps, TooltipSide, TooltipAlign } from "./components/Tooltip";
59
+ export { default as Popover } from "./components/Popover";
60
+ export type { PopoverProps, PopoverSide, PopoverAlign } from "./components/Popover";
61
+ export { default as SearchInput } from "./components/SearchInput";
62
+ export type { SearchInputProps } from "./components/SearchInput";
63
+ export { default as FileUpload } from "./components/FileUpload";
64
+ export type { FileUploadProps, FileUploadSize, FileUploadState } from "./components/FileUpload";
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ export { default as Button } from "./components/Button";
2
+ export { default as ContentLoader } from "./components/ContentLoader";
3
+ export { default as SkeletonLoader } from "./components/SkeletonLoader";
4
+ export { default as TextField } from "./components/TextField";
5
+ export { default as TextareaField } from "./components/TextareaField";
6
+ export { default as CheckboxField } from "./components/CheckboxField";
7
+ export { default as ToggleField } from "./components/ToggleField";
8
+ export { default as Icon } from "./components/Icon";
9
+ export { default as Card } from "./components/Card";
10
+ export { default as Page } from "./components/Page";
11
+ export { default as Badge, BadgeWithDot } from "./components/Badge";
12
+ export { default as StatusBadge } from "./components/StatusBadge";
13
+ export { default as Checkbox } from "./components/Checkbox";
14
+ export { default as Toggle } from "./components/Toggle";
15
+ export { default as Tag } from "./components/Tag";
16
+ export { default as Avatar } from "./components/Avatar";
17
+ export { default as IconButton } from "./components/IconButton";
18
+ export { default as Breadcrumbs } from "./components/Breadcrumbs";
19
+ export { default as SidebarMenu } from "./components/SidebarMenu";
20
+ export { default as Delimeter } from "./components/Delimeter";
21
+ export { default as ShadowedBlock } from "./components/ShadowedBlock";
22
+ export { default as LayoutBlock } from "./components/LayoutBlock";
23
+ export { default as DetailItem } from "./components/DetailItem";
24
+ export { default as InformationPanel } from "./components/InformationPanel";
25
+ export { default as DropdownButton } from "./components/DropdownButton";
26
+ export { default as CollapsibleFields } from "./components/CollapsibleFields";
27
+ export { default as Pagination } from "./components/Pagination";
28
+ export { default as FormActionButtons } from "./components/FormActionButtons";
29
+ export { default as Tabs } from "./components/Tabs";
30
+ // Tooltip / Popover — primitive shells exported but not yet adopted in host.
31
+ // First real wiring should land on truncated table cells / kebab menus.
32
+ export { default as Tooltip } from "./components/Tooltip";
33
+ export { default as Popover } from "./components/Popover";
34
+ export { default as SearchInput } from "./components/SearchInput";
35
+ export { default as FileUpload } from "./components/FileUpload";
@@ -0,0 +1,47 @@
1
+ /* =====================================================================
2
+ * DS Avatar
3
+ * Mirrors Figma `Avatar` set (sm 32 / md 40 / lg 48).
4
+ * ===================================================================== */
5
+
6
+ .ds-avatar {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ border-radius: var(--radius-pill);
11
+ background-color: var(--surface-brand-minimal);
12
+ color: var(--text-brand);
13
+ font-family: var(--font-family-base);
14
+ font-weight: var(--font-weight-semibold);
15
+ overflow: hidden;
16
+ flex-shrink: 0;
17
+ }
18
+
19
+ .ds-avatar--sm {
20
+ width: 32px;
21
+ height: 32px;
22
+ font-size: var(--font-size-sm);
23
+ }
24
+
25
+ .ds-avatar--md {
26
+ width: 40px;
27
+ height: 40px;
28
+ font-size: var(--font-size-base);
29
+ }
30
+
31
+ .ds-avatar--lg {
32
+ width: 48px;
33
+ height: 48px;
34
+ font-size: var(--font-size-lg);
35
+ }
36
+
37
+ .ds-avatar__img {
38
+ width: 100%;
39
+ height: 100%;
40
+ object-fit: cover;
41
+ display: block;
42
+ }
43
+
44
+ .ds-avatar__initials {
45
+ line-height: 1;
46
+ letter-spacing: 0.02em;
47
+ }