Add the stack layout component and the layout foundation
Plan step 35: <x-stack gap align>, the first of M3's layout components, with what they all share - src/Support/Layout.php reading the props into data-md-* attributes, the spacing-token gap and padding rules, the hide-below/hide-from rules in material.visibility, the stylesheet checks for resources/css/layout, the browser test file and the skill's Layout group. The Workbench puts the material layers above Tailwind's preflight, which would otherwise zero every layout padding and margin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
6be9f35c98
commit
b677e60876
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace NoNameWeb\LivewireMaterial\Support;
|
||||
|
||||
/**
|
||||
* What the layout components share: M3's names for breakpoints and spacing, read from props into
|
||||
* the `data-md-*` attributes resources/css/layout/*.css matches literally.
|
||||
*
|
||||
* Every reader takes whatever a view was given and returns a name from the fixed list or nothing,
|
||||
* so an attribute only ever carries a value the stylesheet has a rule for. Breakpoints are M3's
|
||||
* five, compact below 600px, then medium 600, expanded 840, large 1200 and extra-large 1600
|
||||
* (docs/reference/m3/foundations.md § Layout → Breakpoints); spacing is M3's measurement scale,
|
||||
* `space25` … `space900` (docs/reference/m3/styles-supplement.md § Spacing), the same thirteen
|
||||
* tokens resources/css/tokens/spacing.css declares.
|
||||
*/
|
||||
class Layout
|
||||
{
|
||||
/** The breakpoints, each at its lower edge in px, smallest first. */
|
||||
public const array BREAKPOINTS = [
|
||||
'compact' => 0,
|
||||
'medium' => 600,
|
||||
'expanded' => 840,
|
||||
'large' => 1200,
|
||||
'extra-large' => 1600,
|
||||
];
|
||||
|
||||
/** M3's spacing tokens, by the name after `--md-sys-measurement-`. */
|
||||
public const array SPACING = [
|
||||
'space25', 'space50', 'space75', 'space100', 'space125', 'space200', 'space300',
|
||||
'space400', 'space500', 'space600', 'space700', 'space800', 'space900',
|
||||
];
|
||||
|
||||
/**
|
||||
* The elements a layout component can be drawn as: containers of flow content. A void element
|
||||
* could hold no slot, and a form control or a link would carry its own semantics into a box.
|
||||
*/
|
||||
public const array ELEMENTS = [
|
||||
'div', 'section', 'article', 'aside', 'main', 'nav', 'header', 'footer',
|
||||
'ul', 'ol', 'li', 'dl', 'form', 'fieldset', 'figure', 'span', 'p',
|
||||
];
|
||||
|
||||
/**
|
||||
* A spacing token's name, `none` for anything else that was given, or null when nothing was.
|
||||
* `none` is written out so an unknown gap on a component with a default (a feed's spacer)
|
||||
* falls back to no gap rather than to that default.
|
||||
*/
|
||||
public static function spacing(mixed $value): ?string
|
||||
{
|
||||
if ($value === null || $value === false || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return in_array($value, self::SPACING, true) ? $value : 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* A breakpoint a prop can hide or restack from: medium, expanded, large or extra-large. There
|
||||
* is nothing below compact, and from compact is every width.
|
||||
*/
|
||||
public static function edge(mixed $value): ?string
|
||||
{
|
||||
return is_string($value) && $value !== 'compact' && array_key_exists($value, self::BREAKPOINTS) ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The element to draw, or the component's default when `as` names none of ours.
|
||||
*/
|
||||
public static function element(mixed $as, string $default = 'div'): string
|
||||
{
|
||||
return in_array($as, self::ELEMENTS, true) ? $as : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* One of a fixed set of names, or the default.
|
||||
*
|
||||
* @param list<string> $allowed
|
||||
*/
|
||||
public static function choice(mixed $value, array $allowed, ?string $default = null): ?string
|
||||
{
|
||||
return in_array($value, $allowed, true) ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* A grid's columns at every breakpoint, as the inline custom properties its stylesheet reads.
|
||||
*
|
||||
* `columns` is one whole number for every breakpoint, or a map from breakpoint to number; a
|
||||
* breakpoint left out takes the nearest smaller one's, and compact takes 1 when nothing is
|
||||
* given for it. All five are written on every grid, so a grid inside another never inherits
|
||||
* its parent's count through the cascade.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public static function columns(mixed $columns): array
|
||||
{
|
||||
$given = match (true) {
|
||||
is_array($columns) => $columns,
|
||||
$columns === null => [],
|
||||
default => ['compact' => $columns],
|
||||
};
|
||||
|
||||
$filled = [];
|
||||
$current = 1;
|
||||
|
||||
foreach (array_keys(self::BREAKPOINTS) as $breakpoint) {
|
||||
$count = filter_var($given[$breakpoint] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 24]]);
|
||||
$current = $count === false ? $current : $count;
|
||||
$filled[$breakpoint] = $current;
|
||||
}
|
||||
|
||||
return $filled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `style` those columns become: `--md-columns-compact: 1; …`.
|
||||
*
|
||||
* @param array<string, int> $columns
|
||||
*/
|
||||
public static function columnStyle(array $columns): string
|
||||
{
|
||||
return collect($columns)->map(fn (int $count, string $breakpoint): string => "--md-columns-{$breakpoint}: {$count};")->implode(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* A minimum item width — `280px`, `18rem`, `20ch`, or a bare number read as px — or null.
|
||||
*/
|
||||
public static function length(mixed $value): ?string
|
||||
{
|
||||
if (is_int($value) || (is_string($value) && preg_match('/^\d+$/', $value) === 1)) {
|
||||
return (int) $value > 0 ? ((int) $value).'px' : null;
|
||||
}
|
||||
|
||||
return is_string($value) && preg_match('/^(?:\d+|\d*\.\d+)(?:px|rem|em|ch)$/', $value) === 1 ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes every layout component shares: `hide-below` and `hide-from`.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function visibility(mixed $hideBelow, mixed $hideFrom): array
|
||||
{
|
||||
return array_filter([
|
||||
'data-md-hide-below' => self::edge($hideBelow),
|
||||
'data-md-hide-from' => self::edge($hideFrom),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user