@apliteni/apliteni-ui 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -407,3 +407,20 @@ native control props. `SelectField` accepts option children.
407
407
  `FileField` accepts one file from a picker or a drop and reports it through
408
408
  `onFileChange`. Use `accept` and `hint` to describe allowed file types and size.
409
409
  Your application must validate files before upload.
410
+
411
+ ## Toast
412
+
413
+ Wrap your app in `Toast`, then call `useToast()` inside it to add a notice.
414
+ Pass `title`, optional `text`, and a `tone`: success (default), danger, warn, info,
415
+ or neutral. Notices disappear after five seconds, pausing while hovered or focused.
416
+ Danger notices are announced assertively; other tones are polite. An `action` with a `label` and
417
+ `onClick` stays until selected or dismissed. New notices appear at the bottom.
418
+
419
+ ```tsx
420
+ function SaveButton() {
421
+ const push = useToast();
422
+ return <Button onClick={() => push({ title: 'Saved', text: 'Your changes were saved.' })}>Save</Button>;
423
+ }
424
+
425
+ <Toast><SaveButton /></Toast>
426
+ ```
@@ -512,3 +512,16 @@
512
512
  .ui-file.is-disabled .ui-file__input {
513
513
  cursor: not-allowed;
514
514
  }
515
+
516
+ /* src/Toast.css */
517
+ .rx-toast-stack {
518
+ position: fixed;
519
+ right: var(--space-4);
520
+ bottom: var(--space-4);
521
+ max-width: calc(100% - 2 * var(--space-4));
522
+ z-index: var(--z-toast);
523
+ pointer-events: none;
524
+ }
525
+ .rx-toast-stack > .ui-toast {
526
+ pointer-events: auto;
527
+ }
@@ -526,7 +526,22 @@ type FileFieldProps = FieldMessage & Omit<InputHTMLAttributes<HTMLInputElement>,
526
526
  };
527
527
  declare function FileField({ label, hint, error, required, disabled, onFileChange, className, ...props }: FileFieldProps): react.JSX.Element;
528
528
 
529
+ type ToastNotice = {
530
+ tone?: 'success' | 'danger' | 'warn' | 'info' | 'neutral';
531
+ title: string;
532
+ text?: string;
533
+ action?: {
534
+ label: string;
535
+ onClick: () => void;
536
+ };
537
+ };
538
+ type ToastProps = {
539
+ children: ReactNode;
540
+ };
541
+ declare function useToast(): (notice: ToastNotice) => void;
542
+ declare function Toast({ children }: ToastProps): react.JSX.Element;
543
+
529
544
  declare const PAGE_SIZES: readonly number[];
530
545
  declare const DEFAULT_PAGE_SIZE: number;
531
546
 
532
- export { BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, Timeline, type TimelineEvent, type TimelineProps, sortTableRows };
547
+ export { BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, Timeline, type TimelineEvent, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
@@ -2026,6 +2026,119 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
2026
2026
  ) });
2027
2027
  }
2028
2028
 
2029
+ // src/Toast.tsx
2030
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect8, useRef as useRef13, useState as useState8 } from "react";
2031
+ import { createPortal as createPortal4 } from "react-dom";
2032
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
2033
+ var Context = createContext2(null);
2034
+ var glyphs = { success: "circleCheck", danger: "circleX", warn: "circleAlert", info: "info", neutral: "bolt" };
2035
+ function useToast() {
2036
+ const push = useContext2(Context);
2037
+ if (!push) throw new Error("useToast requires a Toast provider");
2038
+ return push;
2039
+ }
2040
+ function Notice({ notice, remove }) {
2041
+ const root = useRef13(null);
2042
+ const [leaving2, setLeaving] = useState8(false);
2043
+ const dismissed = useRef13(false);
2044
+ const remaining = useRef13(5e3);
2045
+ const [hovered, setHovered] = useState8(false);
2046
+ const [focused, setFocused] = useState8(false);
2047
+ const paused = hovered || focused;
2048
+ const tone = notice.tone ?? "success";
2049
+ const dismiss = useCallback3(() => {
2050
+ if (dismissed.current) return;
2051
+ dismissed.current = true;
2052
+ if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) remove();
2053
+ else setLeaving(true);
2054
+ }, [remove]);
2055
+ useEffect8(() => {
2056
+ if (leaving2) {
2057
+ const element = root.current;
2058
+ const finish = (event) => {
2059
+ if (event.target === element) remove();
2060
+ };
2061
+ element?.addEventListener("animationend", finish);
2062
+ const timer = setTimeout(remove, 260);
2063
+ return () => {
2064
+ clearTimeout(timer);
2065
+ element?.removeEventListener("animationend", finish);
2066
+ };
2067
+ }
2068
+ }, [leaving2, remove]);
2069
+ useEffect8(() => {
2070
+ if (leaving2 || notice.action || paused) return;
2071
+ const started = Date.now();
2072
+ const timer = setTimeout(dismiss, remaining.current);
2073
+ return () => {
2074
+ clearTimeout(timer);
2075
+ remaining.current = Math.max(0, remaining.current - (Date.now() - started));
2076
+ };
2077
+ }, [dismiss, leaving2, notice.action, paused]);
2078
+ return /* @__PURE__ */ jsxs19(
2079
+ "div",
2080
+ {
2081
+ ref: root,
2082
+ className: `ui-toast ui-toast--${tone} ui-toast--soft${leaving2 ? " is-leaving" : ""}`,
2083
+ role: tone === "danger" ? "alert" : "status",
2084
+ "aria-live": tone === "danger" ? "assertive" : "polite",
2085
+ onMouseEnter: () => setHovered(true),
2086
+ onMouseLeave: () => setHovered(false),
2087
+ onFocus: () => setFocused(true),
2088
+ onBlur: (event) => {
2089
+ if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
2090
+ },
2091
+ children: [
2092
+ /* @__PURE__ */ jsx23("span", { className: "ui-toast__icon", children: /* @__PURE__ */ jsx23(Icon, { name: glyphs[tone] }) }),
2093
+ /* @__PURE__ */ jsxs19("div", { className: "ui-toast__body", children: [
2094
+ /* @__PURE__ */ jsx23("div", { className: "ui-toast__title", children: notice.title }),
2095
+ notice.text && /* @__PURE__ */ jsx23("div", { className: "ui-toast__text", children: notice.text })
2096
+ ] }),
2097
+ notice.action && /* @__PURE__ */ jsx23(
2098
+ "button",
2099
+ {
2100
+ type: "button",
2101
+ className: "ui-toast__action",
2102
+ disabled: leaving2,
2103
+ onClick: () => {
2104
+ if (!dismissed.current) {
2105
+ dismiss();
2106
+ notice.action.onClick();
2107
+ }
2108
+ },
2109
+ children: notice.action.label
2110
+ }
2111
+ ),
2112
+ /* @__PURE__ */ jsx23("button", { type: "button", className: "ui-toast__close", "aria-label": "Dismiss", disabled: leaving2, onClick: dismiss, children: /* @__PURE__ */ jsx23(Icon, { name: "x" }) }),
2113
+ !notice.action && /* @__PURE__ */ jsx23(
2114
+ "span",
2115
+ {
2116
+ className: "ui-toast__timer is-running",
2117
+ "aria-hidden": "true",
2118
+ style: { animationPlayState: paused ? "paused" : "running" }
2119
+ }
2120
+ )
2121
+ ]
2122
+ }
2123
+ );
2124
+ }
2125
+ function Toast({ children }) {
2126
+ const [entries, setEntries] = useState8([]);
2127
+ const nextId = useRef13(0);
2128
+ const push = useCallback3((notice) => {
2129
+ const id = nextId.current++;
2130
+ const remove = () => setEntries((current) => current.filter((entry) => entry.id !== id));
2131
+ setEntries((current) => [...current, { id, notice, remove }]);
2132
+ }, []);
2133
+ return /* @__PURE__ */ jsxs19(Context.Provider, { value: push, children: [
2134
+ children,
2135
+ typeof document !== "undefined" && createPortal4(
2136
+ /* @__PURE__ */ jsx23("div", { className: "ui-toast-stack rx-toast-stack", children: entries.map((entry) => /* @__PURE__ */ jsx23(Notice, { notice: entry.notice, remove: entry.remove }, entry.id)) }),
2137
+ document.body
2138
+ )
2139
+ ] });
2140
+ }
2141
+
2029
2142
  // src/index.ts
2030
2143
  var PAGE_SIZES = KIT_PAGE_SIZES;
2031
2144
  var DEFAULT_PAGE_SIZE3 = KIT_DEFAULT_PAGE_SIZE;
@@ -2063,5 +2176,7 @@ export {
2063
2176
  TextArea,
2064
2177
  TextField,
2065
2178
  Timeline,
2066
- sortTableRows
2179
+ Toast,
2180
+ sortTableRows,
2181
+ useToast
2067
2182
  };