Skip to content
Merged
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
1 change: 0 additions & 1 deletion .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ LOG_LEVEL=debug

NIGHTWATCH_ENABLED=false


DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
Expand Down
2 changes: 0 additions & 2 deletions lang/de_CH/components.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
return [
'intro' => [
'title' => 'Schön, bist du da',
'window' => 'Wir sind codebar',
'legend' => 'Was möchtest du über uns wissen?',
'overview' => 'Übersicht',
'shortcuts' => 'Abschnitt wechseln: Zifferntasten 1 bis 3 oder die Pfeiltasten links und rechts.',
'prev_section' => 'Vorheriger Abschnitt',
'next_section' => 'Nächster Abschnitt',
Expand Down
2 changes: 0 additions & 2 deletions lang/en_CH/components.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
return [
'intro' => [
'title' => 'Good to have you here',
'window' => 'We are codebar',
'legend' => 'What would you like to know about us?',
'overview' => 'Overview',
'shortcuts' => 'Switch section: number keys 1 to 3, or the left and right arrow keys.',
'prev_section' => 'Previous section',
'next_section' => 'Next section',
Expand Down
43 changes: 28 additions & 15 deletions resources/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -857,31 +857,44 @@ body {
}

.intro-tabs__panels {
display: grid;
min-width: 0;
}

.intro-tabs__panel {
grid-area: 1 / 1;
}

.intro-tabs__panel:not(:first-child) {
visibility: hidden;
opacity: 0;
@supports not selector(:has(*)) {
.intro-tabs__panel:not(:first-child) {
display: none;
}
}

@supports selector(:has(*)) {
.intro-tabs__panel {
visibility: hidden;
opacity: 0;
transition: opacity 200ms ease;
display: none;
}

.intro-tabs:has([data-tab='0']:checked) [data-panel='0'],
.intro-tabs:has([data-tab='1']:checked) [data-panel='1'],
.intro-tabs:has([data-tab='2']:checked) [data-panel='2'],
.intro-tabs:has([data-tab='3']:checked) [data-panel='3'] {
visibility: visible;
opacity: 1;
.intro-tabs:has([data-tab='2']:checked) [data-panel='2'] {
display: block;
}

@media (width >= 40rem) {
.intro-tabs__panels {
display: grid;
}

.intro-tabs__panel {
display: block;
grid-area: 1 / 1;
visibility: hidden;
opacity: 0;
transition: opacity 200ms ease;
}

.intro-tabs:has([data-tab='0']:checked) [data-panel='0'],
.intro-tabs:has([data-tab='1']:checked) [data-panel='1'],
.intro-tabs:has([data-tab='2']:checked) [data-panel='2'] {
visibility: visible;
opacity: 1;
}
}
}
94 changes: 36 additions & 58 deletions resources/views/components/intro.blade.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
@php
$sections = [
['key' => 'start', 'command' => null, 'next' => null],
['key' => 'who_we_are', 'command' => __('components.intro.who_we_are.command'), 'next' => 2],
['key' => 'what_we_do', 'command' => __('components.intro.what_we_do.command'), 'next' => 3],
['key' => 'who_we_are', 'command' => __('components.intro.who_we_are.command'), 'next' => 1],
['key' => 'what_we_do', 'command' => __('components.intro.what_we_do.command'), 'next' => 2],
['key' => 'how_we_work', 'command' => __('components.intro.how_we_work.command'), 'next' => null],
];

Expand All @@ -22,20 +21,17 @@
<span class="size-3 rounded-full bg-amber-400" aria-hidden="true"></span>
<span class="size-3 rounded-full bg-emerald-400" aria-hidden="true"></span>
<span class="ml-3 truncate font-mono text-xs text-gray-700">
{{ __('components.intro.window') }} <span class="text-gray-500">–</span> {{ config('company.legal_name') }}
{{ config('company.legal_name') }}
</span>
</div>

<input type="radio" name="intro-tab" id="intro-tab-0" data-tab="0" checked class="sr-only"
aria-label="{{ __('components.intro.overview') }}"/>

<div class="flex flex-col border-b border-gray-300 bg-gray-200 sm:flex-row sm:overflow-x-auto">
@foreach(array_slice($sections, 1) as $section)
<label for="intro-tab-{{ $loop->iteration }}"
@foreach($sections as $section)
<label for="intro-tab-{{ $loop->index }}"
class="flex shrink-0 cursor-pointer items-center gap-2.5 border-b border-l-2 border-b-gray-300 border-l-transparent px-4 py-3 font-mono text-sm text-gray-600 transition select-none last:border-b-0 hover:text-gray-900 has-checked:border-l-brand has-checked:bg-gray-100 has-checked:text-gray-900 has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-brand sm:border-t-2 sm:border-r sm:border-b-0 sm:border-l-0 sm:border-t-transparent sm:border-r-gray-300 sm:has-checked:border-t-brand">
<input type="radio" name="intro-tab" id="intro-tab-{{ $loop->iteration }}"
data-tab="{{ $loop->iteration }}" data-shortcut="{{ $loop->iteration }}"
class="sr-only"/>
<input type="radio" name="intro-tab" id="intro-tab-{{ $loop->index }}"
data-tab="{{ $loop->index }}" data-shortcut="{{ $loop->iteration }}"
@checked($loop->first) class="sr-only"/>
<kbd class="{{ $cap }}" aria-hidden="true">{{ $loop->iteration }}</kbd>
{{ $section['command'] }}
</label>
Expand All @@ -53,57 +49,39 @@ class="{{ $cap }} cursor-pointer transition hover:bg-white focus-ring">→</butt
@foreach($sections as $section)
<div class="intro-tabs__panel min-w-0" data-panel="{{ $loop->index }}">
<div>
@if($section['key'] === 'start')
<ul class="space-y-2">
@foreach(array_slice($sections, 1) as $target)
<li>
<label for="intro-tab-{{ $loop->iteration }}"
class="flex max-w-[68ch] cursor-pointer items-start gap-3 select-none">
<kbd class="{{ $cap }} mt-0.5" aria-hidden="true">{{ $loop->iteration }}</kbd>
<span class="text-brand underline-offset-4 hover:underline">
{{ __('components.intro.'.$target['key'].'.teaser') }}
</span>
</label>
<h2 class="sr-only">{{ __('components.intro.'.$section['key'].'.title') }}</h2>

@foreach (\Illuminate\Support\Arr::wrap(__('components.intro.'.$section['key'].'.text')) as $paragraph)
<p @class(['max-w-[68ch]', 'mt-3' => ! $loop->first])>{!! $paragraph !!}</p>
@endforeach

@php($items = __('components.intro.'.$section['key'].'.items'))
@if (is_array($items))
<ul class="mt-4 space-y-2">
@foreach ($items as $item)
<li class="flex max-w-[68ch] gap-3">
<span class="text-gray-500" aria-hidden="true">·</span>
<span class="[&_b]:font-normal [&_b]:text-brand">{!! $item !!}</span>
</li>
@endforeach
</ul>
@else
<h2 class="sr-only">{{ __('components.intro.'.$section['key'].'.title') }}</h2>

@foreach (\Illuminate\Support\Arr::wrap(__('components.intro.'.$section['key'].'.text')) as $paragraph)
<p @class(['max-w-[68ch]', 'mt-3' => ! $loop->first])>{!! $paragraph !!}</p>
@endforeach

@php($items = __('components.intro.'.$section['key'].'.items'))
@if (is_array($items))
<ul class="mt-4 space-y-2">
@foreach ($items as $item)
<li class="flex max-w-[68ch] gap-3">
<span class="text-gray-500" aria-hidden="true">·</span>
<span class="[&_b]:font-normal [&_b]:text-brand">{!! $item !!}</span>
</li>
@endforeach
</ul>
@endif
@endif

@unless($section['key'] === 'start')
<p class="mt-6">
@if($section['next'] !== null)
<label for="intro-tab-{{ $section['next'] }}"
class="{{ $pill }} border border-brand bg-white text-brand hover:bg-brand hover:text-white">
<span aria-hidden="true">→</span>
{{ __('components.intro.next', ['title' => $sections[$section['next']]['command']]) }}
</label>
@else
<a href="{{ localized_route('contact.index') }}"
class="{{ $pill }} focus-ring bg-brand text-white hover:bg-brand-strong">
<span aria-hidden="true">→</span>
{{ __('components.intro.cta') }}
</a>
@endif
</p>
@endunless
<p class="mt-6">
@if($section['next'] !== null)
<label for="intro-tab-{{ $section['next'] }}"
class="{{ $pill }} border border-brand bg-white text-brand hover:bg-brand hover:text-white">
<span aria-hidden="true">→</span>
{{ __('components.intro.next', ['title' => $sections[$section['next']]['command']]) }}
</label>
@else
<a href="{{ localized_route('contact.index') }}"
class="{{ $pill }} focus-ring bg-brand text-white hover:bg-brand-strong">
<span aria-hidden="true">→</span>
{{ __('components.intro.cta') }}
</a>
@endif
</p>
</div>
</div>
@endforeach
Expand Down
78 changes: 37 additions & 41 deletions tests/Feature/Components/IntroTabsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@
: $panel->before('</fieldset>')->toString();
}

it('renders all four sections as panels at once', function () {
it('renders the three sections as panels at once', function () {
$html = introMarkup();

foreach (range(0, 3) as $index) {
foreach (range(0, 2) as $index) {
expect($html)->toContain('data-panel="'.$index.'"');
}

expect($html)->not->toContain('data-panel="4"');
expect($html)->not->toContain('data-panel="3"');
});

it('has a stylesheet rule for every tab it renders', function () {
Expand Down Expand Up @@ -72,7 +72,7 @@
});

it('keeps the four areas on one line each', function () {
$panel = introPanel(introMarkup(), 2);
$panel = introPanel(introMarkup(), 1);

foreach (introItems() as $item) {
expect($panel)->toContain('<span class="[&_b]:font-normal [&_b]:text-brand">'.$item.'</span>');
Expand All @@ -82,34 +82,40 @@
it('switches without javascript through radio inputs', function () {
$html = introMarkup();

foreach (range(0, 3) as $index) {
foreach (range(0, 2) as $index) {
expect($html)
->toContain('id="intro-tab-'.$index.'"')
->toContain('data-tab="'.$index.'"');
}

foreach (range(1, 3) as $index) {
expect($html)->toContain('for="intro-tab-'.$index.'"');
->toContain('data-tab="'.$index.'"')
->toContain('for="intro-tab-'.$index.'"');
}

expect($html)->toContain('name="intro-tab"');
});

it('leaves the start panel out of the tab bar', function () {
it('puts every section into the tab bar, and nothing else', function () {
$bar = Str::of(introMarkup())->after('</legend>')->before('intro-tabs__panels')->toString();

expect($bar)->not->toContain('for="intro-tab-0"')
->and(substr_count($bar, '<label'))->toBe(3);
expect(substr_count($bar, '<label'))->toBe(3)
->and($bar)->toContain('for="intro-tab-0"');
});

it('opens on the start tab', function () {
it('opens on the first section instead of an overview', function () {
$html = introMarkup();

$first = Str::of($html)->after('id="intro-tab-0"')->before('/>')->toString();
$second = Str::of($html)->after('id="intro-tab-1"')->before('/>')->toString();

expect($first)->toContain('checked')
->and($second)->not->toContain('checked');
->and($second)->not->toContain('checked')
->and(introPanel($html, 0))->toContain(__('components.intro.who_we_are.text'));
});

it('no longer lists the sections as teasers anywhere', function () {
$window = Str::of(introMarkup())->after('<fieldset')->before('</fieldset>')->toString();

foreach (['who_we_are', 'what_we_do', 'how_we_work'] as $key) {
expect($window)->not->toContain(__('components.intro.'.$key.'.teaser'));
}
});

it('exposes one number key per tab', function () {
Expand All @@ -135,32 +141,15 @@
->toContain('<button type="button" @click="step(1)" aria-label="'.__('components.intro.next_section').'"');
});

it('opens the start tab with nothing but the three teasers', function () {
expect(introPanel(introMarkup(), 0))->not->toContain(__('components.intro.title'));
});

it('links from the start tab to each of the other three, with the full sentence as the link', function () {
$panel = introPanel(introMarkup(), 0);

foreach ([1, 2, 3] as $index) {
expect($panel)->toContain('for="intro-tab-'.$index.'"');
}

foreach (['who_we_are', 'what_we_do', 'how_we_work'] as $key) {
expect($panel)->toContain(__('components.intro.'.$key.'.teaser'))
->and($panel)->not->toContain(__('components.intro.'.$key.'.command').'</span>');
}
});

it('offers the next section below the two middle panels', function () {
it('offers the next section below the first two panels', function () {
$html = introMarkup();

expect(introPanel($html, 1))->toContain('for="intro-tab-2"')
->and(introPanel($html, 2))->toContain('for="intro-tab-3"');
expect(introPanel($html, 0))->toContain('for="intro-tab-1"')
->and(introPanel($html, 1))->toContain('for="intro-tab-2"');
});

it('ends the last panel on the contact page instead of another tab', function () {
$panel = introPanel(introMarkup(), 3);
$panel = introPanel(introMarkup(), 2);

expect($panel)
->toContain(localized_route('contact.index'))
Expand Down Expand Up @@ -195,12 +184,19 @@
expect(introMarkup())->not->toContain('➜');
});

it('numbers the three teasers like the tabs they open', function () {
$panel = introPanel(introMarkup(), 0);
it('lets the panel height follow the content on mobile and freezes it on desktop', function () {
$intro = Str::after(file_get_contents(resource_path('css/app.css')), '.intro-tabs__panels');

Check failure on line 188 in tests/Feature/Components/IntroTabsTest.php

View workflow job for this annotation

GitHub Actions / pull-request | ci larastan

Parameter #1 $subject of static method Illuminate\Support\Str::after() expects string, string|false given.

foreach (range(1, 3) as $index) {
expect($panel)->toContain('>'.$index.'</kbd>');
}
$mobile = Str::before($intro, '@media');
$desktop = Str::after($intro, '@media (width >= 40rem)');

expect($mobile)->toContain('display: none')
->and($mobile)->not->toContain('display: grid')
->and($desktop)->toContain('display: grid')
->and($desktop)->toContain('grid-area: 1 / 1');

expect(Str::before($intro, '.intro-tabs__panel:not(:first-child)'))
->toContain('@supports not selector(:has(*))');
});

it('translates the commands per locale', function () {
Expand Down
Loading
Loading