@pitchfork-ui/react 0.7.0 → 0.8.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.
Files changed (47) hide show
  1. package/dist/components/AvatarGroup/AvatarGroup.css +26 -0
  2. package/dist/components/AvatarGroup/AvatarGroup2.js +37 -0
  3. package/dist/components/Calendar/Calendar.css +0 -1
  4. package/dist/components/Combobox/Combobox.css +155 -0
  5. package/dist/components/Combobox/Combobox2.js +191 -0
  6. package/dist/components/CommandPalette/CommandPalette.css +225 -0
  7. package/dist/components/CommandPalette/CommandPalette2.js +195 -0
  8. package/dist/components/DateRangePicker/DateRangePicker.css +258 -0
  9. package/dist/components/DateRangePicker/DateRangePicker2.js +378 -0
  10. package/dist/components/Icon/Icon2.js +43 -0
  11. package/dist/components/Kbd/Kbd.css +25 -0
  12. package/dist/components/Kbd/Kbd2.js +17 -0
  13. package/dist/components/NumberInput/NumberInput.css +98 -0
  14. package/dist/components/NumberInput/NumberInput2.js +165 -0
  15. package/dist/components/Popover/Popover.css +46 -0
  16. package/dist/components/Popover/Popover2.js +76 -0
  17. package/dist/components/Toast/Toast.js +129 -0
  18. package/dist/index.cjs +1190 -24
  19. package/dist/index.js +9 -1
  20. package/dist/src/components/AvatarGroup/AvatarGroup.d.ts +14 -0
  21. package/dist/src/components/AvatarGroup/AvatarGroup.test.d.ts +1 -0
  22. package/dist/src/components/AvatarGroup/index.d.ts +1 -0
  23. package/dist/src/components/Combobox/Combobox.d.ts +20 -0
  24. package/dist/src/components/Combobox/Combobox.test.d.ts +1 -0
  25. package/dist/src/components/Combobox/index.d.ts +1 -0
  26. package/dist/src/components/CommandPalette/CommandPalette.d.ts +18 -0
  27. package/dist/src/components/CommandPalette/CommandPalette.test.d.ts +1 -0
  28. package/dist/src/components/CommandPalette/index.d.ts +1 -0
  29. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts +21 -0
  30. package/dist/src/components/DateRangePicker/DateRangePicker.test.d.ts +1 -0
  31. package/dist/src/components/DateRangePicker/index.d.ts +1 -0
  32. package/dist/src/components/Kbd/Kbd.d.ts +9 -0
  33. package/dist/src/components/Kbd/Kbd.test.d.ts +1 -0
  34. package/dist/src/components/Kbd/index.d.ts +1 -0
  35. package/dist/src/components/NumberInput/NumberInput.d.ts +19 -0
  36. package/dist/src/components/NumberInput/NumberInput.test.d.ts +1 -0
  37. package/dist/src/components/NumberInput/index.d.ts +1 -0
  38. package/dist/src/components/Popover/Popover.d.ts +21 -0
  39. package/dist/src/components/Popover/Popover.test.d.ts +1 -0
  40. package/dist/src/components/Popover/index.d.ts +1 -0
  41. package/dist/src/components/Toast/Toast.d.ts +35 -0
  42. package/dist/src/components/Toast/Toast.test.d.ts +1 -0
  43. package/dist/src/components/Toast/index.d.ts +1 -0
  44. package/dist/src/index.d.ts +8 -0
  45. package/dist/styles/theme.css +68 -0
  46. package/dist/styles.css +977 -77
  47. package/package.json +1 -1
@@ -0,0 +1,165 @@
1
+ import { Keys, composeDescribedBy } from "../../a11y/index.js";
2
+ import { useComposedRefs } from "../../hooks/useComposedRefs.js";
3
+ import { useControllableState } from "../../hooks/useControllableState.js";
4
+ import { cx } from "../../utils/cx.js";
5
+ import { Icon } from "../Icon/Icon2.js";
6
+ import { FieldWrapper } from "../../utils/FieldWrapper.js";
7
+ import './NumberInput.css';/* empty css */
8
+ import { forwardRef, useId, useRef, useState } from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/components/NumberInput/NumberInput.tsx
11
+ var decimalsOf = (n) => {
12
+ const str = String(n);
13
+ const dot = str.indexOf(".");
14
+ return dot === -1 ? 0 : str.length - dot - 1;
15
+ };
16
+ var clamp = (n, min, max) => Math.min(Math.max(n, min), max);
17
+ var NumberInput = forwardRef(function NumberInput({ id, value, defaultValue, onValueChange, min = -Infinity, max = Infinity, step = 1, label, description, error, formatOptions, locale, decrementLabel = "Decrease", incrementLabel = "Increase", name, required, disabled, className, placeholder, "aria-describedby": ariaDescribedBy, ...props }, ref) {
18
+ const generatedId = useId();
19
+ const fieldId = id ?? generatedId;
20
+ const descriptionId = description ? `${fieldId}-description` : void 0;
21
+ const errorId = error ? `${fieldId}-error` : void 0;
22
+ const describedBy = composeDescribedBy(ariaDescribedBy, descriptionId, errorId);
23
+ const [rawValue, setCurrentValue] = useControllableState({
24
+ value,
25
+ defaultValue: defaultValue ?? null,
26
+ onChange: onValueChange
27
+ });
28
+ const currentValue = rawValue ?? null;
29
+ const inputRefs = useComposedRefs(useRef(null), ref);
30
+ const stepDecimals = decimalsOf(step);
31
+ const round = (n) => {
32
+ const factor = 10 ** stepDecimals;
33
+ return Math.round(n * factor) / factor;
34
+ };
35
+ const formatValue = (n) => {
36
+ if (n === null) return "";
37
+ if (formatOptions) return new Intl.NumberFormat(locale, formatOptions).format(n);
38
+ return String(n);
39
+ };
40
+ const [focused, setFocused] = useState(false);
41
+ const [draft, setDraft] = useState(() => formatValue(currentValue));
42
+ const displayValue = focused ? draft : formatValue(currentValue);
43
+ const commit = (next) => {
44
+ if (next === null) {
45
+ setCurrentValue(null);
46
+ setDraft("");
47
+ return;
48
+ }
49
+ const clamped = round(clamp(next, min, max));
50
+ setCurrentValue(clamped);
51
+ setDraft(focused ? String(clamped) : formatValue(clamped));
52
+ };
53
+ const stepBy = (direction) => {
54
+ if (disabled) return;
55
+ commit((currentValue ?? (Number.isFinite(min) ? min : Number.isFinite(max) ? max : 0)) + direction * step);
56
+ };
57
+ const atMin = currentValue !== null && currentValue <= min;
58
+ const atMax = currentValue !== null && currentValue >= max;
59
+ const onKeyDown = (event) => {
60
+ if (disabled) return;
61
+ if (event.key === Keys.ArrowUp) {
62
+ event.preventDefault();
63
+ stepBy(1);
64
+ } else if (event.key === Keys.ArrowDown) {
65
+ event.preventDefault();
66
+ stepBy(-1);
67
+ } else if (event.key === Keys.Home && Number.isFinite(min)) {
68
+ event.preventDefault();
69
+ commit(min);
70
+ } else if (event.key === Keys.End && Number.isFinite(max)) {
71
+ event.preventDefault();
72
+ commit(max);
73
+ }
74
+ };
75
+ return /* @__PURE__ */ jsx(FieldWrapper, {
76
+ labelFor: fieldId,
77
+ label,
78
+ description,
79
+ descriptionId,
80
+ error,
81
+ errorId,
82
+ required,
83
+ children: /* @__PURE__ */ jsxs("div", {
84
+ className: cx("pf-numberinput", error && "pf-numberinput--invalid"),
85
+ children: [
86
+ /* @__PURE__ */ jsx("button", {
87
+ type: "button",
88
+ className: "pf-numberinput__step pf-numberinput__step--decrement",
89
+ "aria-label": decrementLabel,
90
+ disabled: disabled || atMin,
91
+ tabIndex: -1,
92
+ onMouseDown: (event) => event.preventDefault(),
93
+ onClick: () => stepBy(-1),
94
+ children: /* @__PURE__ */ jsx(Icon, {
95
+ name: "minus",
96
+ "aria-hidden": true
97
+ })
98
+ }),
99
+ /* @__PURE__ */ jsx("input", {
100
+ ...props,
101
+ id: fieldId,
102
+ ref: inputRefs,
103
+ type: "text",
104
+ inputMode: "decimal",
105
+ role: "spinbutton",
106
+ className: cx("pf-numberinput__input", className),
107
+ value: displayValue,
108
+ placeholder,
109
+ disabled,
110
+ required,
111
+ autoComplete: "off",
112
+ "aria-invalid": error ? true : void 0,
113
+ "aria-describedby": describedBy,
114
+ "aria-valuenow": currentValue ?? void 0,
115
+ "aria-valuemin": Number.isFinite(min) ? min : void 0,
116
+ "aria-valuemax": Number.isFinite(max) ? max : void 0,
117
+ onFocus: () => {
118
+ setFocused(true);
119
+ setDraft(currentValue === null ? "" : String(currentValue));
120
+ },
121
+ onChange: (event) => {
122
+ const raw = event.target.value;
123
+ setDraft(raw);
124
+ if (raw.trim() === "") {
125
+ setCurrentValue(null);
126
+ return;
127
+ }
128
+ const parsed = Number(raw);
129
+ if (!Number.isNaN(parsed)) setCurrentValue(round(clamp(parsed, min, max)));
130
+ },
131
+ onBlur: () => {
132
+ setFocused(false);
133
+ if (draft.trim() === "") commit(null);
134
+ else {
135
+ const parsed = Number(draft);
136
+ commit(Number.isNaN(parsed) ? currentValue : parsed);
137
+ }
138
+ },
139
+ onKeyDown
140
+ }),
141
+ /* @__PURE__ */ jsx("button", {
142
+ type: "button",
143
+ className: "pf-numberinput__step pf-numberinput__step--increment",
144
+ "aria-label": incrementLabel,
145
+ disabled: disabled || atMax,
146
+ tabIndex: -1,
147
+ onMouseDown: (event) => event.preventDefault(),
148
+ onClick: () => stepBy(1),
149
+ children: /* @__PURE__ */ jsx(Icon, {
150
+ name: "plus",
151
+ "aria-hidden": true
152
+ })
153
+ }),
154
+ name ? /* @__PURE__ */ jsx("input", {
155
+ type: "hidden",
156
+ name,
157
+ value: currentValue ?? ""
158
+ }) : null
159
+ ]
160
+ })
161
+ });
162
+ });
163
+ NumberInput.displayName = "NumberInput";
164
+ //#endregion
165
+ export { NumberInput };
@@ -0,0 +1,46 @@
1
+ .pf-popover {
2
+ position: fixed;
3
+ z-index: 1100;
4
+ max-width: min(360px, calc(100vw - 16px));
5
+ background: var(--pf-popover-bg);
6
+ border: 1px solid var(--pf-popover-border);
7
+ border-radius: var(--radius-md);
8
+ box-shadow: var(--pf-popover-elevation-popover-shadow, var(--pf-elevation-popover-shadow));
9
+ color: var(--pf-popover-text);
10
+ padding: var(--space-4);
11
+ transform-origin: top center;
12
+ animation: pf-popover-in var(--duration-fast) var(--easing-decelerate);
13
+ }
14
+
15
+ .pf-popover:focus-visible {
16
+ outline: none;
17
+ }
18
+
19
+ @keyframes pf-popover-in {
20
+ from {
21
+ opacity: 0;
22
+ transform: translateY(-4px) scale(0.97);
23
+ }
24
+ to {
25
+ opacity: 1;
26
+ transform: translateY(0) scale(1);
27
+ }
28
+ }
29
+
30
+ .pf-popover--exiting {
31
+ animation: pf-popover-out var(--duration-fast) var(--easing-accelerate) forwards;
32
+ }
33
+
34
+ @keyframes pf-popover-out {
35
+ to {
36
+ opacity: 0;
37
+ transform: translateY(-4px) scale(0.97);
38
+ }
39
+ }
40
+
41
+ @media (prefers-reduced-motion: reduce) {
42
+ .pf-popover,
43
+ .pf-popover--exiting {
44
+ animation: none;
45
+ }
46
+ }
@@ -0,0 +1,76 @@
1
+ import { Keys } from "../../a11y/index.js";
2
+ import { useAnchoredPosition } from "../../hooks/useAnchoredPosition.js";
3
+ import { useComposedRefs } from "../../hooks/useComposedRefs.js";
4
+ import { useControllableState } from "../../hooks/useControllableState.js";
5
+ import { useOutsideInteraction } from "../../hooks/useOutsideInteraction.js";
6
+ import { usePresence } from "../../hooks/usePresence.js";
7
+ import { cx } from "../../utils/cx.js";
8
+ import './Popover.css';/* empty css */
9
+ import { cloneElement, useEffect, useId, useRef } from "react";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ import { createPortal } from "react-dom";
12
+ //#region src/components/Popover/Popover.tsx
13
+ function Popover({ trigger, children, open, defaultOpen, onOpenChange, align = "start", label, closeOnOutsideClick = true, className }) {
14
+ const [isOpen = false, setOpen] = useControllableState({
15
+ value: open,
16
+ defaultValue: defaultOpen ?? false,
17
+ onChange: onOpenChange
18
+ });
19
+ const contentId = useId();
20
+ const triggerRef = useRef(null);
21
+ const contentRef = useRef(null);
22
+ const { isMounted, isExiting } = usePresence(isOpen, 160);
23
+ const style = useAnchoredPosition({
24
+ anchorRef: triggerRef,
25
+ floatingRef: contentRef,
26
+ enabled: isOpen,
27
+ align,
28
+ matchAnchorWidth: false,
29
+ flip: true
30
+ });
31
+ useOutsideInteraction({
32
+ refs: [triggerRef, contentRef],
33
+ enabled: isOpen,
34
+ onInteractOutside: () => {
35
+ if (closeOnOutsideClick) setOpen(false);
36
+ }
37
+ });
38
+ const wasOpen = useRef(false);
39
+ useEffect(() => {
40
+ if (isOpen) {
41
+ wasOpen.current = true;
42
+ contentRef.current?.focus();
43
+ } else if (wasOpen.current) {
44
+ wasOpen.current = false;
45
+ triggerRef.current?.focus();
46
+ }
47
+ }, [isOpen]);
48
+ const triggerProps = trigger.props;
49
+ return /* @__PURE__ */ jsxs(Fragment, { children: [cloneElement(trigger, {
50
+ ref: useComposedRefs(triggerRef, trigger.ref ?? triggerProps.ref),
51
+ "aria-haspopup": "dialog",
52
+ "aria-expanded": isOpen,
53
+ onClick: (event) => {
54
+ triggerProps.onClick?.(event);
55
+ setOpen(!isOpen);
56
+ }
57
+ }), isMounted && typeof document !== "undefined" ? createPortal(/* @__PURE__ */ jsx("div", {
58
+ ref: contentRef,
59
+ id: contentId,
60
+ role: "dialog",
61
+ "aria-label": label,
62
+ tabIndex: -1,
63
+ className: cx("pf-popover", isExiting && "pf-popover--exiting", className),
64
+ style,
65
+ onKeyDown: (event) => {
66
+ if (event.key === Keys.Escape) {
67
+ event.stopPropagation();
68
+ setOpen(false);
69
+ }
70
+ },
71
+ children
72
+ }), document.body) : null] });
73
+ }
74
+ Popover.displayName = "Popover";
75
+ //#endregion
76
+ export { Popover };
@@ -0,0 +1,129 @@
1
+ import { Notification, NotificationStack } from "../Notification/Notification2.js";
2
+ import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { createPortal } from "react-dom";
5
+ //#region src/components/Toast/Toast.tsx
6
+ var _emit = null;
7
+ function ToastItem({ entry, onRemove }) {
8
+ const { id, shouldExit } = entry;
9
+ const handleRemove = useCallback(() => onRemove(id), [onRemove, id]);
10
+ useEffect(() => {
11
+ if (!shouldExit) return;
12
+ const t = setTimeout(handleRemove, 270);
13
+ return () => clearTimeout(t);
14
+ }, [shouldExit, handleRemove]);
15
+ return /* @__PURE__ */ jsx(Notification, {
16
+ variant: entry.variant,
17
+ heading: entry.heading,
18
+ description: entry.description,
19
+ action: entry.action,
20
+ dismissible: entry.dismissible,
21
+ onDismiss: handleRemove,
22
+ className: shouldExit ? "pf-notification--exiting" : void 0,
23
+ onAnimationEnd: (e) => {
24
+ if (shouldExit && e.currentTarget === e.target) handleRemove();
25
+ }
26
+ });
27
+ }
28
+ function ToastProvider({ children, placement = "top-right", defaultDuration = 4e3 }) {
29
+ const [entries, setEntries] = useState([]);
30
+ const timers = useRef(/* @__PURE__ */ new Map());
31
+ const remove = useCallback((id) => {
32
+ timers.current.delete(id);
33
+ setEntries((prev) => prev.filter((e) => e.id !== id));
34
+ }, []);
35
+ const signalExit = useCallback((id) => {
36
+ timers.current.delete(id);
37
+ setEntries((prev) => prev.map((e) => e.id === id ? {
38
+ ...e,
39
+ shouldExit: true
40
+ } : e));
41
+ }, []);
42
+ const toast = useCallback((options) => {
43
+ const id = `t-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
44
+ const duration = options.duration !== void 0 ? options.duration : defaultDuration;
45
+ setEntries((prev) => [{
46
+ ...options,
47
+ id,
48
+ shouldExit: false,
49
+ dismissible: options.dismissible ?? true
50
+ }, ...prev]);
51
+ if (typeof duration === "number" && duration > 0) {
52
+ const t = setTimeout(() => signalExit(id), duration);
53
+ timers.current.set(id, t);
54
+ }
55
+ return id;
56
+ }, [defaultDuration, signalExit]);
57
+ const dismiss = useCallback((id) => {
58
+ const t = timers.current.get(id);
59
+ if (t) clearTimeout(t);
60
+ signalExit(id);
61
+ }, [signalExit]);
62
+ const dismissAll = useCallback(() => {
63
+ timers.current.forEach(clearTimeout);
64
+ timers.current.clear();
65
+ setEntries((prev) => prev.map((e) => ({
66
+ ...e,
67
+ shouldExit: true
68
+ })));
69
+ }, []);
70
+ useEffect(() => {
71
+ _emit = toast;
72
+ return () => {
73
+ if (_emit === toast) _emit = null;
74
+ };
75
+ }, [toast]);
76
+ useEffect(() => {
77
+ const map = timers.current;
78
+ return () => map.forEach(clearTimeout);
79
+ }, []);
80
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, {
81
+ value: {
82
+ toast,
83
+ dismiss,
84
+ dismissAll
85
+ },
86
+ children: [children, typeof document !== "undefined" ? createPortal(/* @__PURE__ */ jsx(NotificationStack, {
87
+ placement,
88
+ children: entries.map((entry) => /* @__PURE__ */ jsx(ToastItem, {
89
+ entry,
90
+ onRemove: remove
91
+ }, entry.id))
92
+ }), document.body) : null]
93
+ });
94
+ }
95
+ var ToastContext = createContext(null);
96
+ function useToast() {
97
+ const ctx = useContext(ToastContext);
98
+ if (!ctx) throw new Error("useToast must be used inside <ToastProvider>.");
99
+ return ctx;
100
+ }
101
+ /**
102
+ * Fire a toast imperatively from anywhere — no hook needed.
103
+ * Requires a `<ToastProvider>` to be mounted somewhere in the tree.
104
+ */
105
+ function toast(options) {
106
+ if (!_emit) {
107
+ console.warn("[pitchfork-ui] toast() called before <ToastProvider> is mounted.");
108
+ return "";
109
+ }
110
+ return _emit(options);
111
+ }
112
+ toast.info = (opts) => toast({
113
+ ...opts,
114
+ variant: "info"
115
+ });
116
+ toast.success = (opts) => toast({
117
+ ...opts,
118
+ variant: "success"
119
+ });
120
+ toast.warning = (opts) => toast({
121
+ ...opts,
122
+ variant: "warning"
123
+ });
124
+ toast.danger = (opts) => toast({
125
+ ...opts,
126
+ variant: "danger"
127
+ });
128
+ //#endregion
129
+ export { ToastProvider, toast, useToast };