Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

9.2.0 #432

Merged
merged 4 commits into from
Oct 1, 2024
Merged

9.2.0 #432

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file.

This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/).

## [9.2.0]

### Added
- Introduced new, more flexible, and simpler to use `tailwindClasses` function. Replaces `getTwPart`, `getTwDynamicPart`, and `getTwClasses`.
- **Potentially breaking**: `twClassesEditor` is now appended to `twClasses`. If you need editor-only classes, you can now use the `twClassesEditorOnly` key. Editor-only classes replace `twClasses`, but will also have classes from `twClassesEditor`.
- **Potentially breaking**: `parts` key in manifest now supports specifying multiple parts just with a comma-separated string.
- You can now apply classes to multiple parts within one option or combination! Also work with responsive options.
- There are now (basic) warnings for misconfigurations of parts and options.

## [9.1.6]

### Fixed
Expand Down Expand Up @@ -663,6 +672,7 @@ Init setup

[Unreleased]: https://github.com/infinum/eightshift-libs/compare/main...HEAD

[9.2.0]: https://github.com/infinum/eightshift-libs/compare/9.1.6...9.2.0
[9.1.6]: https://github.com/infinum/eightshift-libs/compare/9.1.5...9.1.6
[9.1.5]: https://github.com/infinum/eightshift-libs/compare/9.1.4...9.1.5
[9.1.4]: https://github.com/infinum/eightshift-libs/compare/9.1.3...9.1.4
Expand Down
2 changes: 1 addition & 1 deletion src/Cli/CliHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ protected function getShortenCliPathOutput(string $path, string $ref = 'projectR
protected function getFolderItems(string $path): array
{
$output = \array_diff(\scandir($path), ['..', '.']);
$output = \array_values($output);
$output = \array_values($output); // @phpstan-ignore-line

return $output;
}
Expand Down
212 changes: 212 additions & 0 deletions src/Helpers/TailwindTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

namespace EightshiftLibs\Helpers;

use Exception;
use JsonException;

/**
* Class TailwindTrait Helper.
*/
Expand Down Expand Up @@ -48,6 +51,8 @@ public static function getTwBreakpoints($desktopFirst = false)
* @param array<mixed> $manifest Component/block manifest data.
* @param array<string> ...$custom Additional custom classes.
*
* @deprecated 9.2.0 Use `tailwindClasses` instead.
*
* @return string
*/
public static function getTwPart($part, $manifest, ...$custom)
Expand Down Expand Up @@ -75,6 +80,8 @@ public static function getTwPart($part, $manifest, ...$custom)
* @param array<mixed> $manifest Component/block manifest data.
* @param array<string> ...$custom Additional custom classes.
*
* @deprecated 9.2.0 Use `tailwindClasses` instead.
*
* @return string
*/
public static function getTwDynamicPart($part, $attributes, $manifest, ...$custom)
Expand Down Expand Up @@ -170,6 +177,8 @@ public static function getTwDynamicPart($part, $attributes, $manifest, ...$custo
* @param array<mixed> $manifest Component/block manifest data.
* @param array<string> ...$custom Additional custom classes.
*
* @deprecated 9.2.0 Use `tailwindClasses` instead.
*
* @return string
*/
public static function getTwClasses($attributes, $manifest, ...$custom)
Expand Down Expand Up @@ -289,4 +298,207 @@ public static function getTwClasses($attributes, $manifest, ...$custom)

return Helpers::classnames([$baseClasses, ...$mainClasses, ...$combinationClasses, ...$custom]);
}

/**
* Unifies the given input classes into a single string.
*
* Takes an array or string of CSS classes and unifies them into a single string,
* ensuring that there are no duplicate classes and that the classes are properly formatted.
*
* @param mixed $input The input classes to be unified. This can be a string or an array of strings.
*
* @return string The unified string of CSS classes.
*/
private static function unifyClasses($input): string
{
if (\is_array($input)) {
return Helpers::classnames($input);
}

return \trim($input);
}

/**
* Processes the given option for a specific part name.
*
* This method processes the option value for a given part name based on the provided definitions.
* It ensures that the option value is correctly handled according to the definitions.
*
* @param string $partName The name of the part for which the option is being processed.
* @param mixed $optionValue The value of the option to be processed.
* @param array<mixed> $defs The definitions that dictate how the option should be processed.
*
* @return string The processed option value.
*/
private static function processOption($partName, $optionValue, $defs): string
{
$optionClasses = [];

$isResponsive = $defs['responsive'] ?? false;
$itemPartName = isset($defs['part']) ? $defs['part'] : 'base';
$isSingleValue = isset($defs['twClasses']) || isset($defs['twClassesEditor']);

// Part checks.
if (!$isSingleValue && !isset($defs[$partName])) {
return '';
}

if ($isSingleValue && !\str_contains($itemPartName, $partName)) {
return '';
}

// Non-responsive options.
if (!$isResponsive) {
$rawValue = $defs['twClasses'][$optionValue] ?? $defs[$partName]['twClasses'][$optionValue] ?? '';

return self::unifyClasses($rawValue);
}

// Responsive options.
$breakpoints = \array_keys($optionValue);

if (\in_array('_desktopFirst', $breakpoints, true)) {
$breakpoints = \array_filter($breakpoints, fn($breakpoint) => $breakpoint !== '_desktopFirst');
}

foreach ($breakpoints as $breakpoint) {
$breakpointValue = $optionValue[$breakpoint];

if (!$breakpointValue) {
continue;
}

$rawValue = $defs['twClasses'][$breakpointValue] ?? $defs[$partName]['twClasses'][$breakpointValue] ?? '';
$rawClasses = self::unifyClasses($rawValue);

if ($breakpoint === '_default') {
$optionClasses[] = $rawClasses;

continue;
}

$splitClasses = \explode(' ', $rawClasses);
$splitClasses = \array_map(fn($cn) => empty($cn) ? null : "{$breakpoint}:{$cn}", $splitClasses);

$optionClasses = [...$optionClasses, ...$splitClasses];
}

return self::unifyClasses($optionClasses);
}

/**
* Processes the given combination for a specific part name.
*
* This method processes the combination value for a given part name based on the provided attributes and manifest.
* It ensures that the combination is correctly handled according to the attributes and manifest.
*
* @param string $partName The name of the part for which the combination is being processed.
* @param mixed $combo The combination value to be processed.
* @param array<mixed> $attributes The attributes that dictate how the combination should be processed.
* @param array<mixed> $manifest The manifest that provides additional context for processing the combination.
*
* @throws JsonException If the combination was not defined correctly.
*
* @return string The processed combination value.
*/
private static function processCombination($partName, $combo, $attributes, $manifest): string
{
$matches = true;

foreach ($combo['attributes'] as $attributeName => $allowedValue) {
$optionValue = Helpers::checkAttr($attributeName, $attributes, $manifest, true);

if (\is_bool($optionValue)) {
$optionValue = $optionValue ? 'true' : 'false';
}

if (\is_array($allowedValue) && !\in_array($optionValue, $allowedValue, true)) {
$matches = false;
break;
}

if ($optionValue !== $allowedValue) {
$matches = false;
break;
}
}

if (!$matches) {
return '';
}

$itemPartName = isset($combo['part']) ? $combo['part'] : 'base';
$isSingleValue = isset($combo['twClasses']) || isset($combo['twClassesEditor']);

if ($isSingleValue && !\str_contains($itemPartName, $partName)) {
return '';
}

$rawValue = $combo['output'][$partName]['twClasses'] ?? $combo['twClasses'] ?? '';

if (\is_array($rawValue) && !\array_is_list($rawValue)) {
throw new JsonException('Combination was not defined correctly. Please check the combination definition in the manifest.');
}

return self::unifyClasses($rawValue);
}

/**
* Get Tailwind classes for the given component/block.
*
* @param string $part Part to get classes for.
* @param array<mixed> $attributes Component/block attributes.
* @param array<mixed> $manifest Component/block manifest data.
* @param array<string> ...$custom Additional custom classes.
*
* @throws Exception If the part is not defined in the manifest.
*
* @return string
*/
public static function tailwindClasses($part, $attributes, $manifest, ...$custom): string
{
// If nothing is set, return custom classes as a fallback.
if (!$attributes || !$manifest || !isset($manifest['tailwind']) || \array_keys($manifest['tailwind']) === []) {
return $custom ? Helpers::classnames($custom) : ''; // @phpstan-ignore-line
}

$allParts = isset($manifest['tailwind']['parts']) ? ['base', ...\array_keys($manifest['tailwind']['parts'])] : ['base'];

$partName = 'base';

if (!empty($part) && isset($manifest['tailwind']['parts'][$part]) && \in_array($part, $allParts, true)) {
$partName = $part;
} elseif ($part !== 'base') {
throw new Exception("Part '{$part}' is not defined in the manifest.");
}

// Base classes.
$baseClasses = self::unifyClasses($manifest['tailwind']['parts'][$partName]['twClasses'] ?? $manifest['tailwind']['base']['twClasses'] ?? ['']);

// Option classes.
$options = $manifest['tailwind']['options'] ?? [];

$optionClasses = [];

foreach ($options as $attributeName => $defs) {
$optionValue = Helpers::checkAttr($attributeName, $attributes, $manifest, true);

if (\is_bool($optionValue)) {
$optionValue = $optionValue ? 'true' : 'false';
}

$optionClasses[] = self::processOption($partName, $optionValue, $defs);
}

// Combinations.
$combinations = $manifest['tailwind']['combinations'] ?? [];

$combinationClasses = [];

foreach ($combinations as $combo) {
$combinationClasses[] = self::processCombination($partName, $combo, $attributes, $manifest);
}

return Helpers::classnames([$baseClasses, ...$optionClasses, ...$combinationClasses, ...$custom]);
}
}