Skip to content

Commit ecdc403

Browse files
committed
Add input wrapper playground for Blade and JavaScript adapters
1 parent 4dbcfe8 commit ecdc403

12 files changed

Lines changed: 475 additions & 0 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Filament\Pages;
4+
5+
use App\Filament\Widgets\InputWrapperBlade;
6+
use App\Filament\Widgets\InputWrapperReact;
7+
use App\Filament\Widgets\InputWrapperSvelte;
8+
use App\Filament\Widgets\InputWrapperVue;
9+
use Filament\Pages\Dashboard;
10+
11+
class InputWrapperPlayground extends Dashboard
12+
{
13+
protected static string $routePath = 'input-wrapper-playground';
14+
15+
protected static ?string $title = 'Input wrapper playground';
16+
17+
public function getWidgets(): array
18+
{
19+
return [InputWrapperBlade::class, InputWrapperReact::class, InputWrapperVue::class, InputWrapperSvelte::class];
20+
}
21+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Filament\Widgets;
4+
5+
use Filament\Widgets\Widget;
6+
7+
class InputWrapperBlade extends Widget
8+
{
9+
protected string $view = 'filament.widgets.input-wrapper-blade';
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Filament\Widgets;
4+
5+
use Filament\Widgets\Concerns\HasJsRenderer;
6+
use Filament\Widgets\Widget;
7+
use Illuminate\Support\Facades\Vite;
8+
9+
class InputWrapperReact extends Widget
10+
{
11+
use HasJsRenderer;
12+
13+
public function getRenderer(): string
14+
{
15+
return Vite::asset('resources/js/filament/widgets/input-wrapper-react.jsx');
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Filament\Widgets;
4+
5+
use Filament\Widgets\Concerns\HasJsRenderer;
6+
use Filament\Widgets\Widget;
7+
use Illuminate\Support\Facades\Vite;
8+
9+
class InputWrapperSvelte extends Widget
10+
{
11+
use HasJsRenderer;
12+
13+
public function getRenderer(): string
14+
{
15+
return Vite::asset('resources/js/filament/widgets/input-wrapper-svelte.svelte.js');
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Filament\Widgets;
4+
5+
use Filament\Widgets\Concerns\HasJsRenderer;
6+
use Filament\Widgets\Widget;
7+
use Illuminate\Support\Facades\Vite;
8+
9+
class InputWrapperVue extends Widget
10+
{
11+
use HasJsRenderer;
12+
13+
public function getRenderer(): string
14+
{
15+
return Vite::asset('resources/js/filament/widgets/input-wrapper-vue.js');
16+
}
17+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<script>
2+
import { PoundSterling } from '@lucide/svelte'
3+
import InputWrapper from '../../../../vendor/filament/support/resources/js/svelte/InputWrapper.svelte'
4+
import Icon from '../../../../vendor/filament/support/resources/js/svelte/Icon.svelte'
5+
let amount = $state(125)
6+
let disabled = $state(false)
7+
let invalid = $state(false)
8+
let affixes = $state(true)
9+
let icons = $state(false)
10+
let inline = $state(false)
11+
let actions = $state(false)
12+
let input = $state()
13+
function reset() {
14+
amount = 125
15+
disabled = false
16+
invalid = false
17+
affixes = true
18+
icons = false
19+
inline = false
20+
actions = false
21+
}
22+
</script>
23+
24+
{#snippet icon()}<Icon aria-hidden="true"><PoundSterling /></Icon>{/snippet}
25+
{#snippet action()}<button
26+
type="button"
27+
{disabled}
28+
onclick={() => (amount = undefined)}>Clear</button
29+
>{/snippet}
30+
<div class="icon-demo" data-framework="svelte">
31+
<h2 class="fi-section-header-heading">Svelte · JavaScript</h2>
32+
<label for="svelte-price">Workshop price</label>
33+
<InputWrapper
34+
{disabled}
35+
valid={!invalid}
36+
prefix={affixes ? '£' : null}
37+
suffix={affixes ? 'GBP' : null}
38+
inlinePrefix={inline}
39+
inlineSuffix={inline}
40+
prefixIcon={icons ? icon : undefined}
41+
suffixActions={actions ? action : undefined}
42+
>
43+
<input
44+
bind:this={input}
45+
id="svelte-price"
46+
class="fi-input"
47+
type="number"
48+
min="1"
49+
required
50+
bind:value={amount}
51+
{disabled}
52+
aria-invalid={invalid}
53+
aria-describedby={invalid ? 'svelte-price-error' : undefined}
54+
/>
55+
</InputWrapper>
56+
{#if invalid}<p id="svelte-price-error" role="status">
57+
Enter a workshop price of at least £1.
58+
</p>{/if}
59+
<div class="icon-demo-controls">
60+
<label><input type="checkbox" bind:checked={disabled} /> Disabled</label
61+
>
62+
<label><input type="checkbox" bind:checked={invalid} /> Invalid</label>
63+
<label
64+
><input type="checkbox" bind:checked={affixes} /> Text affixes</label
65+
>
66+
<label><input type="checkbox" bind:checked={icons} /> Icon</label>
67+
<label><input type="checkbox" bind:checked={inline} /> Inline</label>
68+
<label
69+
><input type="checkbox" bind:checked={actions} /> Clear action</label
70+
>
71+
</div>
72+
<div class="icon-demo-controls">
73+
<button
74+
type="button"
75+
class="fi-btn fi-size-sm"
76+
onclick={() => (invalid = !input.checkValidity())}>Validate</button
77+
>
78+
<button type="button" class="fi-btn fi-size-sm" onclick={reset}
79+
>Reset</button
80+
>
81+
</div>
82+
<p>
83+
Native input value: {amount ?? 'empty'}. Wrapper styling and input
84+
semantics are supplied separately.
85+
</p>
86+
</div>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<script setup>
2+
import { ref } from 'vue'
3+
import { PoundSterling } from '@lucide/vue'
4+
import InputWrapper from '../../../../vendor/filament/support/resources/js/vue/InputWrapper.vue'
5+
import Icon from '../../../../vendor/filament/support/resources/js/vue/Icon.vue'
6+
const amount = ref('125')
7+
const disabled = ref(false)
8+
const invalid = ref(false)
9+
const affixes = ref(true)
10+
const icons = ref(false)
11+
const inline = ref(false)
12+
const actions = ref(false)
13+
const input = ref()
14+
function reset() {
15+
amount.value = '125'
16+
disabled.value = false
17+
invalid.value = false
18+
affixes.value = true
19+
icons.value = false
20+
inline.value = false
21+
actions.value = false
22+
}
23+
</script>
24+
<template>
25+
<div class="icon-demo" data-framework="vue">
26+
<h2 class="fi-section-header-heading">Vue · JavaScript</h2>
27+
<label for="vue-price">Workshop price</label>
28+
<InputWrapper
29+
:disabled="disabled"
30+
:valid="!invalid"
31+
:prefix="affixes ? '£' : null"
32+
:suffix="affixes ? 'GBP' : null"
33+
:inline-prefix="inline"
34+
:inline-suffix="inline"
35+
>
36+
<template v-if="icons" #prefixIcon
37+
><Icon aria-hidden="true"><PoundSterling /></Icon
38+
></template>
39+
<template v-if="actions" #suffixActions
40+
><button
41+
type="button"
42+
:disabled="disabled"
43+
@click="amount = ''"
44+
>
45+
Clear
46+
</button></template
47+
>
48+
<input
49+
ref="input"
50+
id="vue-price"
51+
class="fi-input"
52+
type="number"
53+
min="1"
54+
required
55+
v-model="amount"
56+
:disabled="disabled"
57+
:aria-invalid="invalid"
58+
:aria-describedby="invalid ? 'vue-price-error' : undefined"
59+
/>
60+
</InputWrapper>
61+
<p v-if="invalid" id="vue-price-error" role="status">
62+
Enter a workshop price of at least £1.
63+
</p>
64+
<div class="icon-demo-controls">
65+
<label><input type="checkbox" v-model="disabled" /> Disabled</label>
66+
<label><input type="checkbox" v-model="invalid" /> Invalid</label>
67+
<label
68+
><input type="checkbox" v-model="affixes" /> Text affixes</label
69+
>
70+
<label><input type="checkbox" v-model="icons" /> Icon</label>
71+
<label><input type="checkbox" v-model="inline" /> Inline</label>
72+
<label
73+
><input type="checkbox" v-model="actions" /> Clear action</label
74+
>
75+
</div>
76+
<div class="icon-demo-controls">
77+
<button
78+
type="button"
79+
class="fi-btn fi-size-sm"
80+
@click="invalid = !input.checkValidity()"
81+
>
82+
Validate
83+
</button>
84+
<button type="button" class="fi-btn fi-size-sm" @click="reset">
85+
Reset
86+
</button>
87+
</div>
88+
<p>
89+
Native input value: {{ amount || 'empty' }}. Wrapper styling and
90+
input semantics are supplied separately.
91+
</p>
92+
</div>
93+
</template>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React, { useRef, useState } from 'react'
2+
import { createRoot } from 'react-dom/client'
3+
import { PoundSterling } from 'lucide-react'
4+
import InputWrapper from '../../../../vendor/filament/support/resources/js/react/InputWrapper'
5+
import Icon from '../../../../vendor/filament/support/resources/js/react/Icon'
6+
7+
function InputWrapperReact() {
8+
const [amount, setAmount] = useState('125')
9+
const [disabled, setDisabled] = useState(false)
10+
const [invalid, setInvalid] = useState(false)
11+
const [affixes, setAffixes] = useState(true)
12+
const [icons, setIcons] = useState(false)
13+
const [inline, setInline] = useState(false)
14+
const [actions, setActions] = useState(false)
15+
const input = useRef(null)
16+
return (
17+
<div className="icon-demo" data-framework="react">
18+
<h2 className="fi-section-header-heading">React · JavaScript</h2>
19+
<label htmlFor="react-price">Workshop price</label>
20+
<InputWrapper
21+
disabled={disabled}
22+
valid={!invalid}
23+
prefix={affixes ? '£' : null}
24+
suffix={affixes ? 'GBP' : null}
25+
inlinePrefix={inline}
26+
inlineSuffix={inline}
27+
prefixIcon={
28+
icons ? (
29+
<Icon aria-hidden="true">
30+
<PoundSterling />
31+
</Icon>
32+
) : null
33+
}
34+
suffixActions={
35+
actions ? (
36+
<button
37+
type="button"
38+
disabled={disabled}
39+
onClick={() => setAmount('')}
40+
>
41+
Clear
42+
</button>
43+
) : null
44+
}
45+
>
46+
<input
47+
ref={input}
48+
id="react-price"
49+
className="fi-input"
50+
type="number"
51+
min="1"
52+
required
53+
value={amount}
54+
onChange={(event) => setAmount(event.target.value)}
55+
disabled={disabled}
56+
aria-invalid={invalid}
57+
aria-describedby={invalid ? 'react-price-error' : undefined}
58+
/>
59+
</InputWrapper>
60+
{invalid && (
61+
<p id="react-price-error" role="status">
62+
Enter a workshop price of at least £1.
63+
</p>
64+
)}
65+
<div className="icon-demo-controls">
66+
{[
67+
[disabled, setDisabled, 'Disabled'],
68+
[invalid, setInvalid, 'Invalid'],
69+
[affixes, setAffixes, 'Text affixes'],
70+
[icons, setIcons, 'Icon'],
71+
[inline, setInline, 'Inline'],
72+
[actions, setActions, 'Clear action'],
73+
].map(([checked, update, label]) => (
74+
<label key={label}>
75+
<input
76+
type="checkbox"
77+
checked={checked}
78+
onChange={(event) => update(event.target.checked)}
79+
/>{' '}
80+
{label}
81+
</label>
82+
))}
83+
</div>
84+
<div className="icon-demo-controls">
85+
<button
86+
type="button"
87+
className="fi-btn fi-size-sm"
88+
onClick={() => setInvalid(!input.current.checkValidity())}
89+
>
90+
Validate
91+
</button>
92+
<button
93+
type="button"
94+
className="fi-btn fi-size-sm"
95+
onClick={() => {
96+
setAmount('125')
97+
setDisabled(false)
98+
setInvalid(false)
99+
setAffixes(true)
100+
setIcons(false)
101+
setInline(false)
102+
setActions(false)
103+
}}
104+
>
105+
Reset
106+
</button>
107+
</div>
108+
<p>
109+
Native input value: {amount || 'empty'}. Wrapper styling and
110+
input semantics are supplied separately.
111+
</p>
112+
</div>
113+
)
114+
}
115+
116+
export default function mountInputWrapperReact({ host, props: initialProps }) {
117+
const root = createRoot(host)
118+
const update = (props) => root.render(<InputWrapperReact {...props} />)
119+
update(initialProps)
120+
return { update, destroy: () => root.unmount() }
121+
}

0 commit comments

Comments
 (0)