@duro-app/ui 3.3.0 → 3.4.1
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/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.stories.d.ts +1 -0
- package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1001 -995
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Dialog/Dialog.stories.tsx +53 -1
- package/src/components/Dialog/Dialog.tsx +11 -1
- package/src/components/Drawer/Drawer.tsx +11 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@tanstack/react-virtual": "^3.14.6",
|
|
67
|
-
"@duro-app/tokens": "^3.
|
|
67
|
+
"@duro-app/tokens": "^3.4.1"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -9,7 +9,8 @@ import {Inline} from '../Inline/Inline'
|
|
|
9
9
|
import {Stack} from '../Stack/Stack'
|
|
10
10
|
import {Input} from '../Input/Input'
|
|
11
11
|
import {Field} from '../Field/Field'
|
|
12
|
-
import {
|
|
12
|
+
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
13
|
+
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
13
14
|
|
|
14
15
|
const meta: Meta = {
|
|
15
16
|
title: 'Components/Dialog',
|
|
@@ -321,3 +322,54 @@ function BackdropDismissDemo() {
|
|
|
321
322
|
export const BackdropDismiss: Story = {
|
|
322
323
|
render: () => <BackdropDismissDemo />,
|
|
323
324
|
}
|
|
325
|
+
|
|
326
|
+
// --- Inside a transformed ancestor ---
|
|
327
|
+
// A fixed overlay inside an ancestor with transform / filter / backdrop-filter
|
|
328
|
+
// is positioned against that ancestor, not the viewport. A consumer's sticky
|
|
329
|
+
// blurred top bar is exactly that: the dialog must still centre on the screen,
|
|
330
|
+
// which it does because Portal renders into the ThemeProvider mount.
|
|
331
|
+
|
|
332
|
+
const barStyles = css.create({
|
|
333
|
+
bar: {
|
|
334
|
+
transform: 'translateZ(0)',
|
|
335
|
+
padding: spacing.sm,
|
|
336
|
+
borderWidth: 1,
|
|
337
|
+
borderStyle: 'solid',
|
|
338
|
+
borderColor: colors.border,
|
|
339
|
+
},
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
export const InsideTransformedAncestor: Story = {
|
|
343
|
+
render: () => (
|
|
344
|
+
<html.div style={barStyles.bar}>
|
|
345
|
+
<Dialog.Root>
|
|
346
|
+
<Dialog.Trigger>
|
|
347
|
+
<Button>Open from the bar</Button>
|
|
348
|
+
</Dialog.Trigger>
|
|
349
|
+
<Dialog.Portal size="sm">
|
|
350
|
+
<Dialog.Header>
|
|
351
|
+
<Dialog.Title>Centred on the screen</Dialog.Title>
|
|
352
|
+
<Dialog.Close />
|
|
353
|
+
</Dialog.Header>
|
|
354
|
+
<Dialog.Body>
|
|
355
|
+
<Text>Not on the bar it was opened from.</Text>
|
|
356
|
+
</Dialog.Body>
|
|
357
|
+
</Dialog.Portal>
|
|
358
|
+
</Dialog.Root>
|
|
359
|
+
</html.div>
|
|
360
|
+
),
|
|
361
|
+
play: async ({canvasElement}) => {
|
|
362
|
+
const canvas = within(canvasElement)
|
|
363
|
+
await userEvent.click(canvas.getByText('Open from the bar'))
|
|
364
|
+
const doc = canvasElement.ownerDocument
|
|
365
|
+
const dialog = doc.querySelector('[role="dialog"]') as HTMLElement
|
|
366
|
+
await expect(dialog).not.toBeNull()
|
|
367
|
+
const bar = canvas.getByText('Open from the bar').closest('div') as HTMLElement
|
|
368
|
+
// rendered outside the bar…
|
|
369
|
+
await expect(bar.contains(dialog)).toBe(false)
|
|
370
|
+
// …and fully on screen, not centred on a bar at the top edge
|
|
371
|
+
const r = dialog.getBoundingClientRect()
|
|
372
|
+
await expect(r.top).toBeGreaterThanOrEqual(0)
|
|
373
|
+
await expect(r.bottom).toBeLessThanOrEqual(doc.defaultView!.innerHeight)
|
|
374
|
+
},
|
|
375
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {createPortal} from 'react-dom'
|
|
1
2
|
import {
|
|
2
3
|
type ReactNode,
|
|
3
4
|
type RefObject,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
import {html} from 'react-strict-dom'
|
|
13
14
|
import {DURATION_MS, type DurationToken} from '@duro-app/tokens/keys'
|
|
14
15
|
import {styles} from './styles.css'
|
|
16
|
+
import {usePortalMount} from '../ThemeProvider/ThemeProvider'
|
|
15
17
|
|
|
16
18
|
// --- Types ---
|
|
17
19
|
|
|
@@ -194,6 +196,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
194
196
|
descriptionId,
|
|
195
197
|
popupRef,
|
|
196
198
|
} = useDialog()
|
|
199
|
+
const mount = usePortalMount()
|
|
197
200
|
|
|
198
201
|
const handleBackdropClick = useCallback(() => {
|
|
199
202
|
if (dismissable) {
|
|
@@ -203,7 +206,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
203
206
|
|
|
204
207
|
if (!open) return null
|
|
205
208
|
|
|
206
|
-
|
|
209
|
+
const node = (
|
|
207
210
|
<>
|
|
208
211
|
{/* Backdrop — click to dismiss */}
|
|
209
212
|
<html.div
|
|
@@ -238,6 +241,13 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
238
241
|
</html.div>
|
|
239
242
|
</>
|
|
240
243
|
)
|
|
244
|
+
|
|
245
|
+
// Out of the tree into the ThemeProvider mount (like Select and Combobox):
|
|
246
|
+
// a fixed overlay inside an ancestor with transform / filter /
|
|
247
|
+
// backdrop-filter is positioned against THAT ancestor, not the viewport —
|
|
248
|
+
// a dialog opened from a sticky blurred top bar centred on the bar. Without
|
|
249
|
+
// a mount (SSR, no ThemeProvider) it renders in place.
|
|
250
|
+
return mount ? createPortal(node, mount) : node
|
|
241
251
|
}
|
|
242
252
|
|
|
243
253
|
// --- Header ---
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {createPortal} from 'react-dom'
|
|
1
2
|
import {
|
|
2
3
|
type ReactNode,
|
|
3
4
|
type RefObject,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
import {html} from 'react-strict-dom'
|
|
13
14
|
import {DURATION_MS, type DurationToken} from '@duro-app/tokens/keys'
|
|
14
15
|
import {styles} from './styles.css'
|
|
16
|
+
import {usePortalMount} from '../ThemeProvider/ThemeProvider'
|
|
15
17
|
import {useSwipeDismiss} from './useSwipeDismiss'
|
|
16
18
|
|
|
17
19
|
// --- Types ---
|
|
@@ -230,6 +232,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
230
232
|
descriptionId,
|
|
231
233
|
panelRef,
|
|
232
234
|
} = useDrawer()
|
|
235
|
+
const mount = usePortalMount()
|
|
233
236
|
|
|
234
237
|
const handleBackdropClick = useCallback(() => {
|
|
235
238
|
if (dismissable) {
|
|
@@ -249,7 +252,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
249
252
|
|
|
250
253
|
const isHorizontal = anchor === 'left' || anchor === 'right'
|
|
251
254
|
|
|
252
|
-
|
|
255
|
+
const node = (
|
|
253
256
|
<>
|
|
254
257
|
{/* Backdrop — click to dismiss */}
|
|
255
258
|
<html.div
|
|
@@ -287,6 +290,13 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
287
290
|
</html.div>
|
|
288
291
|
</>
|
|
289
292
|
)
|
|
293
|
+
|
|
294
|
+
// Out of the tree into the ThemeProvider mount (like Select and Combobox):
|
|
295
|
+
// a fixed overlay inside an ancestor with transform / filter /
|
|
296
|
+
// backdrop-filter is positioned against THAT ancestor, not the viewport —
|
|
297
|
+
// a dialog opened from a sticky blurred top bar centred on the bar. Without
|
|
298
|
+
// a mount (SSR, no ThemeProvider) it renders in place.
|
|
299
|
+
return mount ? createPortal(node, mount) : node
|
|
290
300
|
}
|
|
291
301
|
|
|
292
302
|
// --- Header ---
|