@cosxai/ui 0.20.0 → 0.21.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "0.20.0",
4
- "description": "COSX design system — React 19 component primitives shared across product-meta and other consumers",
3
+ "version": "0.21.0",
4
+ "description": "COSX design system \u2014 React 19 component primitives shared across product-meta and other consumers",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./src/index.ts",
@@ -1,6 +1,8 @@
1
1
  import { useEffect, type ReactNode, type CSSProperties } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
 
4
+ import { useViewport } from "../hooks/useViewport";
5
+
4
6
  // Modal base. Portals to document.body so it escapes ancestor
5
7
  // stacking contexts. Closes on Esc + backdrop click (both opt-out).
6
8
  // Caller owns open state and slots in the header/body/footer.
@@ -25,6 +27,16 @@ export interface ModalProps {
25
27
  // Optional aria-labelled-by for screen readers (point at the
26
28
  // ModalHeader title's id).
27
29
  labelledBy?: string;
30
+ // How the modal presents on PHONE viewports (no effect elsewhere):
31
+ // "center" (default) — the desktop card, scaled to fit
32
+ // "page" — full-screen, slides in from the right like
33
+ // a pushed native page. For content dialogs
34
+ // the user enters to work in.
35
+ // "sheet" — bottom sheet rising from the lower edge.
36
+ // For light pickers / short forms.
37
+ // Confirm-style dialogs should stay "center" — native alerts are
38
+ // centered cards too.
39
+ phonePresentation?: "center" | "page" | "sheet";
28
40
  }
29
41
 
30
42
  const WIDTHS = { sm: 340, md: 440, lg: 560, xl: 720, "2xl": 880, "3xl": 1024, "4xl": 1200, "5xl": 1440 };
@@ -37,7 +49,10 @@ export function Modal({
37
49
  cardStyle,
38
50
  children,
39
51
  labelledBy,
52
+ phonePresentation = "center",
40
53
  }: ModalProps) {
54
+ const vp = useViewport();
55
+ const mode = vp.isPhone ? phonePresentation : "center";
41
56
  useEffect(() => {
42
57
  if (!open || !dismissable) return;
43
58
  const onKey = (e: KeyboardEvent) => {
@@ -79,9 +94,9 @@ export function Modal({
79
94
  backdropFilter: "blur(8px)",
80
95
  WebkitBackdropFilter: "blur(8px)",
81
96
  display: "flex",
82
- alignItems: "center",
97
+ alignItems: mode === "sheet" ? "flex-end" : "center",
83
98
  justifyContent: "center",
84
- padding: 16,
99
+ padding: mode === "center" ? 16 : 0,
85
100
  zIndex: 100,
86
101
  }}
87
102
  onMouseDown={(e) => {
@@ -93,15 +108,23 @@ export function Modal({
93
108
  <div
94
109
  style={{
95
110
  width: "100%",
96
- maxWidth: width,
97
- maxHeight: "calc(100vh - 32px)",
111
+ maxWidth: mode === "center" ? width : "100%",
112
+ maxHeight:
113
+ mode === "page" ? "100dvh" : mode === "sheet" ? "92dvh" : "calc(100vh - 32px)",
114
+ ...(mode === "page" ? { height: "100dvh" } : {}),
98
115
  background: "var(--ck-bg-surface)",
99
116
  border: "1px solid var(--ck-border-subtle)",
100
117
  // Modal cards lift on the `-lg` radius rather than the
101
118
  // page's default `-md` so the corners read as softer than
102
119
  // the cards underneath them — supports the "this took
103
120
  // over" cue alongside the deeper overlay shadow.
104
- borderRadius: "var(--ck-radius-lg)",
121
+ borderRadius:
122
+ mode === "page"
123
+ ? 0
124
+ : mode === "sheet"
125
+ ? "var(--ck-radius-lg) var(--ck-radius-lg) 0 0"
126
+ : "var(--ck-radius-lg)",
127
+ ...(mode === "page" ? { border: "none" } : {}),
105
128
  // Overlay shadow > shadow-3. shadow-3 was tuned for cards
106
129
  // sitting on a page; a modal needs to read as physically
107
130
  // lifted off the page, not as another tier of card.
@@ -113,7 +136,13 @@ export function Modal({
113
136
  color: "var(--ck-text-primary)",
114
137
  ...cardStyle,
115
138
  }}
116
- className="ck-anim-popover"
139
+ className={
140
+ mode === "page"
141
+ ? "ck-anim-page-push"
142
+ : mode === "sheet"
143
+ ? "ck-anim-sheet-up"
144
+ : "ck-anim-popover"
145
+ }
117
146
  >
118
147
  {children}
119
148
  </div>
@@ -88,12 +88,19 @@ a:visited { color: var(--ck-accent); }
88
88
  @keyframes ck-fade-in { from { opacity: 0; } to { opacity: 1; } }
89
89
  @keyframes ck-popover-enter { from { opacity: 0; transform: translateY(8px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
90
90
  @keyframes ck-page-enter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
91
+ /* Full-distance entrances for phone modal presentations: a pushed
92
+ page slides in from the right edge, a sheet rises from the bottom.
93
+ Unlike the 14px nudges above these travel the whole viewport. */
94
+ @keyframes ck-page-push { from { transform: translateX(100%); } to { transform: translateX(0); } }
95
+ @keyframes ck-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
91
96
 
92
97
  .ck-anim-slide-right { animation: ck-slide-in-right 180ms var(--ck-ease) both; }
93
98
  .ck-anim-slide-left { animation: ck-slide-in-left 180ms var(--ck-ease) both; }
94
99
  .ck-anim-fade { animation: ck-fade-in 180ms var(--ck-ease) both; }
95
100
  .ck-anim-popover { animation: ck-popover-enter 160ms var(--ck-ease) both; transform-origin: top center; }
96
101
  .ck-anim-page { animation: ck-page-enter var(--ck-dur-page) cubic-bezier(0.16, 1, 0.3, 1) both; }
102
+ .ck-anim-page-push { animation: ck-page-push 260ms cubic-bezier(0.32, 0.72, 0, 1) both; }
103
+ .ck-anim-sheet-up { animation: ck-sheet-up 260ms cubic-bezier(0.32, 0.72, 0, 1) both; }
97
104
 
98
105
  /* Respect the user's OS-level motion preference. Cross-fade
99
106
  transitions on bg/colour stay (below the meaningful-motion
@@ -103,7 +110,9 @@ a:visited { color: var(--ck-accent); }
103
110
  .ck-anim-slide-left,
104
111
  .ck-anim-fade,
105
112
  .ck-anim-popover,
106
- .ck-anim-page {
113
+ .ck-anim-page,
114
+ .ck-anim-page-push,
115
+ .ck-anim-sheet-up {
107
116
  animation: none;
108
117
  }
109
118
  }