Skip to content

Commit

Permalink
filament v3
Browse files Browse the repository at this point in the history
  • Loading branch information
leandrocfe committed Aug 15, 2023
1 parent 501160f commit a769fd9
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 40 deletions.
77 changes: 37 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,97 +1,94 @@
# Brazilian pt-BR form fields.

This package provides custom form fields for [Filament](https://filamentphp.com/) (**>=v2.17.28**) that are commonly used in Brazilian web applications, such as CPF/CNPJ validation, phone number formatting, money with currency symbol, and CEP integration with [ViaCep](https://viacep.com.br).
This package provides custom form fields for [Filament](https://filamentphp.com/) that are commonly used in Brazilian web applications, such as CPF/CNPJ validation, phone number formatting, money with currency symbol, and CEP integration with [ViaCep](https://viacep.com.br).

This package uses [LaravelLegends/pt-br-validator](https://github.com/LaravelLegends/pt-br-validator) to validate Brazilian Portuguese fields.

![image demo](https://raw.githubusercontent.com/leandrocfe/filament-ptbr-form-fields/develop/screenshots/v1-example.png)
![image demo](https://raw.githubusercontent.com/leandrocfe/filament-ptbr-form-fields/develop/screenshots/v3-example.png)

## Installation

You can install the package via Composer:

```bash
composer require leandrocfe/filament-ptbr-form-fields
composer require leandrocfe/filament-ptbr-form-fields:"^3.0"
```

### Filament V2 - if you are using Filament v2.x, you can use [this section](https://github.com/leandrocfe/filament-ptbr-form-fields/tree/2.0.0)

## Usage

### CPF / CNPJ

To create a dynamic input that accepts either CPF or CNPJ, use:

```php
use Leandrocfe\FilamentPtbrFormFields\PtbrCpfCnpj;
PtbrCpfCnpj::make('cpf_or_cnpj')
```

You can also add validation to this field by chaining the rule() method:

```php
PtbrCpfCnpj::make('cpf_or_cnpj')
->rule('cpf_ou_cnpj')
use Leandrocfe\FilamentPtbrFormFields\Document;
Document::make('cpf_or_cnpj')
->dynamic()
```

If you want to create an input that only accepts CPF or only accepts CNPJ, use:

```php
//CPF
PtbrCpfCnpj::make('cpf')
Document::make('cpf')
->cpf()
```

```php
//CNPJ
PtbrCpfCnpj::make('cnpj')
Document::make('cnpj')
->cnpj()
```

You can also add validation to these fields as well:
If you want to use a custom mask for the input, use the cpf() or cnpj() method with a string argument representing the desired mask:

```php
//CPF with validation
PtbrCpfCnpj::make('cpf')
->cpf()
->rule('cpf')
Document::make('cpf')
->cpf('999999999-99')
```

```php
//CNPJ with validation
PtbrCpfCnpj::make('cnpj')
->cnpj()
->rule('cnpj')
Document::make('cnpj')
->cnpj('99999999/9999-57')
```

If you want to use a custom mask for the input, use the cpf() or cnpj() method with a string argument representing the desired mask:
### Validation
`Document` uses [LaravelLegends/pt-br-validator](https://github.com/LaravelLegends/pt-br-validator) to validate Brazilian Portuguese fields by default (`cpf_ou_cnpj`, `cpf`, `cnpj`)

You can disable validation using the `validation(false)` method:

```php
PtbrCpfCnpj::make('cpf')
->cpf('999999999-99')
Document::make('cpf_or_cnpj')
->validation(false)
->dynamic()
```

```php
PtbrCpfCnpj::make('cnpj')
->cnpj('99999999/9999-57')
Document::make('cpf')
->validation(false)
->cpf()
```

### Phone number

To create a dynamic input that formats phone numbers with DDD, use:

```php
use Leandrocfe\FilamentPtbrFormFields\PtbrPhone;
PtbrPhone::make('phone_number')
use Leandrocfe\FilamentPtbrFormFields\PhoneNumber;
PhoneNumber::make('phone_number')
```

If you want to use a custom phone number format, use the format() method with a string argument representing the desired format:

```php
PtbrPhone::make('phone_number')
PhoneNumber::make('phone_number')
->format('99999-9999')
```

```php
PtbrPhone::make('phone_number')
PhoneNumber::make('phone_number')
->format('(+99)(99)99999-9999')
```

Expand All @@ -100,28 +97,28 @@ PtbrPhone::make('phone_number')
To create a money input with the Brazilian currency symbol as the prefix, use:

```php
use Leandrocfe\FilamentPtbrFormFields\PtbrMoney;
PtbrMoney::make('price')
use Leandrocfe\FilamentPtbrFormFields\Money;
Money::make('price')
```

If you want to remove the prefix, use the prefix() method with a null argument:

```php
PtbrMoney::make('price')
Money::make('price')
->prefix(null)
```

By default, the mask is removed from the input when it is submitted. If you want to keep the mask, use the dehydrateMask() method with a false argument:

```php
PtbrMoney::make('price')
Money::make('price')
->dehydrateMask(false)
```

The initial value of the input is '0,00'. If you want to change the initial value, use the initialValue() method with a string argument:

```php
PtbrMoney::make('price')
Money::make('price')
->initialValue(null)
```

Expand All @@ -130,9 +127,9 @@ PtbrMoney::make('price')
To integrate with the ViaCep API for CEP validation and address autofill, use:

```php
use Leandrocfe\FilamentPtbrFormFields\PtbrCep;
use Leandrocfe\FilamentPtbrFormFields\Cep;
use Filament\Forms\Components\TextInput;
PtbrCep::make('postal_code')
Cep::make('postal_code')
->viaCep(
mode: 'suffix', // Determines whether the action should be appended to (suffix) or prepended to (prefix) the cep field, or not included at all (none).
errorMessage: 'CEP inválido.', // Error message to display if the CEP is invalid.
Expand Down Expand Up @@ -160,7 +157,7 @@ TextInput::make('city'),
TextInput::make('state'),
```

The mode parameter specifies whether the search action should be appended to or prepended to the CEP field, using the values suffix or prefix. Alternatively, you can use the none value with the ->lazy() method to indicate that the other address fields will be automatically filled only when the CEP field loses focus.
The mode parameter specifies whether the search action should be appended to or prepended to the CEP field, using the values suffix or prefix. Alternatively, you can use the none value with the `->live(onBlur: true)` method to indicate that the other address fields will be automatically filled only when the CEP field loses focus.

## Testing

Expand Down
Binary file added screenshots/v3-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions src/Cep.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Leandrocfe\FilamentPtbrFormFields;

use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Set;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
use Livewire\Component as Livewire;

class Cep extends TextInput
{
public function viaCep(string $mode = 'suffix', string $errorMessage = 'CEP inválido.', array $setFields = []): static
{
$viaCepRequest = function ($state, $livewire, $set, $component, $errorMessage, array $setFields) {

$livewire->validateOnly($component->getKey());

$request = Http::get("viacep.com.br/ws/$state/json/")->json();

foreach ($setFields as $key => $value) {
$set($key, $request[$value] ?? null);
}

if (blank($request) || Arr::has($request, 'erro')) {
throw ValidationException::withMessages([
$component->getKey() => $errorMessage,
]);
}
};

$this
->minLength(9)
->mask('99999-999')
->afterStateUpdated(function ($state, Livewire $livewire, Set $set, Component $component) use ($errorMessage, $setFields, $viaCepRequest) {
$viaCepRequest($state, $livewire, $set, $component, $errorMessage, $setFields);
})
->suffixAction(function () use ($mode, $errorMessage, $setFields, $viaCepRequest) {
if ($mode === 'suffix') {
return Action::make('search-action')
->label('Buscar CEP')
->icon('heroicon-o-magnifying-glass')
->action(function ($state, Livewire $livewire, Set $set, Component $component) use ($errorMessage, $setFields, $viaCepRequest) {
$viaCepRequest($state, $livewire, $set, $component, $errorMessage, $setFields);
})
->cancelParentActions();
}
})
->prefixAction(function () use ($mode, $errorMessage, $setFields, $viaCepRequest) {
if ($mode === 'prefix') {
return Action::make('search-action')
->label('Buscar CEP')
->icon('heroicon-o-magnifying-glass')
->action(function ($state, Livewire $livewire, Set $set, Component $component) use ($errorMessage, $setFields, $viaCepRequest) {
$viaCepRequest($state, $livewire, $set, $component, $errorMessage, $setFields);
})
->cancelParentActions();
}
});

return $this;
}
}
63 changes: 63 additions & 0 deletions src/Document.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Leandrocfe\FilamentPtbrFormFields;

use Closure;
use Filament\Forms\Components\TextInput;
use Filament\Support\RawJs;

class Document extends TextInput
{
public bool $validation = true;

public function dynamic(bool $condition = true): static
{
if (self::getValidation()) {
$this->rule('cpf_ou_cnpj');
}

if ($condition) {
$this->mask(RawJs::make(<<<'JS'
$input.length > 14 ? '99.999.999/9999-99' : '999.999.999-99'
JS))->minLength(14);
}

return $this;
}

public function cpf(string|Closure $format = '999.999.999-99'): static
{
$this->dynamic(false)
->mask($format);

if (self::getValidation()) {
$this->rule('cpf');
}

return $this;
}

public function cnpj(string|Closure $format = '99.999.999/9999-99'): static
{
$this->dynamic(false)
->mask($format);

if (self::getValidation()) {
$this->rule('cnpj');
}

return $this;
}

public function validation(bool|Closure $condition = true): static
{
$this->validation = $condition;

return $this;
}

public function getValidation(): bool
{
return $this->validation;
}
}
71 changes: 71 additions & 0 deletions src/Money.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace Leandrocfe\FilamentPtbrFormFields;

use Closure;
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Str;

class Money extends TextInput
{
protected string|int|float|null $initialValue = '0,00';

protected function setUp(): void
{
$this
->prefix('R$')
->maxLength(13)
->extraAlpineAttributes([

'x-on:keypress' => 'function() {
var charCode = event.keyCode || event.which;
if (charCode < 48 || charCode > 57) {
event.preventDefault();
return false;
}
return true;
}',

'x-on:keyup' => 'function() {
var money = $el.value.replace(/\D/g, "");
money = (money / 100).toFixed(2) + "";
money = money.replace(".", ",");
money = money.replace(/(\d)(\d{3})(\d{3}),/g, "$1.$2.$3,");
money = money.replace(/(\d)(\d{3}),/g, "$1.$2,");
$el.value = money;
}',
])
->dehydrateMask()
->default(0.00)
->formatStateUsing(fn ($state) => $state ? number_format(floatval($state), 2, ',', '.') : $this->initialValue);
}

public function dehydrateMask(bool|Closure $condition = true): static
{

if ($condition) {
$this->dehydrateStateUsing(
fn ($state): ?float => $state ?
floatval(
Str::of($state)
->replace('.', '')
->replace(',', '.')
->toString()
) * 10 :
null
);
} else {
$this->dehydrateStateUsing(null);
}

return $this;
}

public function initialValue(null|string|int|float|Closure $value = '0,00'): static
{
$this->initialValue = $value;

return $this;
}
}
Loading

0 comments on commit a769fd9

Please sign in to comment.