@mittwald/flow-react-components 1.5.0-next.36 → 1.5.0-next.37

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.module.mjs","names":[],"sources":["../../../../../../../src/components/Modal/Modal.module.scss"],"sourcesContent":["@use \"@/styles/mixins/scrollbar\";\n\n/* Caps the dialog width per size token (`size-s` / `size-m` / `size-l`). */\n@mixin size($size) {\n &.size-#{$size} {\n [role=\"dialog\"] {\n width: inherit;\n max-width: var(--modal--size--#{$size});\n }\n }\n}\n\n/* ===========================================================================\n Base — shared by Modal (centered / bottom sheet) and OffCanvas (side panel).\n =========================================================================== */\n.modal,\n.offCanvas {\n /* Overlay.module.scss lays the viewport out; the dialog inside it is the only\n pointer target. */\n > :global(.flow--overlay--viewport) {\n pointer-events: none;\n\n /* react-aria's own modal wrapper — it gets no className from Flow, so\n its default `react-aria-Modal` class is the handle. */\n > :global(.react-aria-Modal) {\n display: flex;\n justify-content: center;\n width: inherit;\n max-height: var(--visual-viewport-height);\n padding: var(--size-px--xl);\n }\n }\n\n /* Close button: hidden when an action group is present, unless forced. */\n &:has(.actionGroup) {\n .closeButton:not(.alwaysVisible) {\n display: none;\n }\n }\n\n .closeButton.alwaysHidden {\n display: none;\n }\n\n /* The dialog card itself — a flex column. It is also the scroll container on\n mobile, and the sticky header/footer stack against its content only. */\n [role=\"dialog\"] {\n display: flex;\n flex-direction: column;\n isolation: isolate;\n max-height: 100%;\n overflow: hidden;\n outline: none;\n pointer-events: all;\n background-color: var(--modal--background-color);\n border: var(--modal--border-width) var(--modal--border-style)\n var(--modal--border-color);\n\n // A consumer `Form` (or raw `<form>`) wrapping the modal content. `Form`\n // is a plain FC, so the props context cannot give it a class.\n // stylelint-disable-next-line selector-max-type -- consumer form, no class\n > form {\n display: contents;\n }\n }\n\n /* Header */\n // A `Header`/`SectionHeader` — or a raw `<header>` — the consumer places in\n // the modal; both must match.\n // stylelint-disable-next-line selector-max-type -- consumer-placed header\n header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: var(--heading--icon-to-text-spacing);\n padding-top: var(--modal--header-padding-top);\n padding-inline: var(--modal--padding);\n\n /* Flatten the heading's text and content spans into the header row, so\n badges stay behind the title instead of wrapping onto their own line.\n Heading's spans do carry classes, but `.flow--heading--heading-content`\n already sets `display: none` on `:empty` at the same specificity a class\n selector here would reach — source order would then decide. */\n // stylelint-disable-next-line selector-max-type -- see above\n > span {\n display: contents;\n }\n\n :global(.flow--heading--heading-text-spacer) {\n display: none;\n }\n\n :global(.flow--heading--heading-content)\n :global(.flow--heading--heading-content-item):not(:last-child) {\n margin-inline-end: 0;\n }\n\n .headerTitle {\n min-width: 0;\n }\n\n .closeButton {\n order: 1;\n margin-inline-start: auto;\n }\n }\n\n /* Content — the scrollable body */\n .content,\n .columnLayout {\n @include scrollbar.scrollbar;\n\n flex-grow: 1;\n min-height: var(--modal--content-min-height);\n padding: var(--modal--padding);\n overflow: hidden auto;\n background-color: var(--modal--background-color);\n }\n\n .columnLayout .accentBox {\n position: sticky;\n top: 0;\n height: fit-content;\n }\n\n /* Action group — the footer */\n .actionGroup {\n padding: var(--modal--padding);\n background-color: var(--modal--footer-background-color);\n }\n\n /* ActionGroup's \"sorted\" layout sets flex-grow:1 on itself; the modal footer\n must keep its natural height (otherwise it fills the full-height off-canvas\n panel). Scope via the dialog so flex-grow:0 wins the equal-specificity /\n source-order battle — without raising the specificity of the padding above,\n which the mobile rules override. */\n [role=\"dialog\"] .actionGroup {\n flex-grow: 0;\n }\n}\n\n/* ===========================================================================\n Modal — desktop (centered dialog, zoom in/out).\n =========================================================================== */\n@media (width > 768px) {\n .modal {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n border-radius: var(--modal--corner-radius);\n }\n\n > :global(.flow--overlay--viewport) > :global(.react-aria-Modal) {\n &[data-entering] {\n animation: modal-zoom var(--transition--duration--slow)\n cubic-bezier(0.1, 0.8, 0.3, 1.2);\n }\n\n &[data-exiting] {\n animation: modal-zoom var(--transition--duration--default)\n cubic-bezier(0.1, 0.8, 0.3, 1.2) reverse;\n }\n }\n }\n}\n\n/* ===========================================================================\n OffCanvas — side panel (all widths), full height, slides in from the side.\n =========================================================================== */\n.offCanvas {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n height: 100%;\n border-radius: 0;\n }\n\n > :global(.flow--overlay--viewport) {\n padding-inline-start: var(--size-px--m);\n\n > :global(.react-aria-Modal) {\n justify-content: flex-end;\n width: inherit;\n height: inherit;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-right var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-right\n var(--transition--duration--default) reverse;\n }\n }\n }\n\n &.left {\n > :global(.flow--overlay--viewport) {\n padding-inline: 0 var(--size-px--m);\n\n > :global(.react-aria-Modal) {\n justify-content: flex-start;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-left var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-left\n var(--transition--duration--default) reverse;\n }\n }\n }\n }\n}\n\n/* ===========================================================================\n Modal — mobile (bottom sheet), issue #2423.\n\n One scroll container: the dialog scrolls while the header sticks to the top\n and the footer to the bottom. While the virtual keyboard is open\n (.keyboardVisible) both un-stick so header + content + footer flow normally\n and the content gets the full available space.\n =========================================================================== */\n@media (max-width: 768px) {\n .modal {\n /* Bottom-anchored sheet: rounded top corners + slide-up animation. */\n > :global(.flow--overlay--viewport) > :global(.react-aria-Modal) {\n margin-top: auto;\n width: 100%;\n padding: unset;\n overflow: hidden;\n border-start-start-radius: var(--modal--corner-radius);\n border-start-end-radius: var(--modal--corner-radius);\n pointer-events: all;\n\n &[data-entering] {\n animation: modal-slide-up var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-up var(--transition--duration--default) reverse;\n }\n }\n\n /* The dialog is the single scroll container. Capped just below the visual\n viewport so a full-height sheet keeps a small gap from the top edge. */\n [role=\"dialog\"] {\n @include scrollbar.scrollbar;\n\n width: inherit;\n max-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n overflow: hidden auto;\n }\n\n // stylelint-disable-next-line selector-max-type -- consumer form, see above\n [role=\"dialog\"] > form {\n width: inherit;\n }\n\n /* flex: 0 0 auto keeps natural heights; without flex-shrink:0 the flex\n column compresses the content box and the (overflow:visible) fields\n spill over the footer. */\n .header,\n .content,\n .columnLayout,\n .actionGroup {\n flex: 0 0 auto;\n }\n\n .content,\n .columnLayout {\n min-height: 0;\n overflow: visible;\n }\n\n /* Docked header + footer (sticky within the single scroll container). */\n .header {\n position: sticky;\n top: 0;\n z-index: 1;\n background-color: var(--modal--background-color);\n }\n\n .actionGroup {\n position: sticky;\n bottom: 0;\n z-index: 1;\n }\n }\n\n /* Keyboard open: un-stick both so everything flows for maximum typing space.\n The dialog fills the available height and the footer uses margin-top:auto,\n so with SHORT content the footer still sits at the bottom (not mid-air) and\n with tall content it scrolls away with everything else. Never hidden. */\n .modal.keyboardVisible {\n [role=\"dialog\"] {\n min-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n }\n\n .header,\n .actionGroup {\n position: static;\n }\n\n .actionGroup {\n margin-top: auto;\n }\n }\n\n /* Shared mobile spacing + full-width stacked actions (Modal + OffCanvas). */\n .modal,\n .offCanvas {\n .header {\n padding-inline: var(--modal--padding-mobile);\n }\n\n .content,\n .columnLayout,\n .actionGroup {\n padding: var(--modal--padding-mobile);\n }\n\n .actionGroup {\n flex-direction: column;\n\n // The footer's buttons are consumer `Button`/`Action` children; a raw\n // `<button>` matches today too.\n // stylelint-disable-next-line selector-max-type -- consumer children\n button {\n width: 100%;\n }\n }\n }\n}\n\n/* ===========================================================================\n Animations\n =========================================================================== */\n@keyframes modal-zoom {\n /* Start with scale(1) to let ResizeObservers take the correct width on mount. */\n 0% {\n opacity: 0;\n transform: scale(1);\n }\n\n 0.1% {\n opacity: 1;\n transform: scale(0.8);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n@keyframes modal-slide-up {\n from {\n translate: 0 30%;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-right {\n from {\n translate: 30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-left {\n from {\n translate: -30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"Modal.module.mjs","names":[],"sources":["../../../../../../../src/components/Modal/Modal.module.scss"],"sourcesContent":["@use \"@/styles/mixins/scrollbar\";\n\n/* Caps the dialog width per size token (`size-s` / `size-m` / `size-l`). */\n@mixin size($size) {\n &.size-#{$size} {\n [role=\"dialog\"] {\n width: inherit;\n max-width: var(--modal--size--#{$size});\n }\n }\n}\n\n/* ===========================================================================\n Base — shared by Modal (centered / bottom sheet) and OffCanvas (side panel).\n =========================================================================== */\n.modal,\n.offCanvas {\n /* Overlay.module.scss lays the viewport out; the dialog inside it is the only\n pointer target. */\n > :global(.flow--overlay--viewport) {\n pointer-events: none;\n\n /* react-aria's own modal wrapper — it gets no className from Flow, so\n its default `react-aria-Modal` class is the handle. */\n > :global(.react-aria-Modal) {\n display: flex;\n justify-content: center;\n width: inherit;\n max-height: var(--visual-viewport-height);\n padding: var(--size-px--xl);\n }\n }\n\n /* Close button: hidden when an action group is present, unless forced. */\n &:has(.actionGroup) {\n .closeButton:not(.alwaysVisible) {\n display: none;\n }\n }\n\n .closeButton.alwaysHidden {\n display: none;\n }\n\n /* The dialog card itself — a flex column. It is also the scroll container on\n mobile, and the sticky header/footer stack against its content only. */\n [role=\"dialog\"] {\n display: flex;\n flex-direction: column;\n isolation: isolate;\n max-height: 100%;\n overflow: hidden;\n outline: none;\n pointer-events: all;\n background-color: var(--modal--background-color);\n border: var(--modal--border-width) var(--modal--border-style)\n var(--modal--border-color);\n\n // A consumer `Form` (or raw `<form>`) wrapping the modal content. `Form`\n // is a plain FC, so the props context cannot give it a class.\n // stylelint-disable-next-line selector-max-type -- consumer form, no class\n > form {\n display: contents;\n }\n }\n\n /* Header */\n // A `Header`/`SectionHeader` — or a raw `<header>` — the consumer places in\n // the modal; both must match.\n // stylelint-disable-next-line selector-max-type -- consumer-placed header\n header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: var(--heading--icon-to-text-spacing);\n padding-top: var(--modal--header-padding-top);\n padding-inline: var(--modal--padding);\n\n /* Flatten the heading's text and content spans into the header row, so\n badges stay behind the title instead of wrapping onto their own line.\n Heading's spans do carry classes, but `.flow--heading--heading-content`\n already sets `display: none` on `:empty` at the same specificity a class\n selector here would reach — source order would then decide. */\n // stylelint-disable-next-line selector-max-type -- see above\n > span {\n display: contents;\n }\n\n :global(.flow--heading--heading-text-spacer) {\n display: none;\n }\n\n :global(.flow--heading--heading-content)\n :global(.flow--heading--heading-content-item):not(:last-child) {\n margin-inline-end: 0;\n }\n\n .headerTitle {\n min-width: 0;\n }\n\n /* Remote apps built against Flow < 1.2.0 send the close button without\n ClearPropsContext, so the Heading tunnels it into a content item — that\n item is the flex item then — and passes it its button size `s`. */\n .closeButton,\n :global(.flow--heading--heading-content-item):has(.closeButton) {\n order: 1;\n margin-inline-start: auto;\n }\n\n .closeButton:global(.flow--button--size-s) {\n padding: var(--button--padding-icon-only);\n border-radius: var(--button--corner-radius);\n\n :global(.flow--icon) {\n width: var(--icon--size--m);\n height: var(--icon--size--m);\n }\n }\n }\n\n /* Content — the scrollable body */\n .content,\n .columnLayout {\n @include scrollbar.scrollbar;\n\n flex-grow: 1;\n min-height: var(--modal--content-min-height);\n padding: var(--modal--padding);\n overflow: hidden auto;\n background-color: var(--modal--background-color);\n }\n\n .columnLayout .accentBox {\n position: sticky;\n top: 0;\n height: fit-content;\n }\n\n /* Action group — the footer */\n .actionGroup {\n padding: var(--modal--padding);\n background-color: var(--modal--footer-background-color);\n }\n\n /* ActionGroup's \"sorted\" layout sets flex-grow:1 on itself; the modal footer\n must keep its natural height (otherwise it fills the full-height off-canvas\n panel). Scope via the dialog so flex-grow:0 wins the equal-specificity /\n source-order battle — without raising the specificity of the padding above,\n which the mobile rules override. */\n [role=\"dialog\"] .actionGroup {\n flex-grow: 0;\n }\n}\n\n/* ===========================================================================\n Modal — desktop (centered dialog, zoom in/out).\n =========================================================================== */\n@media (width > 768px) {\n .modal {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n border-radius: var(--modal--corner-radius);\n }\n\n > :global(.flow--overlay--viewport) > :global(.react-aria-Modal) {\n &[data-entering] {\n animation: modal-zoom var(--transition--duration--slow)\n cubic-bezier(0.1, 0.8, 0.3, 1.2);\n }\n\n &[data-exiting] {\n animation: modal-zoom var(--transition--duration--default)\n cubic-bezier(0.1, 0.8, 0.3, 1.2) reverse;\n }\n }\n }\n}\n\n/* ===========================================================================\n OffCanvas — side panel (all widths), full height, slides in from the side.\n =========================================================================== */\n.offCanvas {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n height: 100%;\n border-radius: 0;\n }\n\n > :global(.flow--overlay--viewport) {\n padding-inline-start: var(--size-px--m);\n\n > :global(.react-aria-Modal) {\n justify-content: flex-end;\n width: inherit;\n height: inherit;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-right var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-right\n var(--transition--duration--default) reverse;\n }\n }\n }\n\n &.left {\n > :global(.flow--overlay--viewport) {\n padding-inline: 0 var(--size-px--m);\n\n > :global(.react-aria-Modal) {\n justify-content: flex-start;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-left var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-left\n var(--transition--duration--default) reverse;\n }\n }\n }\n }\n}\n\n/* ===========================================================================\n Modal — mobile (bottom sheet), issue #2423.\n\n One scroll container: the dialog scrolls while the header sticks to the top\n and the footer to the bottom. While the virtual keyboard is open\n (.keyboardVisible) both un-stick so header + content + footer flow normally\n and the content gets the full available space.\n =========================================================================== */\n@media (max-width: 768px) {\n .modal {\n /* Bottom-anchored sheet: rounded top corners + slide-up animation. */\n > :global(.flow--overlay--viewport) > :global(.react-aria-Modal) {\n margin-top: auto;\n width: 100%;\n padding: unset;\n overflow: hidden;\n border-start-start-radius: var(--modal--corner-radius);\n border-start-end-radius: var(--modal--corner-radius);\n pointer-events: all;\n\n &[data-entering] {\n animation: modal-slide-up var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-up var(--transition--duration--default) reverse;\n }\n }\n\n /* The dialog is the single scroll container. Capped just below the visual\n viewport so a full-height sheet keeps a small gap from the top edge. */\n [role=\"dialog\"] {\n @include scrollbar.scrollbar;\n\n width: inherit;\n max-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n overflow: hidden auto;\n }\n\n // stylelint-disable-next-line selector-max-type -- consumer form, see above\n [role=\"dialog\"] > form {\n width: inherit;\n }\n\n /* flex: 0 0 auto keeps natural heights; without flex-shrink:0 the flex\n column compresses the content box and the (overflow:visible) fields\n spill over the footer. */\n .header,\n .content,\n .columnLayout,\n .actionGroup {\n flex: 0 0 auto;\n }\n\n .content,\n .columnLayout {\n min-height: 0;\n overflow: visible;\n }\n\n /* Docked header + footer (sticky within the single scroll container). */\n .header {\n position: sticky;\n top: 0;\n z-index: 1;\n background-color: var(--modal--background-color);\n }\n\n .actionGroup {\n position: sticky;\n bottom: 0;\n z-index: 1;\n }\n }\n\n /* Keyboard open: un-stick both so everything flows for maximum typing space.\n The dialog fills the available height and the footer uses margin-top:auto,\n so with SHORT content the footer still sits at the bottom (not mid-air) and\n with tall content it scrolls away with everything else. Never hidden. */\n .modal.keyboardVisible {\n [role=\"dialog\"] {\n min-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n }\n\n .header,\n .actionGroup {\n position: static;\n }\n\n .actionGroup {\n margin-top: auto;\n }\n }\n\n /* Shared mobile spacing + full-width stacked actions (Modal + OffCanvas). */\n .modal,\n .offCanvas {\n .header {\n padding-inline: var(--modal--padding-mobile);\n }\n\n .content,\n .columnLayout,\n .actionGroup {\n padding: var(--modal--padding-mobile);\n }\n\n .actionGroup {\n flex-direction: column;\n\n // The footer's buttons are consumer `Button`/`Action` children; a raw\n // `<button>` matches today too.\n // stylelint-disable-next-line selector-max-type -- consumer children\n button {\n width: 100%;\n }\n }\n }\n}\n\n/* ===========================================================================\n Animations\n =========================================================================== */\n@keyframes modal-zoom {\n /* Start with scale(1) to let ResizeObservers take the correct width on mount. */\n 0% {\n opacity: 0;\n transform: scale(1);\n }\n\n 0.1% {\n opacity: 1;\n transform: scale(0.8);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n@keyframes modal-slide-up {\n from {\n translate: 0 30%;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-right {\n from {\n translate: 30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-left {\n from {\n translate: -30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n"],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/flow-react-components",
3
- "version": "1.5.0-next.36",
3
+ "version": "1.5.0-next.37",
4
4
  "type": "module",
5
5
  "description": "A React implementation of Flow, mittwald’s design system",
6
6
  "homepage": "https://flow.mittwald.de",
@@ -84,9 +84,9 @@
84
84
  "@internationalized/string-compiler": "^3.4.1",
85
85
  "@lezer/common": "^1.5.2",
86
86
  "@lezer/highlight": "^1.2.3",
87
- "@mittwald/flow-icons": "^1.5.0-next.36",
87
+ "@mittwald/flow-icons": "^1.5.0-next.37",
88
88
  "@mittwald/password-tools-js": "^3.0.0",
89
- "@mittwald/react-tunnel": "^1.5.0-next.36",
89
+ "@mittwald/react-tunnel": "^1.5.0-next.37",
90
90
  "@mittwald/react-use-promise": "^4.2.2",
91
91
  "@react-aria/form": "^3.2.1",
92
92
  "@react-aria/i18n": "^3.13.1",
@@ -139,12 +139,12 @@
139
139
  "@internationalized/date": "^3.12.3",
140
140
  "@lezer/generator": "^1.8.0",
141
141
  "@lezer/lr": "^1.4.10",
142
- "@mittwald/flow-core": "1.5.0-next.36",
143
- "@mittwald/flow-design-tokens": "1.5.0-next.36",
144
- "@mittwald/flow-icons-base": "1.5.0-next.36",
142
+ "@mittwald/flow-core": "1.5.0-next.37",
143
+ "@mittwald/flow-design-tokens": "1.5.0-next.37",
144
+ "@mittwald/flow-icons-base": "1.5.0-next.37",
145
145
  "@mittwald/react-use-promise": "^4.2.2",
146
146
  "@mittwald/remote-dom-react": "1.2.2-mittwald.10",
147
- "@mittwald/typescript-config": "1.5.0-next.36",
147
+ "@mittwald/typescript-config": "1.5.0-next.37",
148
148
  "@nx/storybook": "^22.7.6",
149
149
  "@storybook/addon-a11y": "^10.5.10",
150
150
  "@storybook/addon-actions": "^9.0.8",
@@ -198,7 +198,7 @@
198
198
  },
199
199
  "peerDependencies": {
200
200
  "@internationalized/date": "^3.12.2",
201
- "@mittwald/flow-icons-pro": "1.5.0-next.36",
201
+ "@mittwald/flow-icons-pro": "1.5.0-next.37",
202
202
  "@mittwald/react-use-promise": "^4.2.2",
203
203
  "next": "^16.2.3",
204
204
  "react": "^19.2.0",
@@ -222,5 +222,5 @@
222
222
  "browserslist": [
223
223
  "baseline widely available"
224
224
  ],
225
- "gitHead": "c9ad3b6882bd1ed51da10c4aba45074155770cb3"
225
+ "gitHead": "55a70fecdd275e5beca4858407e6093e050105d0"
226
226
  }