Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Wiebe Nieuwenhuis committed Jan 10, 2023
0 parents commit ccca955
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 0 deletions.
32 changes: 32 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "wiebenieuwenhuis/filament-char-counter",
"description": "Count the amount of characters in a field",
"autoload": {
"psr-4": {
"Wiebenieuwenhuis\\FilamentCharCounter\\": "src/"
}
},
"authors": [
{
"name": "Wiebe Nieuwenhuis",
"email": "[email protected]"
}
],
"require": {
"php": "^8.0",
"filament/forms": "^2.9",
"spatie/laravel-package-tools": "^1.10"
},
"minimum-stability": "dev",
"config": {
"sort-packages": true
},
"extra": {
"laravel": {
"providers": [
"Wiebenieuwenhuis\\FilamentCharCounter\\FilamentCharCounterProvider"
]
}
},
"prefer-stable": true
}
15 changes: 15 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Filament Char Counter

Show a character counter in a text input or textarea.

TextInput with a 55 max counter, you can exceed the 55 character limit, the counter will turn red.
```php
TextInput::make('title')->characterLimit(55),
```

Textarea with a max length of 55 characters. You can't exceed the 55 character limit.
```php
Textarea::make('content')->maxLength(55),
```

![screenshot](screenshot.png)
109 changes: 109 additions & 0 deletions resources/views/text-input.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
@php
$datalistOptions = $getDatalistOptions();
$affixLabelClasses = [
'whitespace-nowrap group-focus-within:text-primary-500',
'text-gray-400' => ! $errors->has($getStatePath()),
'text-danger-400' => $errors->has($getStatePath()),
];
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:id="$getId()"
:label="$getLabel()"
:label-sr-only="$isLabelHidden()"
:helper-text="$getHelperText()"
:hint="$getHint()"
:hint-action="$getHintAction()"
:hint-color="$getHintColor()"
:hint-icon="$getHintIcon()"
:required="$isRequired()"
:state-path="$getStatePath()"
>
<div x-data="{characterLimit: {{ $getCharacterLimit() }}, characterCount: {{ strlen($getState()) }}}" {{ $attributes->merge($getExtraAttributes())->class(['filament-forms-text-input-component flex items-center space-x-2 rtl:space-x-reverse group']) }}>
@if (($prefixAction = $getPrefixAction()) && (! $prefixAction->isHidden()))
{{ $prefixAction }}
@endif
@if ($icon = $getPrefixIcon())
<x-dynamic-component :component="$icon" class="w-5 h-5" />
@endif

@if ($label = $getPrefixLabel())
<span @class($affixLabelClasses)>
{{ $label }}
</span>
@endif
<div class="flex-1 relative">
<div class="bg-white dark:bg-gray-700 absolute right-1 px-2 top-1 bottom-1 flex items-center text-sm" @if($getCharacterLimit()) :class="{'text-danger-500': characterCount > {{ $getCharacterLimit() }}}" @endif>
<span x-text="characterCount"></span>@if($getCharacterLimit())/{{ $getCharacterLimit() }}@endif
</div>
<input
@keyup="characterCount = $event.target.value.length"
@unless ($hasMask())
x-data="{}"
{{ $applyStateBindingModifiers('wire:model') }}="{{ $getStatePath() }}"
type="{{ $getType() }}"
@else
x-data="textInputFormComponent({
{{ $hasMask() ? "getMaskOptionsUsing: (IMask) => ({$getJsonMaskConfiguration()})," : null }}
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')', lazilyEntangledModifiers: ['defer']) }},
})"
type="text"
wire:ignore
{!! $isLazy() ? "x-on:blur=\"\$wire.\$refresh\"" : null !!}
{!! $isDebounced() ? "x-on:input.debounce.{$getDebounce()}=\"\$wire.\$refresh\"" : null !!}
{{ $getExtraAlpineAttributeBag() }}
@endunless
dusk="filament.forms.{{ $getStatePath() }}"
{!! ($autocapitalize = $getAutocapitalize()) ? "autocapitalize=\"{$autocapitalize}\"" : null !!}
{!! ($autocomplete = $getAutocomplete()) ? "autocomplete=\"{$autocomplete}\"" : null !!}
{!! $isAutofocused() ? 'autofocus' : null !!}
{!! $isDisabled() ? 'disabled' : null !!}
id="{{ $getId() }}"
{!! ($inputMode = $getInputMode()) ? "inputmode=\"{$inputMode}\"" : null !!}
{!! $datalistOptions ? "list=\"{$getId()}-list\"" : null !!}
{!! ($placeholder = $getPlaceholder()) ? "placeholder=\"{$placeholder}\"" : null !!}
{!! ($interval = $getStep()) ? "step=\"{$interval}\"" : null !!}
@if (! $isConcealed())
{!! filled($length = $getMaxLength()) ? "maxlength=\"{$length}\"" : null !!}
{!! filled($value = $getMaxValue()) ? "max=\"{$value}\"" : null !!}
{!! filled($length = $getMinLength()) ? "minlength=\"{$length}\"" : null !!}
{!! filled($value = $getMinValue()) ? "min=\"{$value}\"" : null !!}
{!! $isRequired() ? 'required' : null !!}
@endif
{{ $getExtraInputAttributeBag()->class([
'block w-full transition duration-75 rounded-lg shadow-sm focus:border-primary-500 focus:ring-1 focus:ring-inset focus:ring-primary-500 disabled:opacity-70',
'dark:bg-gray-700 dark:text-white dark:focus:border-primary-500' => config('forms.dark_mode'),
]) }}
x-bind:class="{
'border-gray-300': ! (@js($getStatePath()) in $wire.__instance.serverMemo.errors),
'dark:border-gray-600': ! (@js($getStatePath()) in $wire.__instance.serverMemo.errors) && @js(config('forms.dark_mode')),
'border-danger-600 ring-danger-600': (@js($getStatePath()) in $wire.__instance.serverMemo.errors),
'dark:border-danger-400 dark:ring-danger-400': (@js($getStatePath()) in $wire.__instance.serverMemo.errors) && @js(config('forms.dark_mode')),
}"
/>
</div>

@if ($label = $getSuffixLabel())
<span @class($affixLabelClasses)>
{{ $label }}
</span>
@endif

@if ($icon = $getSuffixIcon())
<x-dynamic-component :component="$icon" class="w-5 h-5" />
@endif

@if (($suffixAction = $getSuffixAction()) && (! $suffixAction->isHidden()))
{{ $suffixAction }}
@endif
</div>

@if ($datalistOptions)
<datalist id="{{ $getId() }}-list">
@foreach ($datalistOptions as $option)
<option value="{{ $option }}" />
@endforeach
</datalist>
@endif
</x-dynamic-component>
57 changes: 57 additions & 0 deletions resources/views/textarea.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<x-dynamic-component
:component="$getFieldWrapperView()"
:id="$getId()"
:label="$getLabel()"
:label-sr-only="$isLabelHidden()"
:helper-text="$getHelperText()"
:hint="$getHint()"
:hint-action="$getHintAction()"
:hint-color="$getHintColor()"
:hint-icon="$getHintIcon()"
:required="$isRequired()"
:state-path="$getStatePath()"
>
<div class="relative overflow-hidden"
x-data="{characterLimit: {{ $getCharacterLimit() }}, characterCount: {{ strlen($getState()) }}}">
<div class="bg-white dark:bg-gray-700 absolute right-1 px-2 bottom-1 pb-1 text-sm" @if($getCharacterLimit()) :class="{'text-danger-500': characterCount > {{ $getCharacterLimit() }}}" @endif>
<span x-text="characterCount"></span>@if($getCharacterLimit())/{{ $getCharacterLimit() }}@endif
</div>

<textarea
@keyup="characterCount = $event.target.value.length"
{!! ($autocapitalize = $getAutocapitalize()) ? "autocapitalize=\"{$autocapitalize}\"" : null !!}
{!! ($autocomplete = $getAutocomplete()) ? "autocomplete=\"{$autocomplete}\"" : null !!}
{!! $isAutofocused() ? 'autofocus' : null !!}
{!! ($cols = $getCols()) ? "cols=\"{$cols}\"" : null !!}
{!! $isDisabled() ? 'disabled' : null !!}
id="{{ $getId() }}"
dusk="filament.forms.{{ $getStatePath() }}"
{!! ($placeholder = $getPlaceholder()) ? "placeholder=\"{$placeholder}\"" : null !!}
{!! ($rows = $getRows()) ? "rows=\"{$rows}\"" : null !!}
{{ $applyStateBindingModifiers('wire:model') }}="{{ $getStatePath() }}"
@if (! $isConcealed())
{!! filled($length = $getMaxLength()) ? "maxlength=\"{$length}\"" : null !!}
{!! filled($length = $getMinLength()) ? "minlength=\"{$length}\"" : null !!}
{!! $isRequired() ? 'required' : null !!}
@endif
{{
$attributes
->merge($getExtraAttributes())
->merge($getExtraInputAttributeBag()->getAttributes())
->class([
'filament-forms-textarea-component block w-full transition duration-75 rounded-lg shadow-sm focus:border-primary-500 focus:ring-1 focus:ring-inset focus:ring-primary-500 disabled:opacity-70',
'dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:focus:border-primary-500' => config('forms.dark_mode'),
'border-gray-300' => ! $errors->has($getStatePath()),
'border-danger-600 ring-danger-600' => $errors->has($getStatePath()),
'dark:border-danger-400 dark:ring-danger-400' => $errors->has($getStatePath()) && config('forms.dark_mode')
])
}}
@if ($shouldAutosize())
x-data="textareaFormComponent()"
x-on:input="render()"
style="height: 150px"
{{ $getExtraAlpineAttributeBag() }}
@endif
></textarea>
</div>
</x-dynamic-component>
Binary file added screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/FilamentCharCounterProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Wiebenieuwenhuis\FilamentCharCounter;

use Filament\PluginServiceProvider;
use Spatie\LaravelPackageTools\Package;

class FilamentCharCounterProvider extends PluginServiceProvider
{
public static string $name = 'filament-char-counter';

public function configurePackage(Package $package): void
{
$package->name(static::$name)->hasViews();
}
}
26 changes: 26 additions & 0 deletions src/TextInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Wiebenieuwenhuis\FilamentCharCounter;

use Filament\Forms\Components\TextInput as FilamentTextInput;

class TextInput extends FilamentTextInput
{
protected string $view = 'filament-char-counter::text-input';

protected $characterLimit = 0;

public function characterLimit(int $value): self
{
$this->characterLimit = $value;
return $this;
}

public function getCharacterLimit(): int
{
if($this->maxLength){
return $this->maxLength;
}
return $this->characterLimit;
}
}
26 changes: 26 additions & 0 deletions src/Textarea.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Wiebenieuwenhuis\FilamentCharCounter;

use Filament\Forms\Components\Textarea as FilamentTextarea;

class Textarea extends FilamentTextarea
{
protected string $view = 'filament-char-counter::textarea';

protected $characterLimit = 0;

public function characterLimit(int $value): self
{
$this->characterLimit = $value;
return $this;
}

public function getCharacterLimit(): int
{
if($this->maxLength){
return $this->maxLength;
}
return $this->characterLimit;
}
}

0 comments on commit ccca955

Please sign in to comment.