@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@360crewing/ui` are documented here.
4
+ Format roughly follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## 0.1.0 — 2026-05-27
7
+
8
+ Initial public release. Extracted from `tenant-frontend/src/components`.
9
+
10
+ ### Primitives
11
+
12
+ - Layout: `Page`, `Card`, `ShadowedBlock`, `LayoutBlock`, `Delimeter`
13
+ - Form fields (legacy + DS): `TextField`, `TextareaField`, `CheckboxField`,
14
+ `ToggleField`, `Checkbox`, `Toggle`
15
+ - Buttons: `Button`, `IconButton`, `DropdownButton`, `FormActionButtons`
16
+ - Feedback: `Badge`, `BadgeWithDot`, `StatusBadge`, `Tag`, `Tooltip`, `Popover`,
17
+ `ContentLoader`, `SkeletonLoader`
18
+ - Navigation: `Tabs`, `Breadcrumbs`, `SidebarMenu`, `Pagination`
19
+ - Other: `Avatar`, `Icon`, `SearchInput`, `FileUpload`, `DetailItem`,
20
+ `InformationPanel`, `CollapsibleFields`
21
+
22
+ ### Foundations
23
+
24
+ - `styles/tokens.css` — colour, spacing, radius, shadow tokens
25
+ - `styles/typography.css` — Montserrat-based type scale
26
+
27
+ ### Engineering
28
+
29
+ - Pure ESM, `type: "module"`, exports map with `./styles.css` entry
30
+ - `prepublishOnly` runs `tsc -p tsconfig.build.json` + copies CSS
31
+ - Peer-deps: react 18 || 19, react-dom 18 || 19, mobx-react-lite 4, react-icons 5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 360crewing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @360crewing/ui
2
+
3
+ Pure visual UI primitives for the 360crewing platform. Used by the host
4
+ (`tenant-frontend`) and by marketplace extensions (via
5
+ `@360crewing/marketplace-sdk`, which re-exports this package).
6
+
7
+ ## Principles
8
+
9
+ - **Presentational components only.** No global state, managers, axios, or
10
+ MobX stores. Everything is driven through props.
11
+ - **No import-time side effects.** Importing CSS is the only exception.
12
+ - **Imperative platform services (Modal, Toast, Confirmation, Form) are NOT
13
+ here.** They live in the host as services; extensions reach them only through
14
+ SDK hooks (`useModal`, `useNotify`, `useConfirm`, `useForm`).
15
+
16
+ ## What's included
17
+
18
+ **Forms & inputs:** `Button`, `TextField`, `TextareaField`, `CheckboxField`,
19
+ `ToggleField`, `DropdownButton`, `FormActionButtons`.
20
+
21
+ **Layout & presentation:** `Card`, `Page`, `LayoutBlock`, `ShadowedBlock`,
22
+ `Delimeter`, `InformationPanel`, `DetailItem`, `Badge` (+ `BadgeWithDot`),
23
+ `Icon`.
24
+
25
+ **State & navigation:** `ContentLoader`, `SkeletonLoader`, `CollapsibleFields`,
26
+ `Pagination`.
27
+
28
+ ## i18n-aware components
29
+
30
+ `Pagination`, `CollapsibleFields`, and `FormActionButtons` accept their text
31
+ labels as props with English defaults. The host wraps them in thin i18n
32
+ adapters that call `useTranslation()` and pass localized strings down.
33
+ Extensions inject their own `useI18n()` from the SDK when using these
34
+ primitives directly.
35
+
36
+ ## Styles
37
+
38
+ Import the stylesheet once at the host entry point:
39
+
40
+ ```ts
41
+ import "@360crewing/ui/styles.css";
42
+ ```
43
+
44
+ ## Peer dependencies
45
+
46
+ `react`, `react-dom`, `mobx-react-lite`, `react-icons` — provided by the host.
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import "../styles/Avatar.css";
3
+ export type AvatarSize = "sm" | "md" | "lg";
4
+ export interface AvatarProps {
5
+ size?: AvatarSize;
6
+ /** Image URL. When provided, overrides `initials`. */
7
+ src?: string;
8
+ /** Up to 2 characters shown when `src` is missing or fails. */
9
+ initials?: string;
10
+ /** Accessible label (alt text or aria-label). */
11
+ name?: string;
12
+ className?: string;
13
+ style?: React.CSSProperties;
14
+ }
15
+ /**
16
+ * Avatar — circular user/entity indicator. Mirrors Figma `Avatar` set
17
+ * (sm 32 / md 40 / lg 48). Falls back to initials on `Surface/Brand-Minimal`.
18
+ */
19
+ declare const Avatar: React.FC<AvatarProps>;
20
+ export default Avatar;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import "../styles/Avatar.css";
4
+ /**
5
+ * Avatar — circular user/entity indicator. Mirrors Figma `Avatar` set
6
+ * (sm 32 / md 40 / lg 48). Falls back to initials on `Surface/Brand-Minimal`.
7
+ */
8
+ const Avatar = ({ size = "md", src, initials, name, className, style, }) => {
9
+ const shortInitials = (initials ?? name ?? "")
10
+ .split(/\s+/)
11
+ .map((s) => s[0] ?? "")
12
+ .join("")
13
+ .slice(0, 2)
14
+ .toUpperCase();
15
+ return (_jsx("span", { className: classNames("ds-avatar", `ds-avatar--${size}`, className), style: style, role: "img", "aria-label": name, children: src ? (_jsx("img", { className: "ds-avatar__img", src: src, alt: name ?? "" })) : (_jsx("span", { className: "ds-avatar__initials", children: shortInitials })) }));
16
+ };
17
+ export default Avatar;
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from "react";
2
+ import "../styles/Badge.css";
3
+ export type BadgeColor = "gray" | "brand" | "error" | "warning" | "success" | "slate" | "sky" | "blue" | "indigo" | "purple" | "pink" | "orange";
4
+ export type BadgeSize = "sm" | "md" | "lg";
5
+ export interface BadgeProps {
6
+ color?: BadgeColor;
7
+ size?: BadgeSize;
8
+ className?: string;
9
+ children: ReactNode;
10
+ }
11
+ declare const Badge: ({ color, size, className, children }: BadgeProps) => import("react/jsx-runtime").JSX.Element;
12
+ export default Badge;
13
+ export interface BadgeWithDotProps {
14
+ color?: BadgeColor;
15
+ size?: BadgeSize;
16
+ className?: string;
17
+ children: ReactNode;
18
+ }
19
+ export declare const BadgeWithDot: ({ color, size, className, children }: BadgeWithDotProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import "../styles/Badge.css";
4
+ const Badge = ({ color = "gray", size = "md", className, children }) => {
5
+ return _jsx("span", { className: classNames("badge", `badge--${size}`, `badge--${color}`, className), children: children });
6
+ };
7
+ export default Badge;
8
+ export const BadgeWithDot = ({ color = "gray", size = "md", className, children }) => {
9
+ return _jsxs("span", { className: classNames("badge", "badge--with-dot", `badge--${size}`, `badge--${color}`, className), children: [_jsx("span", { className: "badge__dot", "aria-hidden": true }), children] });
10
+ };
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import "../styles/Breadcrumbs.css";
3
+ export interface BreadcrumbItem {
4
+ label: React.ReactNode;
5
+ href?: string;
6
+ onClick?: (e: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void;
7
+ /** Marks the current page item — rendered non-interactive. */
8
+ isCurrent?: boolean;
9
+ }
10
+ export interface BreadcrumbsProps {
11
+ items: BreadcrumbItem[];
12
+ /** Custom separator. Defaults to `›`. */
13
+ separator?: React.ReactNode;
14
+ className?: string;
15
+ style?: React.CSSProperties;
16
+ }
17
+ /** Breadcrumbs — page-location trail (Figma `Breadcrumbs`). Last item is current/non-interactive; preceding are links. */
18
+ declare const Breadcrumbs: React.FC<BreadcrumbsProps>;
19
+ export default Breadcrumbs;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import "../styles/Breadcrumbs.css";
4
+ /** Breadcrumbs — page-location trail (Figma `Breadcrumbs`). Last item is current/non-interactive; preceding are links. */
5
+ const Breadcrumbs = ({ items, separator = "›", className, style, }) => {
6
+ return (_jsx("nav", { className: classNames("ds-breadcrumbs", className), style: style, "aria-label": "Breadcrumb", children: _jsx("ol", { className: "ds-breadcrumbs__list", children: items.map((it, i) => {
7
+ const isLast = i === items.length - 1;
8
+ const isCurrent = it.isCurrent ?? isLast;
9
+ return (_jsxs("li", { className: classNames("ds-breadcrumbs__item", isCurrent && "ds-breadcrumbs__item--current"), children: [isCurrent ? (_jsx("span", { className: "ds-breadcrumbs__current", "aria-current": "page", children: it.label })) : it.href ? (_jsx("a", { className: "ds-breadcrumbs__link", href: it.href, onClick: it.onClick, children: it.label })) : (_jsx("button", { type: "button", className: "ds-breadcrumbs__link", onClick: it.onClick, children: it.label })), !isLast && (_jsx("span", { className: "ds-breadcrumbs__separator", "aria-hidden": "true", children: separator }))] }, i));
10
+ }) }) }));
11
+ };
12
+ export default Breadcrumbs;
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import "../styles/Button.css";
3
+ export type ButtonVariant = "primary" | "secondary" | "tertiary" | "edit" | "additional" | "danger" | "ghost";
4
+ export type ButtonSize = "sm" | "md" | "lg";
5
+ interface IDefaultButtonProps {
6
+ className?: string;
7
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void | Promise<void>;
8
+ isDisabled?: boolean;
9
+ style?: React.CSSProperties;
10
+ children?: React.ReactNode;
11
+ ref?: React.RefObject<HTMLButtonElement>;
12
+ type?: "button" | "submit" | "reset";
13
+ preventDoubleClick?: boolean;
14
+ cooldownMs?: number;
15
+ lockWhilePending?: boolean;
16
+ /** DS variant → `.ds-button .ds-button--<variant> .ds-button--<size>`.
17
+ * Omitted → legacy `.button` class, so existing `className` callers keep working. */
18
+ variant?: ButtonVariant;
19
+ size?: ButtonSize;
20
+ fullWidth?: boolean;
21
+ iconBefore?: React.ReactNode;
22
+ iconAfter?: React.ReactNode;
23
+ isLoading?: boolean;
24
+ /** Accessibility — required if the button only contains an icon. */
25
+ "aria-label"?: string;
26
+ }
27
+ interface IButtonWithTitle extends IDefaultButtonProps {
28
+ title: string;
29
+ }
30
+ interface IButtonWithChildren extends IDefaultButtonProps {
31
+ title?: string;
32
+ children: React.ReactNode;
33
+ }
34
+ export type ButtonProps = IButtonWithTitle | IButtonWithChildren;
35
+ declare const Button: (({ title, className, onClick, isDisabled, style, children, ref, type, preventDoubleClick, cooldownMs, lockWhilePending, variant, size, fullWidth, iconBefore, iconAfter, isLoading, "aria-label": ariaLabel, }: ButtonProps) => import("react/jsx-runtime").JSX.Element) & {
36
+ displayName: string;
37
+ };
38
+ export default Button;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { observer } from "mobx-react-lite";
4
+ import { useRef } from "react";
5
+ import { ClipLoader } from "react-spinners";
6
+ import "../styles/Button.css";
7
+ const Button = observer(({ title, className, onClick, isDisabled, style, children, ref, type, preventDoubleClick = false, cooldownMs = 600, lockWhilePending = true, variant, size = "md", fullWidth, iconBefore, iconAfter, isLoading, "aria-label": ariaLabel, }) => {
8
+ const lockedRef = useRef(false);
9
+ const timerRef = useRef(null);
10
+ const releaseLockAfterCooldown = () => {
11
+ if (timerRef.current) {
12
+ window.clearTimeout(timerRef.current);
13
+ timerRef.current = null;
14
+ }
15
+ timerRef.current = window.setTimeout(() => {
16
+ lockedRef.current = false;
17
+ timerRef.current = null;
18
+ }, Math.max(0, cooldownMs || 0));
19
+ };
20
+ const onClickHandler = (e) => {
21
+ if (isDisabled || isLoading)
22
+ return;
23
+ if (preventDoubleClick) {
24
+ if (lockedRef.current)
25
+ return;
26
+ lockedRef.current = true;
27
+ }
28
+ const maybePromise = onClick?.(e);
29
+ if (preventDoubleClick) {
30
+ const isPromise = maybePromise && typeof maybePromise.then === "function";
31
+ if (isPromise && lockWhilePending !== false) {
32
+ maybePromise
33
+ .finally(() => {
34
+ releaseLockAfterCooldown();
35
+ });
36
+ }
37
+ else {
38
+ releaseLockAfterCooldown();
39
+ }
40
+ }
41
+ };
42
+ const effectiveDisabled = !!isDisabled || (!!preventDoubleClick && lockedRef.current);
43
+ const useDs = !!variant;
44
+ const composedClassName = classNames(
45
+ // Legacy `.button` only off the DS API; mixing both re-applies the old
46
+ // `min-width:100px; height:20px` reset and breaks DS sizing.
47
+ !useDs && "button", useDs && "ds-button", useDs && `ds-button--${variant}`, useDs && `ds-button--${size}`, useDs && fullWidth && "ds-button--full-width", useDs && isLoading && "is-loading", { disabled: effectiveDisabled, "is-disabled": useDs && effectiveDisabled }, className);
48
+ return (_jsxs("button", { ref: ref, className: composedClassName, onClick: onClickHandler, style: style, type: type, disabled: effectiveDisabled, "aria-label": ariaLabel, "aria-busy": isLoading || undefined, children: [useDs && iconBefore && (_jsx("span", { className: "ds-button__icon", "aria-hidden": "true", children: iconBefore })), children ?? title, useDs && iconAfter && (_jsx("span", { className: "ds-button__icon", "aria-hidden": "true", children: iconAfter })), useDs && isLoading && (_jsx(ClipLoader, { size: 14, color: "currentColor", cssOverride: { position: "absolute" } }))] }));
49
+ });
50
+ export default Button;
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import "../styles/Card.css";
3
+ export interface CardProps {
4
+ title?: React.ReactNode;
5
+ actions?: React.ReactNode;
6
+ children?: React.ReactNode;
7
+ className?: string;
8
+ style?: React.CSSProperties;
9
+ padding?: "none" | "sm" | "md" | "lg";
10
+ }
11
+ declare const Card: React.FC<CardProps>;
12
+ export default Card;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/Card.css";
3
+ const Card = ({ title, actions, children, className, style, padding = "md", }) => {
4
+ return (_jsxs("div", { className: `crewing-card crewing-card--pad-${padding} ${className || ""}`.trim(), style: style, children: [(title || actions) && (_jsxs("div", { className: "crewing-card__header", children: [title && _jsx("div", { className: "crewing-card__title", children: title }), actions && _jsx("div", { className: "crewing-card__actions", children: actions })] })), _jsx("div", { className: "crewing-card__body", children: children })] }));
5
+ };
6
+ export default Card;
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import "../styles/Checkbox.css";
3
+ export type CheckboxSize = "sm" | "md";
4
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
5
+ size?: CheckboxSize;
6
+ /** Renders the field in an error state (red border, optional `errorText`). */
7
+ isError?: boolean;
8
+ /** Label rendered next to the checkbox. */
9
+ label?: React.ReactNode;
10
+ /** Wrapper class applied to the outer <label>. */
11
+ wrapperClassName?: string;
12
+ }
13
+ declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
14
+ export default Checkbox;
@@ -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/Checkbox.css";
5
+ const Checkbox = forwardRef(({ size = "md", isError, label, className, wrapperClassName, disabled, ...inputProps }, ref) => {
6
+ return (_jsxs("label", { className: classNames("ds-checkbox", `ds-checkbox--${size}`, isError && "ds-checkbox--error", disabled && "ds-checkbox--disabled", wrapperClassName), children: [_jsx("input", { ...inputProps, ref: ref, type: "checkbox", disabled: disabled, "aria-invalid": isError || undefined, className: classNames("ds-checkbox__input", className) }), label !== undefined && (_jsx("span", { className: "ds-checkbox__label", children: label }))] }));
7
+ });
8
+ Checkbox.displayName = "Checkbox";
9
+ export default Checkbox;
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import "../styles/CheckboxField.css";
3
+ import "../styles/Error.css";
4
+ export interface CheckboxFieldProps {
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 CheckboxField: React.FC<CheckboxFieldProps>;
16
+ export default CheckboxField;
@@ -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/CheckboxField.css";
4
+ import "../styles/Error.css";
5
+ const CheckboxField = 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: `checkbox-field ${className || ""} ${error ? "error" : ""}`, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onDoubleClick: (e) => e.stopPropagation(), children: [_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) }), label && _jsx("span", { className: "label", children: label }), error && (_jsx("div", { className: "error-text", children: typeof error === "string" ? error : error.message }))] }));
16
+ });
17
+ export default CheckboxField;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import "../styles/CollapsibleFields.css";
3
+ export interface CollapsibleFieldsProps {
4
+ label?: string;
5
+ children: React.ReactNode;
6
+ defaultExpanded?: boolean;
7
+ }
8
+ declare const CollapsibleFields: React.FC<CollapsibleFieldsProps>;
9
+ export default CollapsibleFields;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import "../styles/CollapsibleFields.css";
4
+ const CollapsibleFields = ({ label = "Show full details", children, defaultExpanded = false, }) => {
5
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
6
+ return (_jsxs("div", { className: "collapsible-fields", children: [_jsxs("button", { type: "button", className: "collapsible-toggle", onClick: () => setIsExpanded(!isExpanded), children: [_jsx("span", { className: `arrow ${isExpanded ? "expanded" : ""}`, children: "\u25B6" }), label] }), isExpanded && (_jsx("div", { className: "collapsible-content", children: children }))] }));
7
+ };
8
+ export default CollapsibleFields;
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ export interface ContentLoaderProps {
3
+ color?: string;
4
+ size?: number;
5
+ minHeight?: number | string;
6
+ }
7
+ declare const ContentLoader: React.FC<ContentLoaderProps>;
8
+ export default ContentLoader;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ClipLoader } from "react-spinners";
3
+ const ContentLoader = ({ color = "#36d7b7", size = 50, minHeight = "60vh", }) => {
4
+ return (_jsx("div", { style: {
5
+ display: "flex",
6
+ justifyContent: "center",
7
+ alignItems: "center",
8
+ width: "100%",
9
+ height: "100%",
10
+ minHeight,
11
+ flex: 1,
12
+ }, children: _jsx(ClipLoader, { color: color, size: size }) }));
13
+ };
14
+ export default ContentLoader;
@@ -0,0 +1,3 @@
1
+ import "../styles/Delimeter.css";
2
+ declare const Delimeter: () => import("react/jsx-runtime").JSX.Element;
3
+ export default Delimeter;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "../styles/Delimeter.css";
3
+ const Delimeter = () => {
4
+ return _jsx("div", { className: "hr" });
5
+ };
6
+ export default Delimeter;
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import "../styles/DetailItem.css";
3
+ export interface DetailItemProps {
4
+ title: string | React.ReactNode;
5
+ value: string | React.ReactNode;
6
+ }
7
+ export declare const DetailItem: React.FC<DetailItemProps>;
8
+ export default DetailItem;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/DetailItem.css";
3
+ export const DetailItem = ({ title, value }) => {
4
+ return (_jsxs("div", { className: "detail-item", children: [_jsx("span", { children: title }), _jsx("span", { className: "dots" }), typeof value === "string" ? _jsx("span", { children: value }) : value] }));
5
+ };
6
+ export default DetailItem;
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import "../styles/DropdownButton.css";
3
+ export interface DropdownOption {
4
+ label: string;
5
+ onClick: () => void;
6
+ }
7
+ export interface DropdownButtonProps {
8
+ options: DropdownOption[];
9
+ className?: string;
10
+ children?: React.ReactNode;
11
+ onClick?: () => void;
12
+ isDisabled?: boolean;
13
+ }
14
+ declare const DropdownButton: React.FC<DropdownButtonProps>;
15
+ export default DropdownButton;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import classNames from "classnames";
4
+ import { LuChevronDown } from "react-icons/lu";
5
+ import Button from "./Button";
6
+ import "../styles/DropdownButton.css";
7
+ const DropdownButton = ({ options, className, children, onClick, isDisabled, }) => {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const containerRef = useRef(null);
10
+ useEffect(() => {
11
+ const handleClickOutside = (event) => {
12
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
13
+ setIsOpen(false);
14
+ }
15
+ };
16
+ document.addEventListener("mousedown", handleClickOutside);
17
+ return () => document.removeEventListener("mousedown", handleClickOutside);
18
+ }, []);
19
+ if (options.length <= 1) {
20
+ const singleOnClick = options.length === 1 ? options[0].onClick : onClick;
21
+ return (_jsx(Button, { className: className, onClick: singleOnClick, isDisabled: isDisabled, children: children }));
22
+ }
23
+ return (_jsxs("div", { className: classNames("dropdown-button-container", className, { disabled: isDisabled }), ref: containerRef, onClick: () => !isDisabled && setIsOpen(!isOpen), children: [_jsxs("div", { className: "button-group", children: [_jsx("div", { className: "main-button-content", children: children }), _jsx("div", { className: "dropdown-toggle", children: _jsx(LuChevronDown, {}) })] }), isOpen && (_jsx("div", { className: "dropdown-menu", children: options.map((option, index) => (_jsx("div", { className: "dropdown-item", onClick: (e) => {
24
+ e.stopPropagation();
25
+ option.onClick();
26
+ setIsOpen(false);
27
+ }, children: option.label }, index))) }))] }));
28
+ };
29
+ export default DropdownButton;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import "../styles/FileUpload.css";
3
+ export type FileUploadSize = "md" | "lg";
4
+ export type FileUploadState = "default" | "error" | "success";
5
+ export interface FileUploadProps {
6
+ /** Native MIME / extension list. Defaults to images. */
7
+ accept?: string;
8
+ /** Allow selecting multiple files. */
9
+ multiple?: boolean;
10
+ /** Disabled state. */
11
+ isDisabled?: boolean;
12
+ /** "lg" matches Figma "Image upload / Big"; "md" matches "Medium". */
13
+ size?: FileUploadSize;
14
+ state?: FileUploadState;
15
+ /** Heading text inside the drop zone. */
16
+ label?: React.ReactNode;
17
+ /** Sub-line beneath the label (file-size hint, allowed formats, …). */
18
+ helpText?: React.ReactNode;
19
+ /** Custom icon (defaults to a paperclip glyph). */
20
+ icon?: React.ReactNode;
21
+ /** Fired for accepted files (drag-drop OR click). */
22
+ onFilesSelected?: (files: File[]) => void;
23
+ /** Max size per file in bytes — files exceeding are rejected and reported via onReject. */
24
+ maxSizeBytes?: number;
25
+ onReject?: (reason: "size" | "type", file: File) => void;
26
+ className?: string;
27
+ style?: React.CSSProperties;
28
+ id?: string;
29
+ name?: string;
30
+ }
31
+ declare const FileUpload: React.FC<FileUploadProps>;
32
+ export default FileUpload;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { useCallback, useId, useRef, useState } from "react";
4
+ import "../styles/FileUpload.css";
5
+ const DefaultUploadIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", width: "32", height: "32", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M12 16V4" }), _jsx("path", { d: "M7 9l5-5 5 5" }), _jsx("path", { d: "M5 20h14" })] }));
6
+ const matchesAccept = (accept, file) => {
7
+ if (!accept)
8
+ return true;
9
+ const tokens = accept.split(",").map(t => t.trim().toLowerCase()).filter(Boolean);
10
+ if (tokens.length === 0)
11
+ return true;
12
+ const fname = file.name.toLowerCase();
13
+ const ftype = file.type.toLowerCase();
14
+ return tokens.some(tok => {
15
+ if (tok.startsWith("."))
16
+ return fname.endsWith(tok);
17
+ if (tok.endsWith("/*"))
18
+ return ftype.startsWith(tok.slice(0, -1));
19
+ return ftype === tok;
20
+ });
21
+ };
22
+ const FileUpload = ({ accept = "image/*", multiple, isDisabled, size = "lg", state = "default", label = "Click or drop files to upload", helpText, icon, onFilesSelected, maxSizeBytes, onReject, className, style, id, name, }) => {
23
+ const localId = useId();
24
+ const inputId = id ?? `ds-fileupload-${localId}`;
25
+ const inputRef = useRef(null);
26
+ const [isDragging, setDragging] = useState(false);
27
+ const handleFiles = useCallback((list) => {
28
+ if (!list || list.length === 0)
29
+ return;
30
+ const accepted = [];
31
+ for (let i = 0; i < list.length; i++) {
32
+ const f = list.item(i);
33
+ if (!f)
34
+ continue;
35
+ if (!matchesAccept(accept, f)) {
36
+ onReject?.("type", f);
37
+ continue;
38
+ }
39
+ if (maxSizeBytes !== undefined && f.size > maxSizeBytes) {
40
+ onReject?.("size", f);
41
+ continue;
42
+ }
43
+ accepted.push(f);
44
+ }
45
+ if (accepted.length)
46
+ onFilesSelected?.(accepted);
47
+ }, [accept, maxSizeBytes, onFilesSelected, onReject]);
48
+ const onDrop = (e) => {
49
+ e.preventDefault();
50
+ e.stopPropagation();
51
+ setDragging(false);
52
+ if (isDisabled)
53
+ return;
54
+ handleFiles(e.dataTransfer?.files ?? null);
55
+ };
56
+ const onDragOver = (e) => {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ if (isDisabled)
60
+ return;
61
+ setDragging(true);
62
+ };
63
+ const onDragLeave = (e) => {
64
+ e.preventDefault();
65
+ e.stopPropagation();
66
+ setDragging(false);
67
+ };
68
+ const onChange = (e) => {
69
+ handleFiles(e.target.files);
70
+ // Reset so picking the same file twice still fires onChange
71
+ e.target.value = "";
72
+ };
73
+ return (_jsxs("label", { htmlFor: inputId, className: classNames("ds-file-upload", `ds-file-upload--${size}`, `ds-file-upload--${state}`, isDragging && "is-dragging", isDisabled && "is-disabled", className), style: style, onDrop: onDrop, onDragOver: onDragOver, onDragLeave: onDragLeave, children: [_jsx("input", { ref: inputRef, id: inputId, name: name, type: "file", accept: accept, multiple: multiple, disabled: isDisabled, onChange: onChange, className: "ds-file-upload__input" }), _jsx("span", { className: "ds-file-upload__icon", "aria-hidden": "true", children: icon ?? _jsx(DefaultUploadIcon, {}) }), _jsx("span", { className: "ds-file-upload__label", children: label }), helpText && (_jsx("span", { className: "ds-file-upload__help", children: helpText }))] }));
74
+ };
75
+ export default FileUpload;
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ export interface FormActionButtonsProps {
3
+ isEditing: boolean;
4
+ onSaveEdit: () => Promise<void> | void;
5
+ onDelete?: () => Promise<void> | void;
6
+ isDisabled?: boolean;
7
+ addButtonText?: string;
8
+ children?: React.ReactNode;
9
+ variant?: "default" | "save-only" | "none";
10
+ /** Label of the save button when editing, default "Save". */
11
+ saveLabel?: string;
12
+ /** Label of the delete button when editing, default "Delete". */
13
+ deleteLabel?: string;
14
+ /** Label of the add button when creating, default "Add" (overridden by `addButtonText`). */
15
+ addLabel?: string;
16
+ }
17
+ declare const FormActionButtons: React.FC<FormActionButtonsProps>;
18
+ export default FormActionButtons;