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

Add slideover component #10

Merged
merged 9 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ Just like any other Blade component, check out the [Laravel Blade docs](https://
</x-rapidez::accordion>
```

#### Slideover
```blade
<label for="my-slideover">
Open Slideover
</label>

<x-rapidez::slideover id="my-slideover" title="Example Slideover">
Your slideover content goes here
</x-rapidez::slideover>
```
Make sure to add this class to your body tag to prevent scrolling when the slideover is open:
```html
<body class="has-[.slideover-checkbox:checked]:overflow-hidden">
```

#### Tag

It is a Blade version of a [dynamic Vue component](https://vuejs.org/guide/essentials/component-basics.html#dynamic-components)
Expand Down
118 changes: 113 additions & 5 deletions demo/components.html
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,122 @@ <h4>Batcave</h4>
</table>
</div>

<h2 class="font-bold text-lg">Slideover component</h2>
Soon...
<h2 class="text-lg font-bold mt-8">Slideover Component</h2>
<div class="grid grid-cols-1 gap-5 lg:grid-cols-3">
<div class="flex flex-col gap-3">
<h3 class="text-md font-bold">Default</h3>
<div>
<label class="relative inline-flex items-center justify-center text transition bg text-base font-medium rounded min-h-12 py-1.5 px-5 hover:bg-opacity-80 border-b border-b-black/15 disabled:text-muted disabled:bg-muted disabled:cursor-not-allowed bg-primary text-primary-text" for="default-slideover">
Open Slideover
</label> <div >
<input id="close-default-slideover" class="hidden" type="reset">
<input id="default-slideover" class="peer hidden slideover-checkbox" type="checkbox">
<label
for="default-slideover"
class="pointer-events-none fixed inset-0 z-40 cursor-pointer bg-black/40 opacity-0 transition peer-checked:pointer-events-auto peer-checked:opacity-100"
></label>
<div class="fixed inset-y-0 transition-all bg-white z-40 flex flex-col max-w-md w-full -left-full peer-checked:left-0">
<div class="slideover-header bg-primary py-5">
<div class="px-5">
<div class="relative flex items-center justify-center">
<span class="text-base max-w-full px-10 truncate font-semibold text-white antialiased">
Example Slideover
</span>
<label for="default-slideover" class="absolute right-0 top-1/2 -translate-y-1/2 cursor-pointer text-white">
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg> </label>
</div>
</div>
</div>
<div class="slideover-wrapper flex flex-1 flex-col items-start overflow-y-auto">
<div class="p-4">
<p class="mb-4">This is an example of the slideover component.</p>
<p>You can put any content here!</p>
</div>
</div>
</div>
</div> </div>
</div>

<div class="flex flex-col gap-3">
<h3 class="text-md font-bold">Right-positioned</h3>
<div>
<label class="relative inline-flex items-center justify-center text transition bg text-base font-medium rounded min-h-12 py-1.5 px-5 hover:bg-opacity-80 border-b border-b-black/15 disabled:text-muted disabled:bg-muted disabled:cursor-not-allowed bg-secondary text-secondary-text" for="right-slideover">
Open Right Slideover
</label> <div >
<input id="close-right-slideover" class="hidden" type="reset">
<input id="right-slideover" class="peer hidden slideover-checkbox" type="checkbox">
<label
for="right-slideover"
class="pointer-events-none fixed inset-0 z-40 cursor-pointer bg-black/40 opacity-0 transition peer-checked:pointer-events-auto peer-checked:opacity-100"
></label>
<div class="fixed inset-y-0 transition-all bg-white z-40 flex flex-col max-w-md w-full -right-full peer-checked:right-0">
<div class="slideover-header bg-primary py-5">
<div class="px-5">
<div class="relative flex items-center justify-center">
<span class="text-base max-w-full px-10 truncate font-semibold text-white antialiased">
Right Slideover
</span>
<label for="right-slideover" class="absolute right-0 top-1/2 -translate-y-1/2 cursor-pointer text-white">
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg> </label>
</div>
</div>
</div>
<div class="slideover-wrapper flex flex-1 flex-col items-start overflow-y-auto">
<div class="p-4">
<p class="mb-4">This slideover appears from the right side.</p>
<p>It demonstrates the position property.</p>
</div>
</div>
</div>
</div> </div>
</div>
<div class="flex flex-col gap-3">
<h3 class="text-md font-bold">Mobile only</h3>
<div>
<label class="relative inline-flex items-center justify-center text transition bg text-base font-medium rounded min-h-12 py-1.5 px-5 hover:bg-opacity-80 border-b-black/15 disabled:text-muted disabled:bg-muted disabled:cursor-not-allowed bg-transparent border text-default hover:border-emphasis lg:hidden" for="mobile-slideover">
Open Mobile Slideover
</label> <div >
<input id="close-mobile-slideover" class="hidden" type="reset">
<input id="mobile-slideover" class="peer hidden slideover-checkbox" type="checkbox">
<label
for="mobile-slideover"
class="pointer-events-none fixed inset-0 z-40 cursor-pointer bg-black/40 opacity-0 transition peer-checked:pointer-events-auto peer-checked:opacity-100"
></label>
<div class="fixed inset-y-0 transition-all bg-white z-40 flex flex-col max-w-md w-full -left-full peer-checked:left-0 lg:contents [&amp;&gt;.slideover-wrapper]:lg:contents [&amp;&gt;.slideover-header]:lg:hidden">
<div class="slideover-header bg-primary py-5">
<div class="px-5">
<div class="relative flex items-center justify-center">
<span class="text-base max-w-full px-10 truncate font-semibold text-white antialiased">
Mobile Slideover
</span>
<label for="mobile-slideover" class="absolute right-0 top-1/2 -translate-y-1/2 cursor-pointer text-white">
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg> </label>
</div>
</div>
</div>
<div class="slideover-wrapper flex flex-1 flex-col items-start overflow-y-auto">
<div class="max-lg:p-4">
<p class="mb-4">This is a mobile-specific slideover that transforms on desktop.</p>
<p>On mobile devices, it appears as a slideover.</p>
<p class="mt-4">On desktop screens (lg breakpoint and above), this content is directly embedded in the page instead of being in a slideover.</p>
</div>
</div>
</div>
</div> </div>
</div>
</div>

<h2 class="font-bold text-lg">Accordion component</h2>
<h2 class="text-lg font-bold">Accordion component</h2>

<div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
<div class="grid grid-cols-1 gap-5 lg:grid-cols-3">
<div class="flex flex-col gap-3">
<h3 class="font-bold text-md">Default</h3>
<h3 class="text-md font-bold">Default</h3>
<div class="flex flex-col group rounded border p-3">
<input
id="checkbox-question-1"
Expand Down
59 changes: 52 additions & 7 deletions resources/views/components-preview.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,27 +244,72 @@ function color(variable, fallback) {
</table>
</x-rapidez::prose>

<h2 class="font-bold text-lg">Slideover component</h2>
Soon...
<h2 class="text-lg font-bold mt-8">Slideover Component</h2>
<div class="grid grid-cols-1 gap-5 lg:grid-cols-3">
<div class="flex flex-col gap-3">
<h3 class="text-md font-bold">Default</h3>
<div>
<x-rapidez::button.primary for="default-slideover">
Open Slideover
</x-rapidez::button.primary>
<x-rapidez::slideover id="default-slideover" title="Example Slideover">
<div class="p-4">
<p class="mb-4">This is an example of the slideover component.</p>
<p>You can put any content here!</p>
</div>
</x-rapidez::slideover>
</div>
</div>

<div class="flex flex-col gap-3">
<h3 class="text-md font-bold">Right-positioned</h3>
<div>
<x-rapidez::button.secondary for="right-slideover">
Open Right Slideover
</x-rapidez::button.secondary>
<x-rapidez::slideover id="right-slideover" position="right" title="Right Slideover">
<div class="p-4">
<p class="mb-4">This slideover appears from the right side.</p>
<p>It demonstrates the position property.</p>
</div>
</x-rapidez::slideover>
</div>
</div>
<div class="flex flex-col gap-3">
<h3 class="text-md font-bold">Mobile only</h3>
<div>
<x-rapidez::button.outline for="mobile-slideover" class="lg:hidden">
Open Mobile Slideover
</x-rapidez::button.outline>
<x-rapidez::slideover.mobile id="mobile-slideover" title="Mobile Slideover">
<div class="max-lg:p-4">
<p class="mb-4">This is a mobile-specific slideover that transforms on desktop.</p>
<p>On mobile devices, it appears as a slideover.</p>
<p class="mt-4">On desktop screens (lg breakpoint and above), this content is directly embedded in the page instead of being in a slideover.</p>
</div>
</x-rapidez::slideover.mobile>
</div>
</div>
</div>

<h2 class="font-bold text-lg">Accordion component</h2>
<h2 class="text-lg font-bold">Accordion component</h2>

<div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
<div class="grid grid-cols-1 gap-5 lg:grid-cols-3">
<div class="flex flex-col gap-3">
<h3 class="font-bold text-md">Default</h3>
<h3 class="text-md font-bold">Default</h3>
<x-rapidez::accordion id="checkbox-question-1" class="rounded border p-3">
<x-slot:label class="font-bold">
Question 1
</x-slot:label>
<x-slot:content class="">
<x-slot:content>
Lorem ipsum dolor, sit, amet consectetur adipisicing elit. Reprehenderit eum in deleniti dicta ducimus perspiciatis provident tempore. Consequuntur nemo blanditiis delectus, quasi velit illum ipsa quibusdam maiores cupiditate itaque repellendus.
</x-slot:content>
</x-rapidez::accordion>
<x-rapidez::accordion id="checkbox-question-2" class="rounded border p-3">
<x-slot:label class="font-bold">
Question 2
</x-slot:label>
<x-slot:content class="">
<x-slot:content>
Lorem ipsum dolor, sit, amet consectetur adipisicing elit. Reprehenderit eum in deleniti dicta ducimus perspiciatis provident tempore. Consequuntur nemo blanditiis delectus, quasi velit illum ipsa quibusdam maiores cupiditate itaque repellendus.
</x-slot:content>
</x-rapidez::accordion>
Expand Down
19 changes: 19 additions & 0 deletions resources/views/components/slideover/mobile.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{--
This mobile version transforms into inline content on desktop. The slideover behavior only applies on mobile screens.

## Behavior
- On mobile: Functions as a regular slideover
- On desktop: Content is displayed directly on the page
- Label should be hidden on desktop using `lg:hidden`

## Example
```blade
<label for="mobile-slideover" class="lg:hidden">
Open Mobile Slideover
</label>
<x-rapidez::slideover.mobile id="mobile-slideover" title="Mobile Slideover">
Mobile slideover
</x-rapidez::slideover.mobile>
```
--}}
@include('rapidez::components.slideover.slideover', ['attributes' => $attributes->class('lg:contents [&>.slideover-wrapper]:lg:contents [&>.slideover-header]:lg:hidden')])
21 changes: 21 additions & 0 deletions resources/views/components/slideover/partials/header.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="slideover-header bg-primary py-5">
<div class="px-5">
<div class="relative flex items-center justify-center">
@if ($hasParent)
<label for="{{ $id }}" class="absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer text-white">
<x-heroicon-o-arrow-left class="size-6" />
</label>
@elseif ($headerbutton->isNotEmpty())
{{ $headerbutton }}
@endif
@if ($title)
<span {{ $title->attributes->class('text-base max-w-full px-10 truncate font-semibold text-white antialiased') }}>
{{ $title }}
</span>
@endif
<label for="{{ $closeId }}" class="absolute right-0 top-1/2 -translate-y-1/2 cursor-pointer text-white">
<x-heroicon-o-x-mark class="size-6" />
</label>
</div>
</div>
</div>
98 changes: 98 additions & 0 deletions resources/views/components/slideover/slideover.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{{--
No-js slideover component by making use of checkboxes and form reset logic for nested slideovers.

## Properties
- `has-parent` Used for nested slideovers. Set to `true` when this slideover is a child of another slideover
- `id` Unique identifier for the checkbox input. Required for the slideover toggle
- `open` Default `false`, set to `true` to have it open initially
- `position` Position of the slideover. Can be `left` or `right`. Defaults to `left`
- `tag` Base tag of the slideover. Set to `form` for parent of nested slideover. Defaults to `div`
- `title` Title displayed in the header. Can be provided as a slot or property

## Slots
- `headerbutton` Custom button in the header instead of the default left arrow
- `title` Alternative way to set the header title
- Default slot Main content of the slideover

## Body Class
Add this class to prevent scrolling when slideover is open:
```html
<body class="has-[.slideover-checkbox:checked]:overflow-hidden">
```

## Nesting Slideovers
For nested slideovers:
1. Parent slideover must use `tag="form"`
2. Child slideovers must set `has-parent="true"`
3. Child slideovers should use default `tag="div"`

This setup enables form reset logic and ensures proper background overlay behavior.

## Examples
Basic usage:
```blade
<label for="my-slideover">
Open Menu
</label>
<x-rapidez::slideover id="my-slideover" title="Menu">
Content goes here
</x-rapidez::slideover>
```

Right-positioned slideover:
```blade
<label for="right-menu">
Open Settings
</label>
<x-rapidez::slideover id="right-menu" position="right" title="Settings">
Settings content
</x-rapidez::slideover>
```

Nested slideovers:
```blade
<label for="parent">
Open Parent
</label>
<x-rapidez::slideover id="parent" tag="form" title="Parent">
Parent content
<label for="child">
Open Child
</label>
<x-rapidez::slideover id="child" has-parent="true" title="Child">
Child content
</x-rapidez::slideover>
</x-rapidez::slideover>
```

--}}
@props(['id' => uniqid('slideover-'), 'title', 'hasParent' => false, 'position' => 'left', 'tag' => 'div', 'open' => false])
@slots(['title', 'headerbutton'])

@php
$isInForm = $tag === 'form' || $hasParent;
$closeId = $isInForm ? 'close-' . $id : $id;
@endphp

<x-rapidez::tag :is="$tag">
<input id="{{ 'close-' . $id }}" class="hidden" type="reset">
@if (!$hasParent)
<input @checked($open) id="{{ $id }}" class="peer hidden slideover-checkbox" type="checkbox">
<label
for="{{ $closeId }}"
class="pointer-events-none fixed inset-0 z-40 cursor-pointer bg-black/40 opacity-0 transition peer-checked:pointer-events-auto peer-checked:opacity-100"
></label>
@else
<input @checked($open) id="{{ $id }}" class="peer hidden" type="checkbox">
@endif
<div {{ $attributes->class([
'fixed inset-y-0 transition-all bg-white z-40 flex flex-col max-w-md w-full',
'-right-full peer-checked:right-0' => $position === 'right',
'-left-full peer-checked:left-0' => $position === 'left',
]) }}>
@include('rapidez::components.slideover.partials.header')
<div class="slideover-wrapper flex flex-1 flex-col items-start overflow-y-auto">
{{ $slot }}
</div>
</div>
</x-rapidez::tag>
Loading