Modal
תיבת דו-שיח מודאלית המבוססת על אלמנט הדו-שיח המובנה.
- דפדפנים
- Chrome 123+Firefox 121+Safari 17.5+
- שכבה
- 0
- גודל חבילה דחוסה
- 875 B
- מגבלת פריסה כפויה
- 0 ms
- מגבלת Axe
- 0
- תואם SSR
- כן
- מבוסס על
- elements/dialog
- חוזה מפורסם
- סביבת בדיקות
מגרש ניסויים
import { Modal } from "@gunkin/uiify";
export default function Example() {
return (
<Modal
id="settings-dialog"
trigger="Open settings"
title="Settings"
description="Update your profile preferences."
slotProps={{
trigger: { className: "" },
content: { className: "" },
grabber: { className: "" },
header: { className: "" },
title: { className: "" },
description: { className: "" },
close: { className: " " },
body: { className: "" },
footer: { className: "" },
}}
/>
);
}/* Native dialog surfaces. */
@layer components {
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
) {
margin: auto;
inline-size: min(28rem, calc(100vw - 2 * var(--space-lg)));
padding: var(--space-xl);
border: var(--border-width) solid var(--border);
/* One-off: the modal is the highest-elevation surface in the 2a/2b
handoff (uiify Components Kit.dc.html), rounder than the --radius-lg
card scale (12px) that every other bordered container uses. */
border-radius: var(--radius-xl);
background-color: var(--popover);
color: var(--popover-foreground);
box-shadow: var(--shadow-modal);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
)::backdrop {
background-color: rgb(0 0 0 / 45%);
}
:where([data-uiify-dialog], [data-uiify-modal], [data-uiify-alert-modal], [data-uiify-drawer])
[data-part="title"] {
margin: 0;
font-size: var(--text-xl);
font-weight: var(--weight-semibold);
line-height: 1.3;
}
:where([data-uiify-dialog], [data-uiify-modal], [data-uiify-alert-modal], [data-uiify-drawer])
[data-part="description"] {
margin-block: var(--space-sm) var(--space-lg);
color: var(--muted-foreground);
font-size: var(--text-sm);
line-height: var(--leading-normal);
}
@media (prefers-reduced-motion: no-preference) {
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
),
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
)::backdrop {
transition: var(--transition-presence);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"]
):not([open]) {
opacity: 0;
scale: var(--presence-scale);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
):not([open])::backdrop {
opacity: 0;
}
@starting-style {
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"]
)[open] {
opacity: 0;
scale: var(--presence-scale);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
)[open]::backdrop {
opacity: 0;
}
}
}
/* Modal anatomy. AlertModal, Drawer and Dialog keep the shared surface above; every rule
below is scoped to `[data-uiify-modal]`, so their look is unchanged.
The column layout sits on the base rule, not under [open]: on close `display` stays flex
through the exit transition (allow-discrete), so the dialog must keep its column until it
is hidden. Only `display` depends on [open]. */
[data-uiify-modal][data-part="content"] {
--uiify-modal-inline-size: 28rem;
flex-direction: column;
gap: var(--space-md);
inline-size: min(var(--uiify-modal-inline-size), calc(100vw - 2 * var(--space-lg)));
max-block-size: calc(100dvh - 2 * var(--space-lg));
}
[data-uiify-modal][data-part="content"][open] {
display: flex;
}
[data-uiify-modal][data-part="content"][data-size="sm"] {
--uiify-modal-inline-size: 24rem;
}
[data-uiify-modal][data-part="content"][data-size="lg"] {
--uiify-modal-inline-size: 40rem;
}
[data-uiify-modal][data-part="content"][data-size="full"] {
inset: 0;
margin: 0;
inline-size: 100%;
block-size: 100%;
max-inline-size: none;
max-block-size: none;
padding-block: max(var(--space-xl), env(safe-area-inset-top))
max(var(--space-xl), env(safe-area-inset-bottom));
padding-inline: max(var(--space-xl), env(safe-area-inset-left))
max(var(--space-xl), env(safe-area-inset-right));
border: 0;
border-radius: 0;
}
/* Below 40rem the bottom sheet wins, so align=start only applies from the breakpoint up. */
@media (width >= 40rem) {
[data-uiify-modal][data-part="content"][data-align="start"]:not([data-size="full"]) {
margin-block: var(--space-xl) auto;
}
}
[data-uiify-modal][data-part="header"] {
flex: none;
padding-inline-end: var(--control-md);
}
[data-uiify-modal][data-part="header"] > [data-part="close"] {
position: absolute;
inset-block-start: 10px;
inset-inline-end: 10px;
display: grid;
place-items: center;
}
/* Close glyph: two crossed bars on the ghost Button's free pseudo-elements. `all: unset`
drops the geometry Button gives them; both share the centred grid cell. */
[data-uiify-modal][data-part="header"] > [data-part="close"]:empty {
&::before,
&::after {
all: unset;
content: "";
grid-area: 1 / 1;
inline-size: 0.875rem;
block-size: 2px;
border-radius: 1px;
background-color: currentcolor;
}
&::before {
rotate: 45deg;
}
&::after {
rotate: -45deg;
}
}
[data-uiify-modal][data-part="body"] {
flex: 1 1 auto;
min-block-size: 0;
overflow: auto;
overscroll-behavior: contain;
}
[data-uiify-modal][data-part="footer"] {
display: flex;
flex: none;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-sm);
}
/* Mobile sheet below 40rem (keep in sync with SHEET_QUERY in enhance/sheet-swipe.ts):
at least 50dvh and at most 80dvh, so the top 20% of the viewport is always backdrop. */
[data-uiify-modal][data-part="grabber"] {
display: none;
flex: none;
block-size: 1.5rem;
margin-block: calc(-1 * var(--space-md)) calc(-1 * var(--space-sm));
touch-action: none;
}
[data-uiify-modal][data-part="grabber"]::after {
content: "";
display: block;
inline-size: 2.25rem;
block-size: 0.25rem;
margin-block: 0.5rem 0;
margin-inline: auto;
border-radius: 999px;
background-color: var(--border);
}
@media (width < 40rem) {
[data-uiify-modal][data-part="content"]:not([data-size="full"]) {
inline-size: 100%;
max-inline-size: 100%;
min-block-size: 50dvh;
max-block-size: 80dvh;
margin-block: auto 0;
margin-inline: 0;
padding-block-end: calc(var(--space-xl) + env(safe-area-inset-bottom));
border-block-end-width: 0;
border-end-start-radius: 0;
border-end-end-radius: 0;
}
/* With a ModalBody the body is the scroller, so the rest of the sheet is a swipe surface.
Without one the dialog itself scrolls and keeps the default touch action; the header and
grabber stay swipe handles either way (touch-action applies to the element touched). */
[data-uiify-modal][data-part="content"]:not([data-size="full"]):has(
> [data-uiify-modal][data-part="body"]
) {
touch-action: none;
}
[data-uiify-modal][data-part="content"]:not([data-size="full"])
[data-uiify-modal][data-part="header"] {
touch-action: none;
}
[data-uiify-modal][data-part="body"] {
touch-action: pan-y;
}
[data-uiify-modal][data-part="content"]:not([data-size="full"]) > [data-part="grabber"] {
display: block;
}
}
@media (width < 40rem) and (prefers-reduced-motion: no-preference) {
[data-uiify-modal][data-part="content"]:not([data-size="full"]) {
transition:
translate var(--duration-fast) var(--ease-out),
opacity var(--duration-fast) var(--ease-out),
display var(--duration-fast) allow-discrete,
overlay var(--duration-fast) allow-discrete;
}
[data-uiify-modal][data-part="content"]:not([data-size="full"], [open]) {
translate: 0 100%;
scale: 1;
}
@starting-style {
[data-uiify-modal][data-part="content"]:not([data-size="full"])[open] {
translate: 0 100%;
scale: 1;
}
}
}
/* While a finger drives the sheet (see enhance/sheet-swipe.ts): no transition, position
from the offset. Removing the attribute hands the position back to the rules above,
which animates the sheet out or back. */
[data-uiify-modal][data-part="content"][data-dragging][open]:not([data-size="full"]) {
transition: none;
translate: 0 var(--uiify-sheet-offset, 0);
}
[data-uiify-modal][data-part="content"][data-backdrop="blur"]::backdrop {
backdrop-filter: blur(4px);
}
[data-uiify-modal][data-part="content"][data-backdrop="none"]::backdrop {
background-color: transparent;
}
/* Nested modals: the child is a DOM descendant of the parent, so CSS can see it. The
parent's own backdrop keeps dimming the page (one layer); the parent steps back. */
[data-uiify-modal][data-part="content"] [data-uiify-modal][data-part="content"]::backdrop {
background-color: transparent;
backdrop-filter: none;
}
[data-uiify-modal][data-part="content"]:has([data-uiify-modal][data-part="content"]:modal) {
filter: brightness(0.9);
}
@media (width >= 40rem) {
[data-uiify-modal][data-part="content"]:has([data-uiify-modal][data-part="content"]:modal) {
scale: 0.97;
}
}
}import { Modal } from "@gunkin/uiify";
export default function Example() {
return (
<Modal
id="settings-dialog"
trigger="Open settings"
title="Settings"
description="Update your profile preferences."
slotProps={{
trigger: {
className:
"relative isolate inline-flex items-center justify-center gap-1.5 min-h-10 " +
"min-w-10 px-5 py-px border-[length:var(--border-width)] border-solid " +
"border-transparent rounded-full bg-clip-padding text-base font-medium leading-6 " +
"no-underline cursor-pointer [transition:var(--transition-colors)] " +
"focus-visible:[outline:none] " +
"focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_20%,_transparent)] " +
"disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
"data-[disabled]:cursor-not-allowed " +
"[&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] " +
"[&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] " +
"[&:is([data-selected],[aria-pressed=true],[aria-current=page])]:border-transparent " +
"[&:is([data-selected],[aria-pressed=true],[aria-current=page])]:bg-primary " +
"[&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-primary-foreground " +
"[:checked+&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] " +
"[:checked+&]:[--uiify-button-wash:rgb(255_255_255_/_15%)] " +
"[:checked+&]:border-transparent [:checked+&]:bg-primary " +
"[:checked+&]:text-primary-foreground " +
"[@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true],[aria-current=page]):hover:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] " +
"[@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:not(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] " +
"bg-foreground text-background " +
"[@media_(any-hover:_hover)_and_(any-pointer:_fine)]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_90%,_var(--background))] " +
"active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_80%,_var(--background))]",
},
content: {
className:
"[&[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)] " +
"[&_[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)] " +
"[&[popover]:popover-open:not([data-positioning=native])]:[margin:revert] " +
"[&_[popover]:popover-open:not([data-positioning=native])]:[margin:revert] " +
"[&[data-part=content]]:m-auto " +
"[&[data-part=content]]:[inline-size:min(28rem,_calc(100vw_-_2_*_var(--space-lg)))] " +
"[&[data-part=content]]:p-xl " +
"[&[data-part=content]]:border-[length:var(--border-width)] " +
"[&[data-part=content]]:border-solid [&[data-part=content]]:border-border " +
"[&[data-part=content]]:rounded-xl [&[data-part=content]]:bg-popover " +
"[&[data-part=content]]:text-popover-foreground " +
"[&[data-part=content]]:shadow-modal " +
"[&[data-part=content]::backdrop]:[background-color:rgb(0_0_0_/_45%)] " +
"motion-safe:[&[data-part=content]]:[transition:var(--transition-presence)] " +
"motion-safe:[&[data-part=content]::backdrop]:[transition:var(--transition-presence)] " +
"motion-safe:[&[data-part=content]:not([open])]:opacity-0 " +
"motion-safe:[&[data-part=content]:not([open])]:[scale:var(--presence-scale)] " +
"motion-safe:[&[data-part=content]:not([open])::backdrop]:opacity-0 " +
"motion-safe:starting:[&[data-part=content][open]]:opacity-0 " +
"motion-safe:starting:[&[data-part=content][open]]:[scale:var(--presence-scale)] " +
"motion-safe:starting:[&[data-part=content][open]::backdrop]:opacity-0 " +
"data-[part=content]:[--uiify-modal-inline-size:28rem] " +
"data-[part=content]:flex-col data-[part=content]:gap-md " +
"data-[part=content]:[inline-size:min(var(--uiify-modal-inline-size),_calc(100vw_-_2_*_var(--space-lg)))] " +
"data-[part=content]:[max-block-size:calc(100dvh_-_2_*_var(--space-lg))] " +
"data-[part=content]:[&[open]]:flex " +
"data-[part=content]:data-[size=sm]:[--uiify-modal-inline-size:24rem] " +
"data-[part=content]:data-[size=lg]:[--uiify-modal-inline-size:40rem] " +
"data-[part=content]:data-[size=full]:inset-0 " +
"data-[part=content]:data-[size=full]:m-0 " +
"data-[part=content]:data-[size=full]:[inline-size:100%] " +
"data-[part=content]:data-[size=full]:[block-size:100%] " +
"data-[part=content]:data-[size=full]:[max-inline-size:none] " +
"data-[part=content]:data-[size=full]:[max-block-size:none] " +
"data-[part=content]:data-[size=full]:[padding-block:max(var(--space-xl),_env(safe-area-inset-top))_max(var(--space-xl),_env(safe-area-inset-bottom))] " +
"data-[part=content]:data-[size=full]:[padding-inline:max(var(--space-xl),_env(safe-area-inset-left))_max(var(--space-xl),_env(safe-area-inset-right))] " +
"data-[part=content]:data-[size=full]:border-0 " +
"data-[part=content]:data-[size=full]:border-current " +
"data-[part=content]:data-[size=full]:border-none " +
"data-[part=content]:data-[size=full]:rounded-none " +
"[@media_(width_>=_40rem)]:data-[part=content]:data-[align=start]:[&:not([data-size=full])]:[margin-block:var(--space-xl)_auto] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[inline-size:100%] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[max-inline-size:100%] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[min-block-size:50dvh] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[max-block-size:80dvh] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[margin-block:auto_0] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[margin-inline:0] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[padding-block-end:calc(var(--space-xl)_+_env(safe-area-inset-bottom))] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[border-block-end-width:0] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[border-end-start-radius:0] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[border-end-end-radius:0] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full]):has(>[data-uiify-modal][data-part=body])]:[touch-action:none] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])_[data-uiify-modal][data-part=header]]:[touch-action:none] " +
"[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])>[data-part=grabber]]:block " +
"[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:data-[part=content]:[&:not([data-size=full])]:[transition:translate_var(--duration-fast)_var(--ease-out),_opacity_var(--duration-fast)_var(--ease-out),_display_var(--duration-fast)_allow-discrete,_overlay_var(--duration-fast)_allow-discrete] " +
"[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:data-[part=content]:[&:not([data-size=full],[open])]:[translate:0_100%] " +
"[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:data-[part=content]:[&:not([data-size=full],[open])]:[scale:1] " +
"[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:starting:data-[part=content]:[&:not([data-size=full])[open]]:[translate:0_100%] " +
"[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:starting:data-[part=content]:[&:not([data-size=full])[open]]:[scale:1] " +
"data-[part=content]:data-[dragging]:[&[open]:not([data-size=full])]:[transition:none] " +
"data-[part=content]:data-[dragging]:[&[open]:not([data-size=full])]:[translate:0_var(--uiify-sheet-offset,_0)] " +
"data-[part=content]:data-[backdrop=blur]:backdrop:[backdrop-filter:blur(4px)] " +
"data-[part=content]:data-[backdrop=none]:backdrop:bg-transparent " +
"[&[data-part=content]_[data-uiify-modal][data-part=content]::backdrop]:bg-transparent " +
"[&[data-part=content]_[data-uiify-modal][data-part=content]::backdrop]:[backdrop-filter:none] " +
"data-[part=content]:[&:has([data-uiify-modal][data-part=content]:modal)]:[filter:brightness(0.9)] " +
"[@media_(width_>=_40rem)]:data-[part=content]:[&:has([data-uiify-modal][data-part=content]:modal)]:[scale:0.97]",
},
grabber: {
className:
"hidden flex-none [block-size:1.5rem] " +
"[margin-block:calc(-1_*_var(--space-md))_calc(-1_*_var(--space-sm))] " +
"[touch-action:none] after:content-[''] after:block after:[inline-size:2.25rem] " +
"after:[block-size:0.25rem] after:[margin-block:0.5rem_0] " +
"after:[margin-inline:auto] after:[border-radius:999px] after:bg-border",
},
header: { className: "flex-none [padding-inline-end:var(--control-md)]" },
title: {
className: "m-0 text-[length:var(--text-xl)] font-[number:var(--weight-semibold)] " + "[line-height:1.3]",
},
description: {
className:
"[margin-block:var(--space-sm)_var(--space-lg)] text-muted-foreground text-sm " +
"[line-height:var(--leading-normal)]",
},
close: {
className:
"relative isolate inline-flex items-center justify-center gap-1.5 min-h-10 min-w-10 py-px border-[length:var(--border-width)] border-solid border-transparent rounded-full bg-clip-padding text-base font-medium leading-6 no-underline cursor-pointer [transition:var(--transition-colors)] focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_20%,_transparent)] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:border-transparent [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:bg-primary [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-primary-foreground [:checked+&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [:checked+&]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [:checked+&]:border-transparent [:checked+&]:bg-primary [:checked+&]:text-primary-foreground [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true],[aria-current=page]):hover:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:not(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] bg-transparent text-foreground before:content-none after:content-none hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] px-0 absolute [inset-block-start:10px] [inset-inline-end:10px] grid place-items-center empty:before:[all:unset] empty:before:content-[''] empty:before:[grid-area:1/1] empty:before:[inline-size:0.875rem] empty:before:[block-size:2px] empty:before:[border-radius:1px] empty:before:bg-current empty:before:[rotate:45deg] empty:after:[all:unset] empty:after:content-[''] empty:after:[grid-area:1/1] empty:after:[inline-size:0.875rem] empty:after:[block-size:2px] empty:after:[border-radius:1px] empty:after:bg-current empty:after:[rotate:-45deg]",
},
body: { className: "flex-auto [min-block-size:0] overflow-auto overscroll-contain " + "[touch-action:pan-y]" },
footer: { className: "flex flex-none flex-wrap justify-end gap-sm" },
}}
/>
);
}API
Modal
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
id* | string | — | מזהה ייחודי לתיבת הדו-שיח שאליו מכוונים כפתורי הפתיחה והסגירה, כדי שהתיבה תיפתח ותיסגר לפני ההידרציה. |
trigger* | ReactNode | — | תוכן הכפתור שפותח את תיבת הדו-שיח. |
title* | ReactNode | — | כותרת המוצגת בתוך תיבת הדו-שיח ומשמשת כשמה הנגיש. |
description | ReactNode | — | טקסט משלים המוצג מתחת לכותרת ומקושר באמצעות aria-describedby. |
children | ReactNode | — | תוכן הגוף, המוצג ב-ModalBody הנגלל. |
closeLabel | string | "Close" | השם הנגיש של אייקון הסגירה בכותרת העליונה. |
footer | ReactNode | — | שורת פעולות המקובעת מתחת לגוף. יש להציב כאן ModalClose לכפתור סגירה תחתון. |
size | "sm" | "md" | "lg" | "full" | "md" | מדרגת רוחב (24, 28, 40rem). הערך full ממלא את אזור התצוגה. נכתב ל-data-size; לרוחב מותאם אישית יש לדרוס את --uiify-modal-inline-size. |
align | "center" | "start" | "center" | מיקום אנכי בטאבלט ובמחשב. בטלפונים מוצג לוח תחתון אלא אם size הוא full. |
dismiss | "any" | "closerequest" | "none" | "any" | closedby: הערך any מוסיף לחיצות על הרקע, closerequest מאפשר רק Escape, ו-none רק את הפקדים שלכם. Safari אינו תומך ב-closedby, ולכן Escape תמיד סוגר; UIEnhance מטפל בלחיצות על הרקע (any) ובהחלקת הלוח (כשאינו none). |
backdrop | "opaque" | "blur" | "none" | "opaque" | מראה הרקע. נכתב ל-data-backdrop. |
className | string | — | המחלקה המוחלת על אלמנט השורש של Modal. |
slotProps | ModalSlotProps | — | מאפיינים מובנים לחלקים הפנימיים ש-Modal יוצר; השורש שלו מקבל מאפיינים מובנים ישירות. |
ModalTrigger
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
target* | string | — | מזהה ה-ModalContent שכפתור זה פותח; הדפדפן מאתר אותו בזמן הלחיצה, ולכן לא מופעל JavaScript שלנו. |
children | ReactNode | — | תוכן כפתור הפתיחה. |
as | TAs & NativeButtonAs<TAs> | "button" | רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי. |
ModalContent
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
id* | string | — | מזהה ייחודי שמספק המשתמש בספרייה; כפתור הפתיחה ויעד הסגירה מפנים אליו. |
size | "sm" | "md" | "lg" | "full" | "md" | מדרגת רוחב (24, 28, 40rem). הערך full ממלא את אזור התצוגה. נכתב ל-data-size; לרוחב מותאם אישית יש לדרוס את --uiify-modal-inline-size. |
align | "center" | "start" | "center" | מיקום אנכי בטאבלט ובמחשב. בטלפונים מוצג לוח תחתון אלא אם size הוא full. |
dismiss | "any" | "closerequest" | "none" | "any" | closedby: הערך any מוסיף לחיצות על הרקע, closerequest מאפשר רק Escape, ו-none רק את הפקדים שלכם. Safari אינו תומך ב-closedby, ולכן Escape תמיד סוגר; UIEnhance מטפל בלחיצות על הרקע (any) ובהחלקת הלוח (כשאינו none). |
backdrop | "opaque" | "blur" | "none" | "opaque" | מראה הרקע. נכתב ל-data-backdrop. |
children | ReactNode | — | תוכן תיבת הדו-שיח. |
ModalClose
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
target* | string | — | מזהה ה-ModalContent שכפתור זה סוגר. |
children | ReactNode | — | תוכן כפתור הסגירה. |
as | TAs & NativeButtonAs<TAs> | "button" | רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי. |
ModalHeader
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | שורה עליונה מקובעת המכילה את הכותרת, התיאור ופקד הסגירה. |
ModalTitle
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | השם הנגיש של תיבת הדו-שיח; יש להפנות למזהה שלו מ-aria-labelledby. |
ModalDescription
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | טקסט משלים; יש להפנות למזהה שלו מ-aria-describedby. |
ModalBody
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | אזור הגלילה בין הכותרת העליונה לתחתית. |
ModalFooter
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | שורת פעולות המקובעת מתחת לגוף. |
ModalGrabber
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
children | ReactNode | — | ידית גרירה דקורטיבית בלוח הנייד; ההחלקה מתחילה כאן. |
ModalClientContent (@gunkin/uiify/components/modal/client)
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
id* | string | — | מזהה ייחודי שמספק המשתמש בספרייה; חלקי הפתיחה והסגירה מפנים אליו. |
open | boolean | — | מצב פתיחה מבוקר, הקובע לאחר ההידרציה. |
defaultOpen | boolean | — | מצב התחלתי לא מבוקר, המוחל באמצעות showModal() לאחר ההידרציה; ה-HTML מהשרת נשאר סגור. |
onOpenChange | (open: boolean) => void | — | מופעלת פעם אחת לכל שינוי מובנה או מתוכנת עם המצב החדש, לרבות פעם אחת בעת הטעינה אם הדפדפן פתח את התיבה לפני ההידרציה. |
onOpenChangeComplete | (open: boolean) => void | — | מופעלת פעם אחת לכל מעבר פתיחה או סגירה לאחר סיום מעברי ה-CSS שלו, או מיד אם אין מעברים, לרבות מעבר defaultOpen לאחר ההידרציה; לעולם לא כשתיבת הדו-שיח נשארת ללא שינוי. |
size | "sm" | "md" | "lg" | "full" | "md" | מדרגת רוחב (24, 28, 40rem). הערך full ממלא את אזור התצוגה. נכתב ל-data-size; לרוחב מותאם אישית יש לדרוס את --uiify-modal-inline-size. |
align | "center" | "start" | "center" | מיקום אנכי בטאבלט ובמחשב. בטלפונים מוצג לוח תחתון אלא אם size הוא full. |
dismiss | "any" | "closerequest" | "none" | "any" | closedby: הערך any מוסיף לחיצות על הרקע, closerequest מאפשר רק Escape, ו-none רק את הפקדים שלכם. Safari אינו תומך ב-closedby, ולכן Escape תמיד סוגר; UIEnhance מטפל בלחיצות על הרקע (any) ובהחלקת הלוח (כשאינו none). |
backdrop | "opaque" | "blur" | "none" | "opaque" | מראה הרקע. נכתב ל-data-backdrop. |
children | ReactNode | — | תוכן תיבת הדו-שיח. |
מקלדת
| מקש | פעולה |
|---|---|
| Escape | סגירה (רק המודאל העליון במקרה של קינון) |
| Tab | מעבר מחזורי של המיקוד בתוך המודאל |
| Shift+Tab | סדר Tab הפוך |
נגישות
אלמנט <dialog> מובנה שנפתח באמצעות מפעיל עם command="show-modal" לפני הרצת JavaScript כלשהו. הדפדפן אחראי למודאליות, לכליאת המיקוד, ל-Escape ולהחזרת המיקוד; הסגירה משתמשת ב-request-close כך ש-onCancel יכול למנוע אותה. סמל הסגירה בכותרת תמיד קיים. בטלפונים המודאל הוא יריעה תחתונה; החלקה כלפי מטה והקשה על הרקע הן קיצורי דרך, ולעולם אינן הדרך היחידה לצאת.
נקודות חיבור לעיצוב
data-uiify-modaldata-part="trigger|content|header|title|description|body|footer|grabber|close"data-size="sm|lg|full"data-align="start"data-backdrop="blur|none"--uiify-trigger-open (inside [data-uiify-modal][data-part="root"])--uiify-modal-inline-size (dialog width)