@runcontext/uxd 0.5.2 → 0.6.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric Kittelson
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.
@@ -91,7 +91,10 @@
91
91
  justify-content: center;
92
92
  gap: var(--rc-space-2);
93
93
  font-family: var(--rc-font-sans);
94
+ font-size: var(--rc-text-sm);
94
95
  font-weight: var(--rc-font-weight-semibold);
96
+ padding: var(--rc-space-2) var(--rc-space-4);
97
+ height: 2.5rem;
95
98
  border-radius: var(--rc-radius-md);
96
99
  border: 1px solid transparent;
97
100
  cursor: pointer;
@@ -238,7 +241,7 @@
238
241
  gap: var(--rc-space-1);
239
242
  font-size: var(--rc-text-xs);
240
243
  font-weight: var(--rc-font-weight-medium);
241
- padding: var(--rc-space-0_5) var(--rc-space-2);
244
+ padding: var(--rc-space-1) var(--rc-space-3);
242
245
  border-radius: var(--rc-radius-full);
243
246
  border: 1px solid transparent;
244
247
  line-height: var(--rc-leading-normal);
@@ -618,6 +621,288 @@
618
621
  font-size: var(--rc-text-sm);
619
622
  line-height: var(--rc-leading-normal);
620
623
  }
624
+ /* ── Toast ── */
625
+ .rc-toast-container {
626
+ position: fixed;
627
+ bottom: var(--rc-space-4);
628
+ right: var(--rc-space-4);
629
+ z-index: 9999;
630
+ display: flex;
631
+ flex-direction: column;
632
+ gap: var(--rc-space-2);
633
+ pointer-events: none;
634
+ }
635
+
636
+ .rc-toast {
637
+ display: flex;
638
+ align-items: flex-start;
639
+ gap: var(--rc-space-3);
640
+ padding: var(--rc-space-3) var(--rc-space-4);
641
+ border-radius: var(--rc-radius-lg);
642
+ background: var(--rc-color-surface-card);
643
+ border: 1px solid var(--rc-color-surface-border);
644
+ color: var(--rc-color-text-primary);
645
+ font-family: var(--rc-font-sans);
646
+ font-size: var(--rc-text-sm);
647
+ line-height: var(--rc-line-height);
648
+ box-shadow: var(--rc-shadow-lg);
649
+ pointer-events: auto;
650
+ min-width: 300px;
651
+ max-width: 420px;
652
+ animation: rc-toast-in 0.2s ease-out;
653
+ }
654
+
655
+ .rc-toast--exiting {
656
+ animation: rc-toast-out 0.15s ease-in forwards;
657
+ }
658
+
659
+ .rc-toast__icon {
660
+ flex-shrink: 0;
661
+ width: 18px;
662
+ height: 18px;
663
+ margin-top: 1px;
664
+ }
665
+
666
+ .rc-toast--success .rc-toast__icon { color: var(--rc-color-status-success); }
667
+ .rc-toast--error .rc-toast__icon { color: var(--rc-color-status-error); }
668
+ .rc-toast--warning .rc-toast__icon { color: var(--rc-color-status-warning); }
669
+ .rc-toast--info .rc-toast__icon { color: var(--rc-color-status-info); }
670
+
671
+ .rc-toast--success { border-left: 3px solid var(--rc-color-status-success); }
672
+ .rc-toast--error { border-left: 3px solid var(--rc-color-status-error); }
673
+ .rc-toast--warning { border-left: 3px solid var(--rc-color-status-warning); }
674
+ .rc-toast--info { border-left: 3px solid var(--rc-color-status-info); }
675
+
676
+ .rc-toast__content {
677
+ flex: 1;
678
+ min-width: 0;
679
+ }
680
+
681
+ .rc-toast__dismiss {
682
+ flex-shrink: 0;
683
+ background: none;
684
+ border: none;
685
+ color: var(--rc-color-text-muted);
686
+ cursor: pointer;
687
+ padding: 2px;
688
+ line-height: 1;
689
+ transition: color 0.15s ease;
690
+ }
691
+
692
+ .rc-toast__dismiss:hover {
693
+ color: var(--rc-color-text-primary);
694
+ }
695
+
696
+ @keyframes rc-toast-in {
697
+ from { opacity: 0; transform: translateX(20px); }
698
+ to { opacity: 1; transform: translateX(0); }
699
+ }
700
+
701
+ @keyframes rc-toast-out {
702
+ from { opacity: 1; transform: translateX(0); }
703
+ to { opacity: 0; transform: translateX(20px); }
704
+ }
705
+
706
+ /* ── Tooltip ── */
707
+ .rc-tooltip-wrapper {
708
+ position: relative;
709
+ display: inline-flex;
710
+ }
711
+
712
+ .rc-tooltip {
713
+ position: fixed;
714
+ z-index: 9999;
715
+ padding: var(--rc-space-2) var(--rc-space-3);
716
+ border-radius: var(--rc-radius-md);
717
+ background: #f8f6f1;
718
+ border: 1px solid #d4cfc5;
719
+ color: #1e1e1e;
720
+ font-family: var(--rc-font-sans);
721
+ font-size: var(--rc-text-xs);
722
+ line-height: var(--rc-line-height);
723
+ max-width: 280px;
724
+ white-space: normal;
725
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
726
+ pointer-events: none;
727
+ animation: rc-tooltip-in 0.12s ease-out;
728
+ }
729
+
730
+ .rc-tooltip--info {
731
+ border-left: 3px solid var(--rc-color-brand-gold);
732
+ }
733
+
734
+ @keyframes rc-tooltip-in {
735
+ from { opacity: 0; }
736
+ to { opacity: 1; }
737
+ }
738
+
739
+ /* ── InfoCard ── */
740
+ .rc-info-card {
741
+ display: flex;
742
+ align-items: flex-start;
743
+ gap: var(--rc-space-3);
744
+ padding: var(--rc-space-4);
745
+ border-radius: var(--rc-radius-lg);
746
+ background: var(--rc-color-surface-card);
747
+ border: 1px solid var(--rc-color-surface-border);
748
+ border-left: 3px solid var(--rc-color-brand-gold);
749
+ font-family: var(--rc-font-sans);
750
+ animation: rc-tooltip-in 0.2s ease-out;
751
+ margin-bottom: var(--rc-space-4, 16px);
752
+ overflow: visible;
753
+ }
754
+
755
+ .rc-info-card__icon {
756
+ flex-shrink: 0;
757
+ color: var(--rc-color-brand-gold);
758
+ width: 20px;
759
+ height: 20px;
760
+ margin-top: 1px;
761
+ }
762
+
763
+ .rc-info-card__content {
764
+ flex: 1;
765
+ min-width: 0;
766
+ }
767
+
768
+ .rc-info-card__title {
769
+ font-size: var(--rc-text-sm);
770
+ font-weight: var(--rc-weight-semibold);
771
+ color: var(--rc-color-text-primary);
772
+ margin: 0 0 var(--rc-space-1) 0;
773
+ word-wrap: break-word;
774
+ overflow-wrap: break-word;
775
+ }
776
+
777
+ .rc-info-card__body {
778
+ font-size: var(--rc-text-sm);
779
+ color: var(--rc-color-text-secondary);
780
+ line-height: var(--rc-line-height);
781
+ margin: 0;
782
+ }
783
+
784
+ .rc-info-card__dismiss {
785
+ flex-shrink: 0;
786
+ background: none;
787
+ border: none;
788
+ color: var(--rc-color-text-muted);
789
+ cursor: pointer;
790
+ padding: 2px;
791
+ transition: color 0.15s ease;
792
+ }
793
+
794
+ .rc-info-card__dismiss:hover {
795
+ color: var(--rc-color-text-primary);
796
+ }
797
+
798
+ /* ── Skeleton ── */
799
+ .rc-skeleton {
800
+ background: var(--rc-color-surface-card);
801
+ border-radius: var(--rc-radius-md);
802
+ animation: rc-skeleton-pulse 1.5s ease-in-out infinite;
803
+ }
804
+
805
+ .rc-skeleton--text {
806
+ height: 14px;
807
+ border-radius: var(--rc-radius-sm);
808
+ }
809
+
810
+ .rc-skeleton--card {
811
+ border-radius: var(--rc-radius-lg);
812
+ }
813
+
814
+ .rc-skeleton--circle {
815
+ border-radius: var(--rc-radius-full);
816
+ }
817
+
818
+ .rc-skeleton--stat {
819
+ border-radius: var(--rc-radius-lg);
820
+ height: 96px;
821
+ }
822
+
823
+ .rc-skeleton--table {
824
+ border-radius: var(--rc-radius-md);
825
+ height: 36px;
826
+ width: 100%;
827
+ }
828
+
829
+ @keyframes rc-skeleton-pulse {
830
+ 0%, 100% { opacity: 0.4; }
831
+ 50% { opacity: 0.8; }
832
+ }
833
+
834
+ /* ── ConfirmModal ── */
835
+ .rc-modal-overlay {
836
+ position: fixed;
837
+ inset: 0;
838
+ z-index: 9998;
839
+ background: rgba(0, 0, 0, 0.6);
840
+ display: flex;
841
+ align-items: center;
842
+ justify-content: center;
843
+ animation: rc-tooltip-in 0.12s ease-out;
844
+ }
845
+
846
+ .rc-modal {
847
+ background: var(--rc-color-surface-card);
848
+ border: 1px solid var(--rc-color-surface-border);
849
+ border-radius: var(--rc-radius-lg);
850
+ padding: var(--rc-space-6);
851
+ max-width: 440px;
852
+ width: 100%;
853
+ font-family: var(--rc-font-sans);
854
+ box-shadow: var(--rc-shadow-lg);
855
+ }
856
+
857
+ .rc-modal__title {
858
+ font-size: var(--rc-text-lg);
859
+ font-weight: var(--rc-weight-semibold);
860
+ color: var(--rc-color-text-primary);
861
+ margin: 0 0 var(--rc-space-2) 0;
862
+ }
863
+
864
+ .rc-modal__description {
865
+ font-size: var(--rc-text-sm);
866
+ color: var(--rc-color-text-secondary);
867
+ line-height: var(--rc-line-height);
868
+ margin: 0 0 var(--rc-space-4) 0;
869
+ }
870
+
871
+ .rc-modal__typing-label {
872
+ font-size: var(--rc-text-sm);
873
+ color: var(--rc-color-text-secondary);
874
+ margin: 0 0 var(--rc-space-2) 0;
875
+ }
876
+
877
+ .rc-modal__typing-label strong {
878
+ color: var(--rc-color-text-primary);
879
+ font-weight: var(--rc-weight-semibold);
880
+ }
881
+
882
+ .rc-modal__actions {
883
+ display: flex;
884
+ justify-content: flex-end;
885
+ gap: var(--rc-space-3);
886
+ margin-top: var(--rc-space-4);
887
+ }
888
+
889
+ /* ── ConceptTerm ── */
890
+ .rc-concept-term {
891
+ text-decoration: underline;
892
+ text-decoration-style: dotted;
893
+ text-underline-offset: 3px;
894
+ text-decoration-color: var(--rc-color-text-muted);
895
+ cursor: help;
896
+ display: inline-flex;
897
+ align-items: center;
898
+ gap: 4px;
899
+ }
900
+
901
+ .rc-concept-term__icon {
902
+ width: 14px;
903
+ height: 14px;
904
+ color: var(--rc-color-text-muted);
905
+ }
621
906
  /* ============================================================
622
907
  RunContext UXD — Utility Classes
623
908
  All values reference CSS custom properties from tokens.css.
@@ -79,4 +79,66 @@ interface CodeBlockProps extends HTMLAttributes<HTMLPreElement> {
79
79
  }
80
80
  declare function CodeBlock({ code, className, ...props }: CodeBlockProps): react_jsx_runtime.JSX.Element;
81
81
 
82
- export { type ActivityEvent, ActivityFeed, type ActivityFeedProps, Badge, type BadgeProps, type BadgeVariant, Button, type ButtonProps, Card, type CardProps, CodeBlock, type CodeBlockProps, EmptyState, type EmptyStateProps, ErrorCard, type ErrorCardProps, Input, type InputProps, Select, type SelectProps, Spinner, StatCard, type StatCardProps, Textarea, type TextareaProps, TierBadge, type TierBadgeProps };
82
+ type ToastVariant = 'success' | 'error' | 'warning' | 'info';
83
+ interface ToastMessage {
84
+ id: string;
85
+ variant: ToastVariant;
86
+ message: string;
87
+ duration?: number;
88
+ }
89
+ interface ToastContextValue {
90
+ toast: (variant: ToastVariant, message: string, duration?: number) => void;
91
+ }
92
+ declare function useToast(): ToastContextValue;
93
+ interface ToastProviderProps {
94
+ children: ReactNode;
95
+ }
96
+ declare function ToastProvider({ children }: ToastProviderProps): react_jsx_runtime.JSX.Element;
97
+
98
+ type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
99
+ interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
100
+ content: string;
101
+ placement?: TooltipPlacement;
102
+ variant?: 'default' | 'info';
103
+ children: ReactNode;
104
+ }
105
+ declare function Tooltip({ content, placement, variant, children, className, ...props }: TooltipProps): react_jsx_runtime.JSX.Element;
106
+
107
+ interface InfoCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
108
+ title: string;
109
+ children: ReactNode;
110
+ storageKey: string;
111
+ icon?: ReactNode;
112
+ }
113
+ declare function resetInfoCards(): void;
114
+ declare function InfoCard({ title, children, storageKey, icon, className, ...props }: InfoCardProps): react_jsx_runtime.JSX.Element | null;
115
+
116
+ type SkeletonVariant = 'text' | 'card' | 'circle' | 'stat' | 'table';
117
+ interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
118
+ variant?: SkeletonVariant;
119
+ width?: string | number;
120
+ height?: string | number;
121
+ }
122
+ declare function Skeleton({ variant, width, height, className, style: styleProp, ...props }: SkeletonProps): react_jsx_runtime.JSX.Element;
123
+
124
+ interface ConfirmModalProps {
125
+ open: boolean;
126
+ title: string;
127
+ description: string | ReactNode;
128
+ confirmLabel?: string;
129
+ cancelLabel?: string;
130
+ variant?: 'default' | 'danger';
131
+ requireTyping?: string;
132
+ onConfirm: () => void;
133
+ onCancel: () => void;
134
+ }
135
+ declare function ConfirmModal({ open, title, description, confirmLabel, cancelLabel, variant, requireTyping, onConfirm, onCancel, }: ConfirmModalProps): react_jsx_runtime.JSX.Element | null;
136
+
137
+ interface ConceptTermProps extends HTMLAttributes<HTMLSpanElement> {
138
+ term: string;
139
+ definition: string;
140
+ children?: ReactNode;
141
+ }
142
+ declare function ConceptTerm({ term, definition, children, className, ...props }: ConceptTermProps): react_jsx_runtime.JSX.Element;
143
+
144
+ export { type ActivityEvent, ActivityFeed, type ActivityFeedProps, Badge, type BadgeProps, type BadgeVariant, Button, type ButtonProps, Card, type CardProps, CodeBlock, type CodeBlockProps, ConceptTerm, type ConceptTermProps, ConfirmModal, type ConfirmModalProps, EmptyState, type EmptyStateProps, ErrorCard, type ErrorCardProps, InfoCard, type InfoCardProps, Input, type InputProps, Select, type SelectProps, Skeleton, type SkeletonProps, type SkeletonVariant, Spinner, StatCard, type StatCardProps, Textarea, type TextareaProps, TierBadge, type TierBadgeProps, type ToastContextValue, type ToastMessage, ToastProvider, type ToastProviderProps, type ToastVariant, Tooltip, type TooltipPlacement, type TooltipProps, resetInfoCards, useToast };
@@ -56,9 +56,9 @@ function Badge({ variant, className, children, ...props }) {
56
56
  // src/react/TierBadge.tsx
57
57
  import { jsx as jsx5 } from "react/jsx-runtime";
58
58
  var tierLabels = {
59
- gold: "AI-Ready",
60
- silver: "Trusted",
61
- bronze: "Discoverable"
59
+ gold: "Gold",
60
+ silver: "Silver",
61
+ bronze: "Bronze"
62
62
  };
63
63
  function TierBadge({ tier, ...props }) {
64
64
  return /* @__PURE__ */ jsx5(Badge, { variant: tier, ...props, children: tierLabels[tier] });
@@ -150,18 +150,324 @@ function CodeBlock({ code, className, ...props }) {
150
150
  const classes = ["rc-code", className].filter(Boolean).join(" ");
151
151
  return /* @__PURE__ */ jsx11("pre", { className: classes, ...props, children: /* @__PURE__ */ jsx11("code", { children: code }) });
152
152
  }
153
+
154
+ // src/react/Toast.tsx
155
+ import { createContext, useContext, useCallback, useState, useEffect, useRef } from "react";
156
+ import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
157
+ var ToastContext = createContext(null);
158
+ function useToast() {
159
+ const ctx = useContext(ToastContext);
160
+ if (!ctx) throw new Error("useToast must be used within a ToastProvider");
161
+ return ctx;
162
+ }
163
+ var ICONS = {
164
+ success: "\u2713",
165
+ error: "\u2715",
166
+ warning: "\u26A0",
167
+ info: "\u2139"
168
+ };
169
+ var DEFAULT_DURATION = {
170
+ success: 4e3,
171
+ error: 0,
172
+ // persist until dismissed
173
+ warning: 4e3,
174
+ info: 4e3
175
+ };
176
+ function ToastItem({ toast: t, onDismiss }) {
177
+ const [exiting, setExiting] = useState(false);
178
+ const timerRef = useRef(null);
179
+ const dismiss = useCallback(() => {
180
+ setExiting(true);
181
+ setTimeout(() => onDismiss(t.id), 150);
182
+ }, [t.id, onDismiss]);
183
+ useEffect(() => {
184
+ const dur = t.duration ?? DEFAULT_DURATION[t.variant];
185
+ if (dur > 0) {
186
+ timerRef.current = setTimeout(dismiss, dur);
187
+ }
188
+ return () => {
189
+ if (timerRef.current) clearTimeout(timerRef.current);
190
+ };
191
+ }, [t, dismiss]);
192
+ return /* @__PURE__ */ jsxs5("div", { className: `rc-toast rc-toast--${t.variant}${exiting ? " rc-toast--exiting" : ""}`, role: "alert", children: [
193
+ /* @__PURE__ */ jsx12("span", { className: "rc-toast__icon", "aria-hidden": "true", children: ICONS[t.variant] }),
194
+ /* @__PURE__ */ jsx12("span", { className: "rc-toast__content", children: t.message }),
195
+ /* @__PURE__ */ jsx12("button", { className: "rc-toast__dismiss", onClick: dismiss, "aria-label": "Dismiss notification", children: "\u2715" })
196
+ ] });
197
+ }
198
+ var toastCounter = 0;
199
+ function ToastProvider({ children }) {
200
+ const [toasts, setToasts] = useState([]);
201
+ const toast = useCallback((variant, message, duration) => {
202
+ const id = `toast-${++toastCounter}`;
203
+ setToasts((prev) => [...prev, { id, variant, message, duration }]);
204
+ }, []);
205
+ const dismiss = useCallback((id) => {
206
+ setToasts((prev) => prev.filter((t) => t.id !== id));
207
+ }, []);
208
+ return /* @__PURE__ */ jsxs5(ToastContext.Provider, { value: { toast }, children: [
209
+ children,
210
+ /* @__PURE__ */ jsx12("div", { className: "rc-toast-container", "aria-live": "polite", children: toasts.map((t) => /* @__PURE__ */ jsx12(ToastItem, { toast: t, onDismiss: dismiss }, t.id)) })
211
+ ] });
212
+ }
213
+
214
+ // src/react/Tooltip.tsx
215
+ import { useState as useState2, useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2 } from "react";
216
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
217
+ var GAP = 8;
218
+ function Tooltip({ content, placement = "top", variant = "default", children, className, ...props }) {
219
+ const [visible, setVisible] = useState2(false);
220
+ const [coords, setCoords] = useState2(null);
221
+ const [actualPlacement, setActualPlacement] = useState2(placement);
222
+ const timerRef = useRef2(null);
223
+ const wrapperRef = useRef2(null);
224
+ const tooltipRef = useRef2(null);
225
+ const show = useCallback2(() => {
226
+ timerRef.current = setTimeout(() => setVisible(true), 200);
227
+ }, []);
228
+ const hide = useCallback2(() => {
229
+ if (timerRef.current) clearTimeout(timerRef.current);
230
+ setVisible(false);
231
+ setCoords(null);
232
+ }, []);
233
+ useEffect2(() => {
234
+ if (!visible || !wrapperRef.current || !tooltipRef.current) return;
235
+ const anchor = wrapperRef.current.getBoundingClientRect();
236
+ const tip = tooltipRef.current.getBoundingClientRect();
237
+ let top = 0;
238
+ let left = 0;
239
+ let place = placement;
240
+ if (place === "top") {
241
+ top = anchor.top - tip.height - GAP;
242
+ left = anchor.left + anchor.width / 2 - tip.width / 2;
243
+ if (top < 4) {
244
+ place = "bottom";
245
+ top = anchor.bottom + GAP;
246
+ }
247
+ } else if (place === "bottom") {
248
+ top = anchor.bottom + GAP;
249
+ left = anchor.left + anchor.width / 2 - tip.width / 2;
250
+ if (top + tip.height > window.innerHeight - 4) {
251
+ place = "top";
252
+ top = anchor.top - tip.height - GAP;
253
+ }
254
+ } else if (place === "left") {
255
+ top = anchor.top + anchor.height / 2 - tip.height / 2;
256
+ left = anchor.left - tip.width - GAP;
257
+ } else {
258
+ top = anchor.top + anchor.height / 2 - tip.height / 2;
259
+ left = anchor.right + GAP;
260
+ }
261
+ left = Math.max(4, Math.min(left, window.innerWidth - tip.width - 4));
262
+ top = Math.max(4, Math.min(top, window.innerHeight - tip.height - 4));
263
+ setCoords({ top, left });
264
+ setActualPlacement(place);
265
+ }, [visible, placement]);
266
+ const wrapperClasses = ["rc-tooltip-wrapper", className].filter(Boolean).join(" ");
267
+ const tooltipClasses = [
268
+ "rc-tooltip",
269
+ variant === "info" && "rc-tooltip--info"
270
+ ].filter(Boolean).join(" ");
271
+ return /* @__PURE__ */ jsxs6("span", { ref: wrapperRef, className: wrapperClasses, onMouseEnter: show, onMouseLeave: hide, onFocus: show, onBlur: hide, ...props, children: [
272
+ children,
273
+ visible && /* @__PURE__ */ jsx13(
274
+ "span",
275
+ {
276
+ ref: tooltipRef,
277
+ className: tooltipClasses,
278
+ role: "tooltip",
279
+ style: coords ? { top: `${coords.top}px`, left: `${coords.left}px` } : { visibility: "hidden" },
280
+ children: content
281
+ }
282
+ )
283
+ ] });
284
+ }
285
+
286
+ // src/react/InfoCard.tsx
287
+ import { useState as useState3, useEffect as useEffect3 } from "react";
288
+ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
289
+ function isDismissed(key) {
290
+ if (typeof window === "undefined") return false;
291
+ try {
292
+ return localStorage.getItem(`rc-info-dismissed:${key}`) === "1";
293
+ } catch {
294
+ return false;
295
+ }
296
+ }
297
+ function setDismissed(key) {
298
+ try {
299
+ localStorage.setItem(`rc-info-dismissed:${key}`, "1");
300
+ } catch {
301
+ }
302
+ }
303
+ function resetInfoCards() {
304
+ if (typeof window === "undefined") return;
305
+ try {
306
+ const keys = Object.keys(localStorage).filter((k) => k.startsWith("rc-info-dismissed:"));
307
+ keys.forEach((k) => localStorage.removeItem(k));
308
+ } catch {
309
+ }
310
+ }
311
+ function InfoCard({ title, children, storageKey, icon, className, ...props }) {
312
+ const [hidden, setHidden] = useState3(true);
313
+ useEffect3(() => {
314
+ setHidden(isDismissed(storageKey));
315
+ }, [storageKey]);
316
+ if (hidden) return null;
317
+ const dismiss = () => {
318
+ setDismissed(storageKey);
319
+ setHidden(true);
320
+ };
321
+ const classes = ["rc-info-card", className].filter(Boolean).join(" ");
322
+ return /* @__PURE__ */ jsxs7("div", { className: classes, role: "note", ...props, children: [
323
+ icon && /* @__PURE__ */ jsx14("span", { className: "rc-info-card__icon", "aria-hidden": "true", children: icon }),
324
+ /* @__PURE__ */ jsxs7("div", { className: "rc-info-card__content", children: [
325
+ /* @__PURE__ */ jsx14("p", { className: "rc-info-card__title", children: title }),
326
+ /* @__PURE__ */ jsx14("p", { className: "rc-info-card__body", children })
327
+ ] }),
328
+ /* @__PURE__ */ jsx14("button", { className: "rc-info-card__dismiss", onClick: dismiss, "aria-label": "Dismiss tip", children: "\u2715" })
329
+ ] });
330
+ }
331
+
332
+ // src/react/Skeleton.tsx
333
+ import { jsx as jsx15 } from "react/jsx-runtime";
334
+ function Skeleton({ variant = "text", width, height, className, style: styleProp, ...props }) {
335
+ const style = { ...styleProp };
336
+ if (width) style.width = typeof width === "number" ? `${width}px` : width;
337
+ if (height) style.height = typeof height === "number" ? `${height}px` : height;
338
+ const classes = [
339
+ "rc-skeleton",
340
+ `rc-skeleton--${variant}`,
341
+ className
342
+ ].filter(Boolean).join(" ");
343
+ return /* @__PURE__ */ jsx15(
344
+ "div",
345
+ {
346
+ className: classes,
347
+ style,
348
+ "aria-hidden": "true",
349
+ ...props
350
+ }
351
+ );
352
+ }
353
+
354
+ // src/react/ConfirmModal.tsx
355
+ import { useState as useState4, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback3, useId } from "react";
356
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
357
+ function ConfirmModal({
358
+ open,
359
+ title,
360
+ description,
361
+ confirmLabel = "Confirm",
362
+ cancelLabel = "Cancel",
363
+ variant = "default",
364
+ requireTyping,
365
+ onConfirm,
366
+ onCancel
367
+ }) {
368
+ const [typed, setTyped] = useState4("");
369
+ const overlayRef = useRef3(null);
370
+ const cancelRef = useRef3(null);
371
+ const titleId = useId();
372
+ const canConfirm = requireTyping ? typed === requireTyping : true;
373
+ useEffect4(() => {
374
+ if (!open) {
375
+ setTyped("");
376
+ return;
377
+ }
378
+ document.body.style.overflow = "hidden";
379
+ const handleKey = (e) => {
380
+ if (e.key === "Escape") onCancel();
381
+ if (e.key === "Tab") {
382
+ const modal = overlayRef.current?.querySelector(".rc-modal");
383
+ if (!modal) return;
384
+ const focusable = modal.querySelectorAll(
385
+ 'button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'
386
+ );
387
+ if (focusable.length === 0) return;
388
+ const first = focusable[0];
389
+ const last = focusable[focusable.length - 1];
390
+ if (e.shiftKey && document.activeElement === first) {
391
+ e.preventDefault();
392
+ last.focus();
393
+ } else if (!e.shiftKey && document.activeElement === last) {
394
+ e.preventDefault();
395
+ first.focus();
396
+ }
397
+ }
398
+ };
399
+ document.addEventListener("keydown", handleKey);
400
+ if (!requireTyping) cancelRef.current?.focus();
401
+ return () => {
402
+ document.removeEventListener("keydown", handleKey);
403
+ document.body.style.overflow = "";
404
+ };
405
+ }, [open, onCancel, requireTyping]);
406
+ const handleOverlayClick = useCallback3((e) => {
407
+ if (e.target === overlayRef.current) onCancel();
408
+ }, [onCancel]);
409
+ if (!open) return null;
410
+ const btnVariant = variant === "danger" ? "rc-btn rc-btn--danger" : "rc-btn rc-btn--primary";
411
+ return /* @__PURE__ */ jsx16("div", { className: "rc-modal-overlay", ref: overlayRef, onClick: handleOverlayClick, children: /* @__PURE__ */ jsxs8("div", { className: "rc-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [
412
+ /* @__PURE__ */ jsx16("h2", { className: "rc-modal__title", id: titleId, children: title }),
413
+ /* @__PURE__ */ jsx16("div", { className: "rc-modal__description", children: description }),
414
+ requireTyping && /* @__PURE__ */ jsxs8("div", { children: [
415
+ /* @__PURE__ */ jsxs8("p", { className: "rc-modal__typing-label", children: [
416
+ "Type ",
417
+ /* @__PURE__ */ jsx16("strong", { children: requireTyping }),
418
+ " to confirm:"
419
+ ] }),
420
+ /* @__PURE__ */ jsx16(
421
+ "input",
422
+ {
423
+ className: "rc-input",
424
+ value: typed,
425
+ onChange: (e) => setTyped(e.target.value),
426
+ autoFocus: true,
427
+ spellCheck: false
428
+ }
429
+ )
430
+ ] }),
431
+ /* @__PURE__ */ jsxs8("div", { className: "rc-modal__actions", children: [
432
+ /* @__PURE__ */ jsx16("button", { className: "rc-btn rc-btn--secondary", onClick: onCancel, ref: cancelRef, children: cancelLabel }),
433
+ /* @__PURE__ */ jsx16("button", { className: btnVariant, onClick: onConfirm, disabled: !canConfirm, children: confirmLabel })
434
+ ] })
435
+ ] }) });
436
+ }
437
+
438
+ // src/react/ConceptTerm.tsx
439
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
440
+ function ConceptTerm({ term, definition, children, className, ...props }) {
441
+ const classes = ["rc-concept-term", className].filter(Boolean).join(" ");
442
+ return /* @__PURE__ */ jsx17(Tooltip, { content: definition, variant: "info", placement: "top", children: /* @__PURE__ */ jsxs9("span", { className: classes, ...props, children: [
443
+ children ?? term,
444
+ /* @__PURE__ */ jsxs9("svg", { className: "rc-concept-term__icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
445
+ /* @__PURE__ */ jsx17("circle", { cx: "12", cy: "12", r: "10" }),
446
+ /* @__PURE__ */ jsx17("path", { d: "M12 16v-4" }),
447
+ /* @__PURE__ */ jsx17("path", { d: "M12 8h.01" })
448
+ ] })
449
+ ] }) });
450
+ }
153
451
  export {
154
452
  ActivityFeed,
155
453
  Badge,
156
454
  Button,
157
455
  Card,
158
456
  CodeBlock,
457
+ ConceptTerm,
458
+ ConfirmModal,
159
459
  EmptyState,
160
460
  ErrorCard,
461
+ InfoCard,
161
462
  Input,
162
463
  Select,
464
+ Skeleton,
163
465
  Spinner,
164
466
  StatCard,
165
467
  Textarea,
166
- TierBadge
468
+ TierBadge,
469
+ ToastProvider,
470
+ Tooltip,
471
+ resetInfoCards,
472
+ useToast
167
473
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runcontext/uxd",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,15 +20,6 @@
20
20
  "types": "./dist/react/index.d.ts"
21
21
  }
22
22
  },
23
- "scripts": {
24
- "build": "pnpm build:tokens && pnpm build:css && pnpm build:ts",
25
- "build:tokens": "tsx scripts/generate-css.ts",
26
- "build:css": "cat src/css/tokens.css src/css/components.css src/css/utilities.css > dist/css/index.css && cp src/css/tokens.css dist/css/tokens.css",
27
- "build:ts": "tsup",
28
- "clean": "rm -rf dist",
29
- "dev": "tsup --watch",
30
- "test": "vitest run"
31
- },
32
23
  "devDependencies": {
33
24
  "@types/node": "^22.0.0",
34
25
  "@types/react": "^19.0.0",
@@ -51,5 +42,14 @@
51
42
  "react-dom": {
52
43
  "optional": true
53
44
  }
45
+ },
46
+ "scripts": {
47
+ "build": "pnpm build:tokens && pnpm build:css && pnpm build:ts",
48
+ "build:tokens": "tsx scripts/generate-css.ts",
49
+ "build:css": "cat src/css/tokens.css src/css/components.css src/css/utilities.css > dist/css/index.css && cp src/css/tokens.css dist/css/tokens.css",
50
+ "build:ts": "tsup",
51
+ "clean": "rm -rf dist",
52
+ "dev": "tsup --watch",
53
+ "test": "vitest run"
54
54
  }
55
- }
55
+ }