diff --git a/packages/web/src/scss/components/Tooltip/_TooltipPopover.scss b/packages/web/src/scss/components/Tooltip/_TooltipPopover.scss index 9ea1329fba..eb7085cc0b 100644 --- a/packages/web/src/scss/components/Tooltip/_TooltipPopover.scss +++ b/packages/web/src/scss/components/Tooltip/_TooltipPopover.scss @@ -1,5 +1,4 @@ -// 1. Although it may seem pointless, having the arrow as a standalone HTML element has a purpose: the arrow can be -// precisely positioned in sticky tooltips, e.g. with Floating UI: +// 1. The arrow must be a standalone HTML element so the Floating UI library can position it correctly. // https://floating-ui.com // // 2. Reuse already generated custom properties to allow overriding the arrow appearance live in the browser. @@ -15,6 +14,7 @@ .TooltipPopover { @include placement.child(); + @include placement.child-controlled($prefix: 'tooltip', $offset: theme.$arrow-height); @include typography.generate(theme.$typography); --tooltip-max-width: #{theme.$max-width}; @@ -72,6 +72,7 @@ $class-name: 'TooltipPopover', $dictionary-values: theme.$placement-dictionary, $offset: theme.$arrow-height, + $is-controlled: true, $has-arrow: true ); @@ -83,14 +84,3 @@ .TooltipPopover.is-visible { @extend %visible; } - -// Controlled placement -// stylelint-disable-next-line selector-max-class -.TooltipPopover.TooltipPopover { - @include placement.child-controlled($prefix: 'tooltip', $offset: theme.$arrow-height); -} - -// stylelint-disable-next-line selector-max-class -.TooltipPopover.TooltipPopover .TooltipPopover__arrow { - @include placement.arrow-controlled(); -} diff --git a/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss b/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss index 6d547d2273..0cfa9cbc6d 100644 --- a/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss +++ b/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss @@ -125,7 +125,12 @@ @include test.it('should generate correct placement classes based on a dictionary') { @include test.assert() { @include test.output() { - @include dictionaries.generate-placements('Test', ('top-start')); + @include dictionaries.generate-placements( + $class-name: 'Test', + $dictionary-values: ( + 'top-start', + ) + ); } @include test.expect() { @@ -141,7 +146,12 @@ @include test.assert() { @include test.output() { - @include dictionaries.generate-placements('Test', ('right-end')); + @include dictionaries.generate-placements( + $class-name: 'Test', + $dictionary-values: ( + 'right-end', + ) + ); } @include test.expect() { @@ -154,6 +164,56 @@ } } } + + @include test.assert() { + @include test.output() { + @include dictionaries.generate-placements( + $class-name: 'Test', + $dictionary-values: ( + 'top-start', + ), + $offset: 10px, + $is-controlled: true + ); + } + + @include test.expect() { + .Test[data-spirit-placement='top-start'] { + --test-offset: 10px; + --test-offset-orthogonal: 0; + + transform-origin: bottom left; + } + } + } + + @include test.assert() { + @include test.output() { + @include dictionaries.generate-placements( + $class-name: 'Test', + $dictionary-values: ( + 'top-start', + ), + $offset: 10px, + $is-controlled: true, + $has-arrow: true + ); + } + + @include test.expect() { + .Test[data-spirit-placement='top-start'] { + --test-offset: 10px; + --test-offset-orthogonal: 0; + + transform-origin: bottom left; + } + + .Test[data-spirit-placement='top-start'] > .Test__arrow { + transform-origin: center; + rotate: z 180deg; + } + } + } } } diff --git a/packages/web/src/scss/tools/__tests__/_placement.test.scss b/packages/web/src/scss/tools/__tests__/_placement.test.scss index b8cd493613..64b6294790 100644 --- a/packages/web/src/scss/tools/__tests__/_placement.test.scss +++ b/packages/web/src/scss/tools/__tests__/_placement.test.scss @@ -162,7 +162,7 @@ @include test.assert() { @include test.output() { .child-test { - @include placement.child(3); + @include placement.child($z-index: 3); } } @include test.expect() { @@ -174,11 +174,30 @@ } } + @include test.it('should apply controlled styles to child') { + @include test.assert() { + @include test.output() { + .child-controlled-test { + @include placement.child-controlled('test', 10px); + } + } + @include test.expect() { + .child-controlled-test { + --test-offset-orthogonal: 0; + --test-offset: 10px; + + inset: unset; + translate: unset; + } + } + } + } + @include test.it('should apply custom styles to child variant') { @include test.assert() { @include test.output() { .child-variant-test { - @include placement.child-variant('test', 'top', 10px); + @include placement.child-variant($prefix: 'test', $placement: 'top', $offset: 10px); } } @include test.expect() { @@ -191,22 +210,24 @@ } } } - } - @include test.it('should apply controlled styles to child') { @include test.assert() { @include test.output() { - .child-controlled-test { - @include placement.child-controlled('test', 10px); + .child-variant-test { + @include placement.child-variant( + $prefix: 'test', + $placement: 'top', + $offset: 10px, + $is-controlled: true + ); } } @include test.expect() { - .child-controlled-test { - --test-offset-orthogonal: 0; + .child-variant-test { --test-offset: 10px; + --test-offset-orthogonal: 0; - inset: unset; - translate: unset; + transform-origin: bottom; } } } @@ -248,22 +269,4 @@ } } } - - @include test.it('should apply controlled styles to arrow') { - @include test.assert() { - @include test.output() { - .arrow-controlled-test { - @include placement.arrow-controlled(); - } - } - - @include test.expect() { - .arrow-controlled-test { - inset: unset; - translate: unset; - transform-origin: center; - } - } - } - } } diff --git a/packages/web/src/scss/tools/_dictionaries.scss b/packages/web/src/scss/tools/_dictionaries.scss index b8b9435d8a..b782e612c9 100644 --- a/packages/web/src/scss/tools/_dictionaries.scss +++ b/packages/web/src/scss/tools/_dictionaries.scss @@ -184,17 +184,21 @@ // * $dictionary-values: map of the placements to generate // * $offset: offset of child from its parent, typically for an arrow // * $has-arrow: whether the component has an arrow -@mixin generate-placements($class-name, $dictionary-values, $offset: 0, $has-arrow: false) { +@mixin generate-placements($class-name, $dictionary-values, $offset: 0, $is-controlled: false, $has-arrow: false) { $prefix: spirit-string.convert-pascal-case-to-kebab-case($class-name); @each $placement in $dictionary-values { .#{$class-name}[data-spirit-placement='#{$placement}'] { - @include placement.child-variant($prefix, $placement, $offset); + @include placement.child-variant($prefix, $placement, $offset, $is-controlled); } @if $has-arrow { .#{$class-name}[data-spirit-placement='#{$placement}'] > .#{$class-name}__arrow { - @include placement.arrow-variant($prefix, placement.transform($placement, $main-axis-inverse: true)); + @include placement.arrow-variant( + $prefix, + placement.transform($placement, $main-axis-inverse: true), + $is-controlled + ); } } } diff --git a/packages/web/src/scss/tools/_placement.scss b/packages/web/src/scss/tools/_placement.scss index 7d95bb393b..e2d4e51805 100644 --- a/packages/web/src/scss/tools/_placement.scss +++ b/packages/web/src/scss/tools/_placement.scss @@ -207,16 +207,6 @@ $_logical-to-physical-placement-map: ( z-index: $z-index; } -@mixin child-variant($prefix, $placement, $offset: 0) { - --#{$prefix}-offset: #{$offset}; // 1.3.a - - inset: map.get($_inset-map, $placement); // 1.2 - translate: map.get(-get-child-translate-map($prefix), $placement); // 1.3 - transform-origin: string.unquote( - transform($placement, $main-axis-inverse: true, $cross-axis-physical: true, $join-with: ' ') - ); -} - @mixin child-controlled($prefix, $offset) { --#{$prefix}-offset-orthogonal: 0; // 2. --#{$prefix}-offset: #{$offset}; // 2. @@ -225,6 +215,21 @@ $_logical-to-physical-placement-map: ( translate: unset; // 2. } +@mixin child-variant($prefix, $placement, $offset: 0, $is-controlled: false) { + --#{$prefix}-offset: #{$offset}; // 1.3.a, 2. + + @if $is-controlled { + --#{$prefix}-offset-orthogonal: 0; // 2. + } @else { + inset: map.get($_inset-map, $placement); // 1.2 + translate: map.get(-get-child-translate-map($prefix), $placement); // 1.3 + } + + transform-origin: string.unquote( + transform($placement, $main-axis-inverse: true, $cross-axis-physical: true, $join-with: ' ') + ); +} + @mixin arrow($prefix, $width, $height, $corner-offset) { --#{$prefix}-arrow-width: #{$width}; --#{$prefix}-arrow-height: #{$height}; @@ -234,14 +239,13 @@ $_logical-to-physical-placement-map: ( transform-origin: bottom center; } -@mixin arrow-variant($prefix, $placement) { - inset: map.get($_inset-map, $placement); // 1.2 - translate: map.get(-get-arrow-translate-map($prefix), $placement); // 1.3 - rotate: z map.get($_arrow-rotate-map, $placement); -} +@mixin arrow-variant($prefix, $placement, $is-controlled: false) { + @if $is-controlled { + transform-origin: center; // 2. + } @else { + inset: map.get($_inset-map, $placement); // 1.2 + translate: map.get(-get-arrow-translate-map($prefix), $placement); // 1.3 + } -@mixin arrow-controlled() { - inset: unset; // 2. - translate: unset; // 2. - transform-origin: center; // 2. + rotate: z map.get($_arrow-rotate-map, $placement); }