diff --git a/docs/styleguide/components/interactive-elements.html b/docs/styleguide/components/interactive-elements.html index 1ccb7f9..c0cf881 100644 --- a/docs/styleguide/components/interactive-elements.html +++ b/docs/styleguide/components/interactive-elements.html @@ -836,6 +836,21 @@ + +
+

Component: Tooltip

+ + + + + + +
+ +

Component: File Input

diff --git a/docs/styleguide/scripts/tooltip-overlays.js b/docs/styleguide/scripts/tooltip-overlays.js new file mode 100644 index 0000000..8bd2e64 --- /dev/null +++ b/docs/styleguide/scripts/tooltip-overlays.js @@ -0,0 +1,164 @@ +(function initTooltipOverlayModule() { + const getViewportWidth = () => { + if (window.visualViewport && typeof window.visualViewport.width === 'number') { + return window.visualViewport.width; + } + return window.innerWidth; + }; + + const getPixelValue = (propertyName) => { + const rootStyles = getComputedStyle(document.documentElement); + const rawValue = rootStyles.getPropertyValue(propertyName).trim(); + const rootFontSize = parseFloat(rootStyles.fontSize) || 16; + const value = parseFloat(rawValue); + + if (Number.isNaN(value)) { + return 0; + } + if (rawValue.endsWith('rem')) { + return value * rootFontSize; + } + return value; + }; + + const setTooltipState = (wrap, isOpen) => { + const trigger = wrap.querySelector('[data-component-part="tooltip-trigger"]'); + const panel = wrap.querySelector('[data-component-part="tooltip-panel"]'); + if (!trigger || !panel) { + return; + } + + wrap.dataset.tooltipOpen = String(isOpen); + panel.hidden = !isOpen; + trigger.setAttribute('aria-expanded', String(isOpen)); + }; + + const positionTooltip = (wrap) => { + const trigger = wrap.querySelector('[data-component-part="tooltip-trigger"]'); + const panel = wrap.querySelector('[data-component-part="tooltip-panel"]'); + if (!trigger || !panel || panel.hidden) { + return; + } + + const viewportWidth = getViewportWidth(); + const safeInset = getPixelValue('--spacing-large'); + const triggerRect = trigger.getBoundingClientRect(); + const panelRect = panel.getBoundingClientRect(); + const gap = getPixelValue('--spacing-small'); + + let placement = 'bottom'; + if (triggerRect.right + (panelRect.width / 2) > viewportWidth - safeInset) { + placement = 'left'; + } else if (triggerRect.left - (panelRect.width / 2) < safeInset) { + placement = 'right'; + } + + wrap.dataset.tooltipPlacement = placement; + + if (placement === 'bottom') { + const nextPanelRect = panel.getBoundingClientRect(); + if (nextPanelRect.bottom > window.innerHeight - safeInset + && triggerRect.top - panelRect.height - gap >= safeInset) { + wrap.dataset.tooltipPlacement = 'top'; + } + } + }; + + const closeTooltip = (wrap) => { + wrap.dataset.tooltipPinned = 'false'; + wrap.dataset.tooltipHover = 'false'; + wrap.dataset.tooltipFocus = 'false'; + wrap.dataset.tooltipDismissed = 'false'; + setTooltipState(wrap, false); + }; + + const openTooltip = (wrap) => { + setTooltipState(wrap, true); + positionTooltip(wrap); + }; + + const initTooltip = (wrap) => { + if (wrap.dataset.tooltipInit === 'true') { + return; + } + wrap.dataset.tooltipInit = 'true'; + wrap.dataset.tooltipPlacement = 'bottom'; + wrap.dataset.tooltipPinned = 'false'; + wrap.dataset.tooltipHover = 'false'; + wrap.dataset.tooltipFocus = 'false'; + wrap.dataset.tooltipDismissed = 'false'; + setTooltipState(wrap, false); + + const trigger = wrap.querySelector('[data-component-part="tooltip-trigger"]'); + if (!trigger) { + return; + } + + wrap.addEventListener('pointerenter', () => { + if (wrap.dataset.tooltipDismissed === 'true') { + return; + } + wrap.dataset.tooltipHover = 'true'; + openTooltip(wrap); + }); + + wrap.addEventListener('pointerleave', () => { + wrap.dataset.tooltipHover = 'false'; + wrap.dataset.tooltipDismissed = 'false'; + if (wrap.dataset.tooltipPinned !== 'true' && wrap.dataset.tooltipFocus !== 'true') { + setTooltipState(wrap, false); + } + }); + + wrap.addEventListener('focusin', () => { + if (wrap.dataset.tooltipDismissed === 'true') { + return; + } + wrap.dataset.tooltipFocus = 'true'; + openTooltip(wrap); + }); + + wrap.addEventListener('focusout', (event) => { + if (wrap.contains(event.relatedTarget)) { + return; + } + wrap.dataset.tooltipFocus = 'false'; + wrap.dataset.tooltipDismissed = 'false'; + if (wrap.dataset.tooltipPinned !== 'true' && wrap.dataset.tooltipHover !== 'true') { + setTooltipState(wrap, false); + } + }); + + trigger.addEventListener('click', (event) => { + event.stopPropagation(); + const isPinned = wrap.dataset.tooltipPinned === 'true'; + wrap.dataset.tooltipPinned = String(!isPinned); + + if (isPinned) { + wrap.dataset.tooltipDismissed = 'true'; + setTooltipState(wrap, false); + return; + } + + wrap.dataset.tooltipDismissed = 'false'; + openTooltip(wrap); + }); + }; + + document.querySelectorAll('.sg-tooltip-wrap').forEach(initTooltip); + + document.addEventListener('click', (event) => { + document.querySelectorAll('.sg-tooltip-wrap').forEach((wrap) => { + if (!wrap.contains(event.target)) { + closeTooltip(wrap); + } + }); + }); + + const repositionOpenTooltips = () => { + document.querySelectorAll('.sg-tooltip-wrap[data-tooltip-open="true"]').forEach(positionTooltip); + }; + + window.addEventListener('resize', repositionOpenTooltips); + window.addEventListener('scroll', repositionOpenTooltips, true); +})(); diff --git a/docs/styleguide/semantic-tokens-components.html b/docs/styleguide/semantic-tokens-components.html index 1f4629d..d9849f4 100644 --- a/docs/styleguide/semantic-tokens-components.html +++ b/docs/styleguide/semantic-tokens-components.html @@ -224,6 +224,10 @@ text-help-iconcolor-font-darkTextfarbe im Help-Icon. text-help-panelcolor-font-darkTextfarbe im Help-Panel. layout-help-panel-widthcalc(interaction-height * 8)Standardbreite des Help-Panels. + surface-tooltipcolor-light-greyFläche der generischen Tooltip-Sprechblase. + text-tooltipcolor-font-darkTextfarbe der generischen Tooltip-Sprechblase. + layout-tooltip-widthcalc(interaction-height * 8)Standardbreite der generischen Tooltip-Sprechblase. + radius-tooltipradius-interactionRadius der generischen Tooltip-Sprechblase. icon-sandwich-line-portalcolor-font-darkLinienfarbe des Sandwich-Icons für große und kleine Variante. diff --git a/docs/styleguide/styles/01-foundations.css b/docs/styleguide/styles/01-foundations.css index 21b4d8c..8dd7b93 100644 --- a/docs/styleguide/styles/01-foundations.css +++ b/docs/styleguide/styles/01-foundations.css @@ -62,6 +62,7 @@ --surface-menu-panel-portal: var(--color-light-grey); --surface-help-icon: var(--color-medium-grey); --surface-help-panel: var(--color-light-grey); + --surface-tooltip: var(--color-light-grey); --surface-pulldown-panel: var(--color-light-grey); --surface-activatable-remove: var(--surface-control-default); --surface-slider-track: var(--color-medium-grey); @@ -89,6 +90,8 @@ --layout-mode-toggle-local-width-factor: 3; --layout-mode-toggle-width: calc(var(--interaction-height) * 3.5); --layout-help-panel-width: calc(var(--interaction-height) * 8); + --layout-tooltip-width: calc(var(--interaction-height) * 8); + --radius-tooltip: var(--radius-interaction); --text-control-default: var(--color-font-dark); --text-control-disabled: var(--color-dark-grey); --text-button-process: var(--color-font-light); @@ -98,6 +101,7 @@ --text-menu-link-portal: var(--color-font-dark); --text-help-icon: var(--color-font-dark); --text-help-panel: var(--color-font-dark); + --text-tooltip: var(--color-font-dark); --text-activatable-remove: var(--text-control-default); --text-hyperlink: var(--color-font-hyperlink); --icon-pulldown-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2l4 4 4-4' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E"); diff --git a/docs/styleguide/styles/10-components-interactive-elements.css b/docs/styleguide/styles/10-components-interactive-elements.css index 765fadd..c72b000 100644 --- a/docs/styleguide/styles/10-components-interactive-elements.css +++ b/docs/styleguide/styles/10-components-interactive-elements.css @@ -1239,6 +1239,95 @@ right: 0; } + .sg-tooltip-wrap { + position: relative; + display: inline-flex; + vertical-align: middle; + } + + .sg-tooltip-panel { + position: absolute; + z-index: var(--layer-pulldown-panel); + display: block; + width: var(--layout-tooltip-width); + max-width: calc(100vw - (2 * var(--spacing-large))); + box-sizing: border-box; + overflow-wrap: anywhere; + padding: var(--spacing-large); + border-radius: var(--radius-tooltip); + color: var(--text-tooltip); + background: var(--surface-tooltip); + box-shadow: var(--shadow-overlay); + } + + .sg-tooltip-panel[hidden] { + display: none; + } + + .sg-tooltip-wrap[data-tooltip-placement="bottom"] .sg-tooltip-panel { + top: calc(100% + var(--spacing-small)); + left: 50%; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="top"] .sg-tooltip-panel { + bottom: calc(100% + var(--spacing-small)); + left: 50%; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="left"] .sg-tooltip-panel { + top: 50%; + right: calc(100% + var(--spacing-small)); + transform: translateY(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="right"] .sg-tooltip-panel { + top: 50%; + left: calc(100% + var(--spacing-small)); + transform: translateY(-50%); + } + + .sg-tooltip-panel::before { + position: absolute; + width: 0; + height: 0; + border-style: solid; + content: ""; + } + + .sg-tooltip-wrap[data-tooltip-placement="bottom"] .sg-tooltip-panel::before { + top: calc(var(--spacing-small) / -2); + left: 50%; + border-width: 0 calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2); + border-color: transparent transparent var(--surface-tooltip); + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="top"] .sg-tooltip-panel::before { + bottom: calc(var(--spacing-small) / -2); + left: 50%; + border-width: calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) 0; + border-color: var(--surface-tooltip) transparent transparent; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="left"] .sg-tooltip-panel::before { + top: 50%; + right: calc(var(--spacing-small) / -2); + border-width: calc(var(--spacing-small) / 2) 0 calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2); + border-color: transparent transparent transparent var(--surface-tooltip); + transform: translateY(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="right"] .sg-tooltip-panel::before { + top: 50%; + left: calc(var(--spacing-small) / -2); + border-width: calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) 0; + border-color: transparent var(--surface-tooltip) transparent transparent; + transform: translateY(-50%); + } + .sg-pulldown-panel { position: absolute; top: calc(100% + var(--spacing-small)); diff --git a/docs/styleguide/styles/27-patterns-delete-confirmation.css b/docs/styleguide/styles/27-patterns-delete-confirmation.css index ff32e6a..f44caac 100644 --- a/docs/styleguide/styles/27-patterns-delete-confirmation.css +++ b/docs/styleguide/styles/27-patterns-delete-confirmation.css @@ -63,7 +63,7 @@ display: none; } -.sg-card .sg-delete-confirmation-pattern__body { +.sg-delete-confirmation-pattern__body { display: flex; flex-direction: column; gap: var(--layout-delete-confirmation-content-gap); diff --git a/public/assets/styleguide.upstream.css b/public/assets/styleguide.upstream.css index c60b010..e457843 100644 --- a/public/assets/styleguide.upstream.css +++ b/public/assets/styleguide.upstream.css @@ -62,6 +62,7 @@ --surface-menu-panel-portal: var(--color-light-grey); --surface-help-icon: var(--color-medium-grey); --surface-help-panel: var(--color-light-grey); + --surface-tooltip: var(--color-light-grey); --surface-pulldown-panel: var(--color-light-grey); --surface-activatable-remove: var(--surface-control-default); --surface-slider-track: var(--color-medium-grey); @@ -89,6 +90,8 @@ --layout-mode-toggle-local-width-factor: 3; --layout-mode-toggle-width: calc(var(--interaction-height) * 3.5); --layout-help-panel-width: calc(var(--interaction-height) * 8); + --layout-tooltip-width: calc(var(--interaction-height) * 8); + --radius-tooltip: var(--radius-interaction); --text-control-default: var(--color-font-dark); --text-control-disabled: var(--color-dark-grey); --text-button-process: var(--color-font-light); @@ -98,6 +101,7 @@ --text-menu-link-portal: var(--color-font-dark); --text-help-icon: var(--color-font-dark); --text-help-panel: var(--color-font-dark); + --text-tooltip: var(--color-font-dark); --text-activatable-remove: var(--text-control-default); --text-hyperlink: var(--color-font-hyperlink); --icon-pulldown-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2l4 4 4-4' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E"); @@ -1760,6 +1764,95 @@ section + section { right: 0; } + .sg-tooltip-wrap { + position: relative; + display: inline-flex; + vertical-align: middle; + } + + .sg-tooltip-panel { + position: absolute; + z-index: var(--layer-pulldown-panel); + display: block; + width: var(--layout-tooltip-width); + max-width: calc(100vw - (2 * var(--spacing-large))); + box-sizing: border-box; + overflow-wrap: anywhere; + padding: var(--spacing-large); + border-radius: var(--radius-tooltip); + color: var(--text-tooltip); + background: var(--surface-tooltip); + box-shadow: var(--shadow-overlay); + } + + .sg-tooltip-panel[hidden] { + display: none; + } + + .sg-tooltip-wrap[data-tooltip-placement="bottom"] .sg-tooltip-panel { + top: calc(100% + var(--spacing-small)); + left: 50%; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="top"] .sg-tooltip-panel { + bottom: calc(100% + var(--spacing-small)); + left: 50%; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="left"] .sg-tooltip-panel { + top: 50%; + right: calc(100% + var(--spacing-small)); + transform: translateY(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="right"] .sg-tooltip-panel { + top: 50%; + left: calc(100% + var(--spacing-small)); + transform: translateY(-50%); + } + + .sg-tooltip-panel::before { + position: absolute; + width: 0; + height: 0; + border-style: solid; + content: ""; + } + + .sg-tooltip-wrap[data-tooltip-placement="bottom"] .sg-tooltip-panel::before { + top: calc(var(--spacing-small) / -2); + left: 50%; + border-width: 0 calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2); + border-color: transparent transparent var(--surface-tooltip); + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="top"] .sg-tooltip-panel::before { + bottom: calc(var(--spacing-small) / -2); + left: 50%; + border-width: calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) 0; + border-color: var(--surface-tooltip) transparent transparent; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="left"] .sg-tooltip-panel::before { + top: 50%; + right: calc(var(--spacing-small) / -2); + border-width: calc(var(--spacing-small) / 2) 0 calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2); + border-color: transparent transparent transparent var(--surface-tooltip); + transform: translateY(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="right"] .sg-tooltip-panel::before { + top: 50%; + left: calc(var(--spacing-small) / -2); + border-width: calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) 0; + border-color: transparent var(--surface-tooltip) transparent transparent; + transform: translateY(-50%); + } + .sg-pulldown-panel { position: absolute; top: calc(100% + var(--spacing-small)); @@ -3147,7 +3240,7 @@ section + section { display: none; } -.sg-card .sg-delete-confirmation-pattern__body { +.sg-delete-confirmation-pattern__body { display: flex; flex-direction: column; gap: var(--layout-delete-confirmation-content-gap); @@ -5217,3 +5310,4 @@ main { flex: 1 1 0; min-width: 0; } + diff --git a/public/assets/styleguide.upstream.meta.json b/public/assets/styleguide.upstream.meta.json index 482d51d..67c92d7 100644 --- a/public/assets/styleguide.upstream.meta.json +++ b/public/assets/styleguide.upstream.meta.json @@ -1,7 +1,7 @@ { "styleguideVersion": "2026.05.18.1", - "styleguideCommit": "71734d6", - "syncedAtUtc": "2026-09-08T09:49:27Z", + "styleguideCommit": "5b86185", + "syncedAtUtc": "2026-09-21T12:28:47Z", "sourceRepo": "/Users/mathias/Documents/Dokumente Chouchou/Codebases/Styleguide", "mirroredDocsPath": "docs/styleguide" } diff --git a/public/assets/styles.css b/public/assets/styles.css index c60b010..e457843 100644 --- a/public/assets/styles.css +++ b/public/assets/styles.css @@ -62,6 +62,7 @@ --surface-menu-panel-portal: var(--color-light-grey); --surface-help-icon: var(--color-medium-grey); --surface-help-panel: var(--color-light-grey); + --surface-tooltip: var(--color-light-grey); --surface-pulldown-panel: var(--color-light-grey); --surface-activatable-remove: var(--surface-control-default); --surface-slider-track: var(--color-medium-grey); @@ -89,6 +90,8 @@ --layout-mode-toggle-local-width-factor: 3; --layout-mode-toggle-width: calc(var(--interaction-height) * 3.5); --layout-help-panel-width: calc(var(--interaction-height) * 8); + --layout-tooltip-width: calc(var(--interaction-height) * 8); + --radius-tooltip: var(--radius-interaction); --text-control-default: var(--color-font-dark); --text-control-disabled: var(--color-dark-grey); --text-button-process: var(--color-font-light); @@ -98,6 +101,7 @@ --text-menu-link-portal: var(--color-font-dark); --text-help-icon: var(--color-font-dark); --text-help-panel: var(--color-font-dark); + --text-tooltip: var(--color-font-dark); --text-activatable-remove: var(--text-control-default); --text-hyperlink: var(--color-font-hyperlink); --icon-pulldown-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2l4 4 4-4' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E"); @@ -1760,6 +1764,95 @@ section + section { right: 0; } + .sg-tooltip-wrap { + position: relative; + display: inline-flex; + vertical-align: middle; + } + + .sg-tooltip-panel { + position: absolute; + z-index: var(--layer-pulldown-panel); + display: block; + width: var(--layout-tooltip-width); + max-width: calc(100vw - (2 * var(--spacing-large))); + box-sizing: border-box; + overflow-wrap: anywhere; + padding: var(--spacing-large); + border-radius: var(--radius-tooltip); + color: var(--text-tooltip); + background: var(--surface-tooltip); + box-shadow: var(--shadow-overlay); + } + + .sg-tooltip-panel[hidden] { + display: none; + } + + .sg-tooltip-wrap[data-tooltip-placement="bottom"] .sg-tooltip-panel { + top: calc(100% + var(--spacing-small)); + left: 50%; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="top"] .sg-tooltip-panel { + bottom: calc(100% + var(--spacing-small)); + left: 50%; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="left"] .sg-tooltip-panel { + top: 50%; + right: calc(100% + var(--spacing-small)); + transform: translateY(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="right"] .sg-tooltip-panel { + top: 50%; + left: calc(100% + var(--spacing-small)); + transform: translateY(-50%); + } + + .sg-tooltip-panel::before { + position: absolute; + width: 0; + height: 0; + border-style: solid; + content: ""; + } + + .sg-tooltip-wrap[data-tooltip-placement="bottom"] .sg-tooltip-panel::before { + top: calc(var(--spacing-small) / -2); + left: 50%; + border-width: 0 calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2); + border-color: transparent transparent var(--surface-tooltip); + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="top"] .sg-tooltip-panel::before { + bottom: calc(var(--spacing-small) / -2); + left: 50%; + border-width: calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) 0; + border-color: var(--surface-tooltip) transparent transparent; + transform: translateX(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="left"] .sg-tooltip-panel::before { + top: 50%; + right: calc(var(--spacing-small) / -2); + border-width: calc(var(--spacing-small) / 2) 0 calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2); + border-color: transparent transparent transparent var(--surface-tooltip); + transform: translateY(-50%); + } + + .sg-tooltip-wrap[data-tooltip-placement="right"] .sg-tooltip-panel::before { + top: 50%; + left: calc(var(--spacing-small) / -2); + border-width: calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) calc(var(--spacing-small) / 2) 0; + border-color: transparent var(--surface-tooltip) transparent transparent; + transform: translateY(-50%); + } + .sg-pulldown-panel { position: absolute; top: calc(100% + var(--spacing-small)); @@ -3147,7 +3240,7 @@ section + section { display: none; } -.sg-card .sg-delete-confirmation-pattern__body { +.sg-delete-confirmation-pattern__body { display: flex; flex-direction: column; gap: var(--layout-delete-confirmation-content-gap); @@ -5217,3 +5310,4 @@ main { flex: 1 1 0; min-width: 0; } +