From 27cbb6700f3cf612989eaa002a8d750bf71f021a Mon Sep 17 00:00:00 2001 From: miqayelsrapionyan Date: Mon, 25 Nov 2024 18:30:50 +0400 Subject: [PATCH] Breakpoint media query --- src/CssGenerator.php | 14 ++- .../BreakpointMediaQueryDecorator.php | 118 ++++++++++++++++++ src/Decorators/StylesheetMediaQuery.php | 36 ++++++ src/Strategies/BackgroundStrategy.php | 1 - src/StyleCollector/StyleCollector.php | 47 +++++++ tests/Unit/CssGeneratorTest.php | 78 ++++++++++++ .../BreakpointMediaQueryDecoratorTest.php | 54 ++++++++ tests/Unit/Decorators/StylesheetTest.php | 37 ++++++ 8 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 src/Decorators/BreakpointMediaQueryDecorator.php create mode 100644 src/Decorators/StylesheetMediaQuery.php create mode 100644 tests/Unit/Decorators/BreakpointMediaQueryDecoratorTest.php diff --git a/src/CssGenerator.php b/src/CssGenerator.php index 1ba9c06..f1c0241 100644 --- a/src/CssGenerator.php +++ b/src/CssGenerator.php @@ -6,6 +6,8 @@ use CssGenerator\StyleCollector\StyleCollectorContract; +use function join; + /** * CssGenerator converts variantsStyles array into css string. */ @@ -17,7 +19,7 @@ public function __construct(protected StyleCollectorContract $styleCollector) } /** - * Convert variantsStyles to css string. + * Convert variantsStyles to array of css string key by breakpoint id. * * @return array */ @@ -25,4 +27,14 @@ public function generate(): array { return $this->styleCollector->getStylesheet()->generate(); } + + /** + * Convert variantsStyles to css string. + * + * @return string + */ + public function generateStylesheet(): string + { + return join($this->styleCollector->getStylesheet()->generate()); + } } diff --git a/src/Decorators/BreakpointMediaQueryDecorator.php b/src/Decorators/BreakpointMediaQueryDecorator.php new file mode 100644 index 0000000..9b38287 --- /dev/null +++ b/src/Decorators/BreakpointMediaQueryDecorator.php @@ -0,0 +1,118 @@ +id = $id; + } + + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * @param string $mediaQuery + * + * @return void + */ + public function setMediaQuery(string $mediaQuery): void + { + $this->mediaQuery = $mediaQuery; + } + + /** + * @return string + */ + public function getMediaQuery(): string + { + return $this->mediaQuery; + } + + /** + * @param bool $isDefault + * + * @return void + */ + public function setIsDefault(bool $isDefault): void + { + $this->isDefault = $isDefault; + } + + /** + * @return bool + */ + public function isDefault(): bool + { + return $this->isDefault; + } + + /** + * @param string $widgetId + * @param \CssGenerator\Decorators\StyleDecorator $style + * + * @return void + */ + public function addStyle(string $widgetId, StyleDecorator $style): void + { + $this->styles[$widgetId][] = $style; + } + + /** + * Bring together already generated css blocks. + * + * @return string + */ + public function __toString(): string + { + $css = ''; + if (!$this->isDefault) { + $css .= $this->getMediaQuery(); + } + + foreach ($this->styles as $styles) { + $css .= implode('', $styles); + } + + if (!$this->isDefault) { + $css .= '}'; + } + + return $css; + } +} diff --git a/src/Decorators/StylesheetMediaQuery.php b/src/Decorators/StylesheetMediaQuery.php new file mode 100644 index 0000000..15cd078 --- /dev/null +++ b/src/Decorators/StylesheetMediaQuery.php @@ -0,0 +1,36 @@ + + */ + protected array $breakpoints = []; + + /** + * @param array $breakpoints + * + * @return void + */ + public function setBreakpoints(array $breakpoints): void + { + $this->breakpoints = $breakpoints; + } + + /** + * @return array + */ + public function generate(): array + { + return ['default breakpoint id' => implode('', $this->breakpoints)]; + } +} diff --git a/src/Strategies/BackgroundStrategy.php b/src/Strategies/BackgroundStrategy.php index b09590d..419b6ba 100755 --- a/src/Strategies/BackgroundStrategy.php +++ b/src/Strategies/BackgroundStrategy.php @@ -5,7 +5,6 @@ namespace CssGenerator\Strategies; use function join; -use function is_string; class BackgroundStrategy implements StrategyInterfaceWithMediaMapping { diff --git a/src/StyleCollector/StyleCollector.php b/src/StyleCollector/StyleCollector.php index 1eff683..0a82484 100755 --- a/src/StyleCollector/StyleCollector.php +++ b/src/StyleCollector/StyleCollector.php @@ -5,6 +5,7 @@ namespace CssGenerator\StyleCollector; use CssGenerator\Decorators\BreakpointDecorator; +use CssGenerator\Decorators\BreakpointMediaQueryDecorator; use CssGenerator\Decorators\StaticStyleDecorator; use CssGenerator\Decorators\StaticStylesheet; use CssGenerator\Decorators\StyleDecorator; @@ -12,6 +13,7 @@ use CssGenerator\Decorators\Stylesheet; use function in_array; +use function array_unshift; /** * StyleCollector collects all necessary data for generating css. @@ -68,6 +70,51 @@ public function assignBreakpoints(array $breakpoints): StyleCollector return $this; } + /** + * @param array $breakpoints + * + * @return $this + */ + public function assignMediaQueryBreakpoints(array $breakpoints): StyleCollector + { + $defaultBreakpointWidth = 0; + $sortedBreakpoints = []; + + // Sort breakpoints, if breakpoint is less than default, must be reverse sorted + // example: [320, 769, 1281, 1441, 1921] -> [1281, 769, 320, 1441, 1921] (1281 is default) + foreach ($breakpoints as $breakpointIndex => $breakpoint) { + $newBreakpoint = new BreakpointMediaQueryDecorator(); + $newBreakpoint->setIsDefault($breakpoint['default']); + $newBreakpoint->setId($breakpoint['id']); + + if (!$newBreakpoint->isDefault()) { + if ($defaultBreakpointWidth === 0 || $defaultBreakpointWidth > $breakpoint['width']) { + $width = $breakpoint['width']; + + if (isset($breakpoints[$breakpointIndex + 1])) { + $width = $breakpoints[$breakpointIndex + 1]['width'] - 1; + } + $newBreakpoint->setMediaQuery("@media (max-width: {$width}px) {"); + array_unshift($sortedBreakpoints, $newBreakpoint); + } else { + $newBreakpoint->setMediaQuery("@media (min-width: {$breakpoint['width']}px) {"); + $sortedBreakpoints[] = $newBreakpoint; + } + } + + if ($breakpoint['default']) { + $defaultBreakpointWidth = $breakpoint['width']; + array_unshift($sortedBreakpoints, $newBreakpoint); + } + } + + foreach ($sortedBreakpoints as $sortedBreakpoint) { + $this->data['breakpoints'][$sortedBreakpoint->getId()] = $sortedBreakpoint; + } + + return $this; + } + /** * @return \CssGenerator\Decorators\StyleInterface */ diff --git a/tests/Unit/CssGeneratorTest.php b/tests/Unit/CssGeneratorTest.php index fcbf136..162e001 100644 --- a/tests/Unit/CssGeneratorTest.php +++ b/tests/Unit/CssGeneratorTest.php @@ -248,6 +248,84 @@ public function testGenerate_WhenGivenWithBreakpoints_GeneratesBasedOnBreakpoint $this->assertEquals($expectedBreakpoint3, $css[3]); } + public function testGenerate_WhenGivenWithMediaQueryBreakpoints_GeneratesBasedOnBreakpoints(): void + { + $breakpoints = [ + [ + "id" => 1, + "width" => 320, + "default" => false, + "selected" => false, + "createdAt" => "2022-05-19T11:57:40.000000Z", + ], + [ + "id" => 2, + "width" => 769, + "default" => false, + "selected" => false, + "createdAt" => "2022-05-19T11:57:40.000000Z", + ], + [ + "id" => 3, + "width" => 1281, + "default" => true, + "selected" => true, + "createdAt" => "2022-05-19T11:57:40.000000Z", + ], + [ + "id" => 4, + "width" => 1441, + "default" => false, + "selected" => false, + "createdAt" => "2022-05-19T11:57:40.000000Z", + ], + [ + "id" => 5, + "width" => 1921, + "default" => false, + "selected" => false, + "createdAt" => "2022-05-19T11:57:40.000000Z", + ], + ]; + + $variantsStyles = [ + '[data-widget-hash="random-hash"]' => [ + [ + 'styles' => [ + [ + "type" => "font-family", + "value" => "Helvetica" + ] + ], + 'cssState' => 'normal', + 'breakpointId' => 3 + ], + [ + 'styles' => [ + [ + "type" => "color", + "value" => "rgb(0, 0, 0)" + ] + ], + 'cssState' => 'hover', + 'breakpointId' => 1 + ] + ] + ]; + + $styleCollector = $this->getStyleCollectorInstance(); + $styleCollector + ->assignMediaQueryBreakpoints($breakpoints) + ->assignVariantsStyles($variantsStyles) + ->build(); + + $generator = new CssGenerator($styleCollector); + $css = $generator->generateStylesheet(); + + $expected = '[data-widget-hash="random-hash"] {font-family: Helvetica;}@media (max-width: 1280px) {}@media (max-width: 768px) {[data-widget-hash="random-hash"]:hover {color: rgb(0, 0, 0);}}@media (min-width: 1441px) {}@media (min-width: 1921px) {}'; + $this->assertEquals($expected, $css); + } + public function testGenerateStyles_WithSpecifiedBreakpointId(): void { $staticGlobalStyles = [ diff --git a/tests/Unit/Decorators/BreakpointMediaQueryDecoratorTest.php b/tests/Unit/Decorators/BreakpointMediaQueryDecoratorTest.php new file mode 100644 index 0000000..1a9a512 --- /dev/null +++ b/tests/Unit/Decorators/BreakpointMediaQueryDecoratorTest.php @@ -0,0 +1,54 @@ +setSelector('[data-widget-hash="random-hash"]'); + $styleDecorator->setStyles([ + [ + 'type' => 'font-size', + 'value' => '10px' + ] + ]); + + $breakpointDecorator = new BreakpointMediaQueryDecorator(); + $breakpointDecorator->setIsDefault(false); + $breakpointDecorator->setId(1); + $breakpointDecorator->setMediaQuery('@media (max-width: 768px) {'); + $breakpointDecorator->addStyle('[data-widget-hash="random-hash"]', $styleDecorator); + + $expected = '@media (max-width: 768px) {[data-widget-hash="random-hash"] {font-size: 10px;}}'; + $this->assertEquals($expected, (string)$breakpointDecorator); + } + + public function testToString_WhenBreakpointIsDefault_ReturnsCssBlocksNotWrapped(): void + { + $styleDecorator = new StyleDecorator(); + $styleDecorator->setSelector('[data-widget-hash="random-hash"]'); + $styleDecorator->setStyles([ + [ + 'type' => 'font-size', + 'value' => '10px' + ] + ]); + + $breakpointDecorator = new BreakpointMediaQueryDecorator(); + $breakpointDecorator->setIsDefault(true); + $breakpointDecorator->setId(1); + $breakpointDecorator->setMediaQuery('@media (max-width: 768px) {'); // ignores this line + $breakpointDecorator->addStyle('[data-widget-hash="random-hash"]', $styleDecorator); + + $expected = '[data-widget-hash="random-hash"] {font-size: 10px;}'; + $this->assertEquals($expected, (string)$breakpointDecorator); + } +} diff --git a/tests/Unit/Decorators/StylesheetTest.php b/tests/Unit/Decorators/StylesheetTest.php index 26d8452..ba04a03 100644 --- a/tests/Unit/Decorators/StylesheetTest.php +++ b/tests/Unit/Decorators/StylesheetTest.php @@ -5,6 +5,7 @@ namespace CssGenerator\Tests\Unit\Decorators; use CssGenerator\Decorators\BreakpointDecorator; +use CssGenerator\Decorators\BreakpointMediaQueryDecorator; use CssGenerator\Decorators\StyleDecorator; use CssGenerator\Decorators\Stylesheet; use PHPUnit\Framework\TestCase; @@ -37,4 +38,40 @@ public function testToString_WhenGivenBreakpoints_ReturnsCssBlockWrappedByBreakp 2 => '[data-widget-hash="random-hash"] {font-size: 10px;}' ], $stylesheet->generate()); } + + public function testToString_WhenGivenMediaBreakpoints_ReturnsCssBlockWrappedByBreakpoints(): void + { + $styleDecorator = new StyleDecorator(); + $styleDecorator->setSelector('[data-widget-hash="random-hash"]'); + $styleDecorator->setStyles([ + [ + 'type' => 'font-size', + 'value' => '10px' + ] + ]); + + $breakpointDecorator = new BreakpointMediaQueryDecorator(); + $breakpointDecorator->setIsDefault(false); + $breakpointDecorator->setId(1); + $breakpointDecorator->setMediaQuery('@media (max-width: 768px) {'); + $breakpointDecorator->addStyle('[data-widget-hash="random-hash"]', $styleDecorator); + + $breakpointDecorator2 = new BreakpointMediaQueryDecorator(); + $breakpointDecorator2->setIsDefault(false); + $breakpointDecorator2->setId(2); + $breakpointDecorator2->setMediaQuery('@media (max-width: 1280) {'); + $breakpointDecorator2->addStyle('[data-widget-hash="random-hash"]', $styleDecorator); + + $breakpointDecorator2 = new BreakpointMediaQueryDecorator(); + $breakpointDecorator2->setIsDefault(true); + $breakpointDecorator2->setId(3); + $breakpointDecorator2->setMediaQuery('@media (max-width: 1920) {'); + $breakpointDecorator2->addStyle('[data-widget-hash="random-hash"]', $styleDecorator); + + $stylesheet = new Stylesheet(); + $stylesheet->setBreakpoints([$breakpointDecorator, $breakpointDecorator2]); + + $expected = '@media (max-width: 768px) {[data-widget-hash="random-hash"] {font-size: 10px;}}[data-widget-hash="random-hash"] {font-size: 10px;}'; + $this->assertEquals($expected, join($stylesheet->generate())); + } }