Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rawilk committed Oct 18, 2023
1 parent 3560c4b commit f918024
Show file tree
Hide file tree
Showing 26 changed files with 1,999 additions and 28 deletions.
3 changes: 3 additions & 0 deletions bin/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

npm run build
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"orchestra/testbench": "^8.8",
"pestphp/pest": "^2.20",
"pestphp/pest-plugin-laravel": "^2.2",
"pestphp/pest-plugin-livewire": "^2.1",
"sinnbeck/laravel-dom-assertions": "^1.3",
"spatie/laravel-ray": "^1.31"
},
"autoload": {
Expand Down
975 changes: 975 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "filament-password-input",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "npx tailwindcss -i resources/css/app.css -o resources/dist/app.css --watch",
"build": "npx tailwindcss -i resources/css/app.css -o resources/dist/app.css --minify"
},
"devDependencies": {
"tailwindcss": "^3.3.3"
}
}
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<php>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="base64:uX1uJ9x7RpEoPz8U52f4tuuUMWxt3/36N8WIuFrKZFk="/>
<env name="RAY_ENABLED" value="(true)"/>
<env name="SEND_CACHE_TO_RAY" value="(false)"/>
<env name="SEND_DUMPS_TO_RAY" value="(true)"/>
Expand Down
1 change: 1 addition & 0 deletions resources/css/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@tailwind utilities;
1 change: 1 addition & 0 deletions resources/dist/app.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions resources/lang/en/password.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

return [

'actions' => [

'copy' => [
'tooltip' => 'Copy to clipboard',
],

'regenerate' => [
'tooltip' => 'Generate new password',
'success_message' => 'New password was generated!',
],

'reveal' => [
'show' => 'Show password',
'hide' => 'Hide password',
],

],

];
Empty file removed resources/views/.gitkeep
Empty file.
133 changes: 133 additions & 0 deletions resources/views/password.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
@php
$datalistOptions = $getDatalistOptions();
$extraAlpineAttributes = $getExtraAlpineAttributes();
$id = $getId();
$isDisabled = $isDisabled();
$isReadOnly = $isReadOnly();
$isConcealed = $isConcealed();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$mask = $getMask();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixLabel = $getPrefixLabel();
$suffixIcon = $getSuffixIcon();
$suffixLabel = $getSuffixLabel();
$suffixActions = $getSuffixActions();
$statePath = $getStatePath();
$isPasswordRevealable = $isPasswordRevealable();
$isPasswordInitiallyHidden = $isPasswordInitiallyHidden();
$shouldHidePasswordManagerIcons = $shouldHidePasswordManagerIcons();
$inputTypeToggle = $isPasswordRevealable
? [':type' => 'show ? \'text\' : \'password\'']
: [];
$extraAlpineAttributes = [...$extraAlpineAttributes, ...$inputTypeToggle];
$hasInlineSuffix = $isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel));
@endphp

<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div
x-data="{
@if ($isPasswordRevealable)
show: @js(! $isPasswordInitiallyHidden),
revealMessage: @js($getShowPasswordText()),
maskMessage: @js($getHidePasswordText()),
@endif
}"
>
<x-filament::input.wrapper
:disabled="$isDisabled"
:inline-prefix="$isPrefixInline"
:inline-suffix="$isSuffixInline"
:prefix="$prefixLabel"
:prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon"
:suffix="$suffixLabel"
:suffix-icon="$suffixIcon"
:suffix-actions="$suffixActions"
:valid="! $errors->has($statePath)"
class="fi-fo-text-input fi-pw-input-wrapper"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['overflow-hidden'])
"
>
<div @class([
'flex relative' => $isPasswordRevealable,
])>
<x-filament::input
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->merge($extraAlpineAttributes, escape: false)
->merge([
'class' => 'fi-pw-input',
'autocomplete' => $getAutocomplete(),
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled,
'id' => $id,
'inlinePrefix' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'inlineSuffix' => $hasInlineSuffix,
'inputmode' => $getInputMode(),
'list' => $datalistOptions ? $id . '-list' : null,
'maxlength' => (! $isConcealed) ? $getMaxLength() : null,
'minlength' => (! $isConcealed) ? $getMinLength() : null,
'placeholder' => $getPlaceholder(),
'readonly' => $isReadOnly,
'required' => $isRequired() && (! $isConcealed),
$applyStateBindingModifiers('wire:model') => $statePath,
'type' => $isPasswordRevealable ? null : 'password',
'data-1p-ignore' => $shouldHidePasswordManagerIcons ? '' : null,
'data-lpignore' => $shouldHidePasswordManagerIcons ? 'true' : null,
'x-data' => (count($extraAlpineAttributes) || filled($mask)) ? '{}' : null,
'x-mask' . ($mask instanceof \Filament\Support\RawJs ? ':dynamic' : '') => filled($mask) ? $mask : null,
], escape: false)
"
/>

@if ($isPasswordRevealable && ! $isDisabled)
<button
type="button"
@click="show = ! show"
@class([
'fi-pw-reveal-button',
'text-gray-500 dark:text-gray-400',
'px-1.5' => $hasInlineSuffix,
'ltr:mr-2.5 rtl:ml-2.5' => ! $hasInlineSuffix,
])
x-cloak
x-tooltip="{
content: show ? maskMessage : revealMessage,
theme: $store.theme,
}"
>
<span class="sr-only" x-text="show ? maskMessage : revealMessage"></span>

<x-filament::icon
:icon="$getShowPasswordIcon()"
class="fi-pw-reveal-icon h-5 w-5"
x-show="! show"
/>

<x-filament::icon
:icon="$getHidePasswordIcon()"
class="fi-pw-reveal-icon h-5 w-5"
x-show="show"
/>
</button>
@endif
</div>
</x-filament::input.wrapper>

@if ($datalistOptions)
<datalist id="{{ $id }}-list">
@foreach ($datalistOptions as $option)
<option value="{{ $option }}" />
@endforeach
</datalist>
@endif
</div>
</x-dynamic-component>
132 changes: 132 additions & 0 deletions src/Concerns/CanCopyToClipboard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

declare(strict_types=1);

namespace Rawilk\FilamentPasswordInput\Concerns;

use Closure;
use Filament\Forms\Components\Actions\Action;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Js;

/**
* This will behave very similar to filament's CanBeCopied trait.
*
* Big difference is we determine the state from the input's current
* value instead of defining it with php.
*/
trait CanCopyToClipboard
{
protected bool|Closure $isCopyable = false;

protected string|Closure|null $copyMessage = null;

protected int|Closure|null $copyMessageDuration = null;

protected string|Closure|null $copyIcon = null;

protected string|Closure|array|null $copyIconColor = null;

protected string|Closure|null $copyTooltip = null;

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

return $this;
}

public function copyMessage(string|Closure|null $message): static
{
$this->copyMessage = $message;

return $this;
}

public function copyMessageDuration(int|Closure|null $duration): static
{
$this->copyMessageDuration = $duration;

return $this;
}

public function copyIcon(string|Closure|null $icon): static
{
$this->copyIcon = $icon;

return $this;
}

public function copyIconColor(string|Closure|array|null $color): static
{
$this->copyIconColor = $color;

return $this;
}

public function copyTooltip(string|Closure|null $tooltip): static
{
$this->copyTooltip = $tooltip;

return $this;
}

public function isCopyable(): bool
{
return (bool) $this->evaluate($this->isCopyable);
}

public function getCopyMessage(): string
{
return $this->evaluate($this->copyMessage) ?? __('filament::components/copyable.messages.copied');
}

public function getCopyMessageDuration(): int
{
return $this->evaluate($this->copyMessageDuration) ?? 2000;
}

public function getCopyIcon(): string
{
return $this->evaluate($this->copyIcon) ?? 'heroicon-m-clipboard';
}

public function getCopyIconColor(): string|array|null
{
return $this->evaluate($this->copyIconColor);
}

public function getCopyTooltip(): string
{
return $this->evaluate($this->copyTooltip) ?? __('filament-password-input::password.actions.copy.tooltip');
}

public function getCopyToClipboardAction(): Action
{
$copyDuration = Js::from($this->getCopyMessageDuration());
$copyMessage = Js::from($this->getCopyMessage());
$tooltip = $this->getCopyTooltip();

$action = Action::make('copyToClipboard')
->livewireClickHandlerEnabled(false)
->icon($this->getCopyIcon())
->iconButton()
->tooltip($tooltip)
->label($tooltip)
->extraAttributes([
'x-on:click' => new HtmlString(<<<JS
const text = \$wire.get('{$this->getStatePath()}');
window.navigator.clipboard.writeText(text);
\$tooltip({$copyMessage}, { theme: \$store.theme, timeout: {$copyDuration} });
JS),
// IMO it's weird when the title and tooltip show at the same time...
'title' => '',
]);

if ($color = $this->getCopyIconColor()) {
$action->color($color);
}

return $action;
}
}
Loading

0 comments on commit f918024

Please sign in to comment.