Skip to content

Commit

Permalink
Feat(web-twig): Introduce spacing style props to all components #DS-1109
Browse files Browse the repository at this point in the history
  • Loading branch information
crishpeen committed Jan 23, 2024
1 parent b6b8721 commit 78a697e
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 2 deletions.
29 changes: 28 additions & 1 deletion packages/web-twig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,34 @@ considered carefully, and extensively tested. In general, customizing Spirit des
do offer control over layout and other aspects. In addition, you can use Spirit defined design tokens to ensure your
application conforms to your design requirements, and is adaptive across platform scales and color schemes.

### Escape hatches
### Style Props

All Spirit components accept a set of props that can be used to control their outer spacing. The props are:

- `margin`
- `marginTop`
- `marginRight`
- `marginBottom`
- `marginLeft`
- `marginX`
- `marginY`

These props accept a spacing token (eg. `space-100`), `auto` or an object with breakpoint keys and spacing token
values or `auto`. We use these props to set global CSS utility classes on the root element of the component.

Examples:

```twig
<Alert marginBottom="space-100" />
<Button marginX="{{ { mobile: 'space-100', tablet: 'space-200' } }}" />
<Button marginLeft="{{ { mobile: 'space-100', tablet: 'space-200', desktop: 'auto' } }}" />
```

If you need more control over the styling of a component, you can use [escape hatches](#escape-hatches).

### Escape Hatches

While we encourage teams to utilize Spirit design as it is, we do realize that sometimes product specific customizations
may be needed. In these cases, we encourage you or your designers to **talk to us**. We may be able to suggest
Expand Down
62 changes: 62 additions & 0 deletions packages/web-twig/src/Twig/PropsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,32 @@

namespace Lmc\SpiritWebTwigBundle\Twig;

use Lmc\SpiritWebTwigBundle\DependencyInjection\SpiritWebTwigExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class PropsExtension extends AbstractExtension
{
private const BREAKPOINT_MOBILE = 'mobile';

private const CLASS_SEPARATOR = '-';

private const CLASS_NAMES_SEPARATOR = ' ';

private const STYLE_SPACING_AUTO = 'auto';

public const STYLE_SPACING_PROPS = [
'margin' => 'm',
'marginBottom' => 'mb',
'marginLeft' => 'ml',
'marginRight' => 'mr',
'marginTop' => 'mt',
'marginX' => 'mx',
'marginY' => 'my',
];

public const VALIDATION_ATTRIBUTES = [
'min', 'max', 'minlength', 'maxlength', 'pattern',
];
Expand Down Expand Up @@ -112,6 +132,26 @@ public function renderStyleProp(Environment $environment, array $props): string
]);
}

/**
* Converts a mixed value to a string.
*
* @return string value as a string.
*/
private function getSpacingClassName(string $propName, string $propValue, ?string $breakpoint = null): string
{
$container = new ContainerBuilder();
$classPrefix = $container->hasParameter(SpiritWebTwigExtension::PARAMETER_SPIRIT_CSS_CLASS_PREFIX) && is_string($container->getParameter(SpiritWebTwigExtension::PARAMETER_SPIRIT_CSS_CLASS_PREFIX))
? $container->getParameter(SpiritWebTwigExtension::PARAMETER_SPIRIT_CSS_CLASS_PREFIX)
: '';
$utilityName = self::STYLE_SPACING_PROPS[$propName];

// Return just a number from the value if not `auto`
$utilityValue = $propValue == self::STYLE_SPACING_AUTO ? self::STYLE_SPACING_AUTO : preg_replace('/[^0-9]/', '', $propValue);
$infix = $breakpoint !== null && $breakpoint !== self::BREAKPOINT_MOBILE ? self::CLASS_SEPARATOR . $breakpoint : '';

return $classPrefix . $utilityName . $infix . self::CLASS_SEPARATOR . $utilityValue;
}

/**
* @param array<string, mixed> $props
* @return array<string, mixed>
Expand All @@ -128,6 +168,28 @@ public function useStyleProps(array $props): array
$styleProps['className'] = $props['UNSAFE_className'] ?? null;
$styleProps['style'] = $props['UNSAFE_style'] ?? null;

foreach ($props as $propName => $propValue) {
if (array_key_exists($propName, self::STYLE_SPACING_PROPS)) {
$spacingClasses = [];

if (is_array($propValue)) {
foreach ($propValue as $breakpoint => $value) {
if (is_scalar($value)) {
$spacingClasses[] = $this->getSpacingClassName($propName, (string) $value, $breakpoint);
}
}
} elseif (is_scalar($propValue)) {
$spacingClasses[] = $this->getSpacingClassName($propName, (string) $propValue);
}

if (count($spacingClasses)) {
$styleProps['className'] .= self::CLASS_NAMES_SEPARATOR . implode(self::CLASS_NAMES_SEPARATOR, $spacingClasses);
}
}
}

$styleProps['className'] = is_string($styleProps['className']) ? trim($styleProps['className']) : null;

return $styleProps;
}
}
55 changes: 54 additions & 1 deletion packages/web-twig/tests/Twig/PropsExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Lmc\SpiritWebTwigBundle\Twig;

use Lmc\SpiritWebTwigBundle\DependencyInjection\SpiritWebTwigExtension;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Twig\Environment;

class PropsExtensionTest extends TestCase
Expand Down Expand Up @@ -325,7 +327,7 @@ public function testShouldUseStyleProp(array $props, array $expectedResponse): v
}

/**
* @return array<string, array<int, array<string, string|null>>>
* @return array<string, array<int, array<int|string, array<string, string>|string|null>>>
*/
public function useStylePropDataProvider(): array
{
Expand Down Expand Up @@ -360,6 +362,57 @@ public function useStylePropDataProvider(): array
'className' => null,
'style' => 'position: absolute;',
]],
'simple spacing style prop' => [[
'margin' => 'space-100',
], [
'className' => 'm-100',
'style' => null,
]],
'complex spacing style prop' => [[
'marginX' => [
'mobile' => 'space-100',
'tablet' => 'auto',
'desktop' => 'space-200',
],
], [
'className' => 'mx-100 mx-tablet-auto mx-desktop-200',
'style' => null,
]],
'skipping breakpoint with spacing style prop' => [[
'marginX' => [
'mobile' => 'space-100',
'desktop' => 'auto',
],
], [
'className' => 'mx-100 mx-desktop-auto',
'style' => null,
]],
'all spacing style props' => [[
'margin' => 'space-100',
'marginTop' => 'space-200',
'marginRight' => 'space-300',
'marginBottom' => 'space-400',
'marginLeft' => 'space-500',
'marginX' => 'space-600',
'marginY' => 'space-700',
], [
'className' => 'm-100 mt-200 mr-300 mb-400 ml-500 mx-600 my-700',
'style' => null,
]],
'both spacing and UNSAFE_style' => [[
'margin' => 'space-100',
'UNSAFE_style' => 'position: absolute;',
], [
'className' => 'm-100',
'style' => 'position: absolute;',
]],
'both spacing and UNSAFE_className' => [[
'margin' => 'space-100',
'UNSAFE_className' => 'm-500',
], [
'className' => 'm-500 m-100',
'style' => null,
]],
];
}
}

0 comments on commit 78a697e

Please sign in to comment.