├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── config └── filament-copyable.php ├── copyable-text-field.PNG ├── dist └── filament-copyable.js ├── filament-copyable-column.PNG ├── mix-manifest.json ├── package.json ├── resources ├── js │ └── filament-copyable.js └── views │ ├── columns │ └── copyable-text-column.blade.php │ └── forms │ └── components │ └── copyable-text-input.blade.php ├── src ├── FilamentCopyableProvider.php ├── Forms │ └── Components │ │ └── CopyableTextInput.php └── Tables │ └── Columns │ └── CopyableTextColumn.php └── webpack.mix.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .php_cs 3 | .php_cs.cache 4 | .phpunit.result.cache 5 | build 6 | composer.lock 7 | coverage 8 | docs 9 | package-lock.json 10 | phpunit.xml 11 | phpstan.neon 12 | testbench.yaml 13 | vendor 14 | node_modules 15 | .php-cs-fixer.cache -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) Saad Nasir Siddique 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filament Copyable 2 | 3 | Copyable Text Column and Field for Filament PHP. 4 | 5 | ![Screenshot of Login](./filament-copyable-column.PNG) 6 | ![Screenshot of Login](./copyable-text-field.PNG) 7 | 8 | ## Installation 9 | 10 | You can install the package via composer: 11 | 12 | ```bash 13 | composer require saadj55/filament-copyable 14 | ``` 15 | ## Usage 16 | ### Column 17 | In in your Table Schema: 18 | 19 | ```php 20 | 21 | \Saadj55\FilamentCopyable\Tables\Columns\CopyableTextColumn::make('name') 22 | 23 | ``` 24 | You can make the icon to only appear on hover with the `->showOnHover()` method, 25 | 26 | You can set a custom [heroicon](https://heroicons.com/) by using the `->icon('heroicon-o-duplicate')` method. 27 | 28 | ### Field 29 | 30 | ```php 31 | 32 | \Saadj55\FilamentCopyable\Forms\Components\CopyableTextInput::make('name') 33 | 34 | ``` 35 | ## License 36 | 37 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saadj55/filament-copyable", 3 | "description": "Filament fields and columns with copyable values", 4 | "autoload": { 5 | "psr-4": { 6 | "Saadj55\\FilamentCopyable\\": "src/" 7 | } 8 | }, 9 | "version": "0.1.4", 10 | "authors": [ 11 | { 12 | "name": "Saad Nasir", 13 | "email": "saadj55@gmail.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.0", 18 | "filament/filament": "^2.9", 19 | "spatie/laravel-package-tools": "^1.10" 20 | }, 21 | "minimum-stability": "dev", 22 | "config": { 23 | "sort-packages": true 24 | }, 25 | "extra": { 26 | "laravel": { 27 | "providers": [ 28 | "Saadj55\\FilamentCopyable\\FilamentCopyableProvider" 29 | ] 30 | } 31 | }, 32 | "prefer-stable": true 33 | } 34 | -------------------------------------------------------------------------------- /config/filament-copyable.php: -------------------------------------------------------------------------------- 1 | { // webpackBootstrap 2 | /******/ "use strict"; 3 | /******/ var __webpack_modules__ = ({ 4 | 5 | /***/ "./node_modules/@ryangjchandler/alpine-clipboard/src/index.js": 6 | /*!********************************************************************!*\ 7 | !*** ./node_modules/@ryangjchandler/alpine-clipboard/src/index.js ***! 8 | \********************************************************************/ 9 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 10 | 11 | __webpack_require__.r(__webpack_exports__); 12 | /* harmony export */ __webpack_require__.d(__webpack_exports__, { 13 | /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) 14 | /* harmony export */ }); 15 | let onCopy = () => {} 16 | 17 | const copy = (target) => { 18 | if (typeof target === 'function') { 19 | target = target() 20 | } 21 | 22 | if (typeof target === 'object') { 23 | target = JSON.stringify(target) 24 | } 25 | 26 | return window.navigator.clipboard.writeText(target) 27 | .then(onCopy) 28 | } 29 | 30 | function Clipboard(Alpine) { 31 | Alpine.magic('clipboard', () => { 32 | return copy 33 | }) 34 | 35 | Alpine.directive('clipboard', (el, { modifiers, expression }, { evaluateLater, cleanup }) => { 36 | const getCopyContent = modifiers.includes('raw') ? c => c(expression) : evaluateLater(expression) 37 | const clickHandler = () => getCopyContent(copy) 38 | 39 | el.addEventListener('click', clickHandler) 40 | 41 | cleanup(() => { 42 | el.removeEventListener('click', clickHandler) 43 | }) 44 | }) 45 | } 46 | 47 | Clipboard.configure = (config) => { 48 | if (config.hasOwnProperty('onCopy') && typeof config.onCopy === 'function') { 49 | onCopy = config.onCopy 50 | } 51 | 52 | return Clipboard 53 | } 54 | 55 | /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Clipboard); 56 | 57 | /***/ }) 58 | 59 | /******/ }); 60 | /************************************************************************/ 61 | /******/ // The module cache 62 | /******/ var __webpack_module_cache__ = {}; 63 | /******/ 64 | /******/ // The require function 65 | /******/ function __webpack_require__(moduleId) { 66 | /******/ // Check if module is in cache 67 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 68 | /******/ if (cachedModule !== undefined) { 69 | /******/ return cachedModule.exports; 70 | /******/ } 71 | /******/ // Create a new module (and put it into the cache) 72 | /******/ var module = __webpack_module_cache__[moduleId] = { 73 | /******/ // no module.id needed 74 | /******/ // no module.loaded needed 75 | /******/ exports: {} 76 | /******/ }; 77 | /******/ 78 | /******/ // Execute the module function 79 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 80 | /******/ 81 | /******/ // Return the exports of the module 82 | /******/ return module.exports; 83 | /******/ } 84 | /******/ 85 | /************************************************************************/ 86 | /******/ /* webpack/runtime/define property getters */ 87 | /******/ (() => { 88 | /******/ // define getter functions for harmony exports 89 | /******/ __webpack_require__.d = (exports, definition) => { 90 | /******/ for(var key in definition) { 91 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 92 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 93 | /******/ } 94 | /******/ } 95 | /******/ }; 96 | /******/ })(); 97 | /******/ 98 | /******/ /* webpack/runtime/hasOwnProperty shorthand */ 99 | /******/ (() => { 100 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 101 | /******/ })(); 102 | /******/ 103 | /******/ /* webpack/runtime/make namespace object */ 104 | /******/ (() => { 105 | /******/ // define __esModule on exports 106 | /******/ __webpack_require__.r = (exports) => { 107 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 108 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 109 | /******/ } 110 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 111 | /******/ }; 112 | /******/ })(); 113 | /******/ 114 | /************************************************************************/ 115 | var __webpack_exports__ = {}; 116 | // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. 117 | (() => { 118 | /*!*******************************************!*\ 119 | !*** ./resources/js/filament-copyable.js ***! 120 | \*******************************************/ 121 | __webpack_require__.r(__webpack_exports__); 122 | /* harmony import */ var _ryangjchandler_alpine_clipboard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ryangjchandler/alpine-clipboard */ "./node_modules/@ryangjchandler/alpine-clipboard/src/index.js"); 123 | 124 | document.addEventListener('alpine:init', function () { 125 | Alpine.plugin(_ryangjchandler_alpine_clipboard__WEBPACK_IMPORTED_MODULE_0__["default"]); 126 | }); 127 | })(); 128 | 129 | /******/ })() 130 | ; -------------------------------------------------------------------------------- /filament-copyable-column.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saadj55/filament-copyable/33b3c4b209bf9ae70eb971585545b0b4d7e6c02d/filament-copyable-column.PNG -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dist/filament-copyable.js": "/dist/filament-copyable.js" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "npm run development", 4 | "development": "mix", 5 | "watch": "mix watch", 6 | "watch-poll": "mix watch -- --watch-options-poll=1000", 7 | "hot": "mix watch --hot", 8 | "prod": "npm run production", 9 | "production": "mix --production" 10 | }, 11 | "devDependencies": { 12 | "autoprefixer": "^10.4.7", 13 | "laravel-mix": "^6.0.49", 14 | "mix-tailwindcss": "^1.3.0", 15 | "postcss": "^8.4.14", 16 | "postcss-import": "^14.1.0", 17 | "tailwindcss": "^3.1.4" 18 | }, 19 | "dependencies": { 20 | "@ryangjchandler/alpine-clipboard": "^2.2.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/js/filament-copyable.js: -------------------------------------------------------------------------------- 1 | import Clipboard from "@ryangjchandler/alpine-clipboard" 2 | 3 | document.addEventListener('alpine:init', () => { 4 | Alpine.plugin(Clipboard); 5 | }) 6 | -------------------------------------------------------------------------------- /resources/views/columns/copyable-text-column.blade.php: -------------------------------------------------------------------------------- 1 |
merge($getExtraAttributes())->class([ 6 | 'px-4 py-3 filament-tables-text-column flex items-center gap-2', 7 | 'text-primary-600 transition hover:underline hover:text-primary-500 focus:underline focus:text-primary-500' => $getAction() || $getUrl(), 8 | 'whitespace-normal' => $canWrap(), 9 | ]) }} 10 | > 11 | {{ $getFormattedState() }} 12 | @if($getFormattedState()) 13 | 24 | @endif 25 |
-------------------------------------------------------------------------------- /resources/views/forms/components/copyable-text-input.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $datalistOptions = $getDatalistOptions(); 3 | 4 | $affixLabelClasses = [ 5 | 'whitespace-nowrap group-focus-within:text-primary-500', 6 | 'text-gray-400' => ! $errors->has($getStatePath()), 7 | 'text-danger-400' => $errors->has($getStatePath()), 8 | ]; 9 | @endphp 10 | 11 | 22 |
merge($getExtraAttributes())->class(['flex items-center space-x-2 rtl:space-x-reverse group filament-forms-text-input-component']) }}> 23 | @if (($prefixAction = $getPrefixAction()) && (! $prefixAction->isHidden())) 24 | {{ $prefixAction }} 25 | @endif 26 | 27 | @if ($icon = $getPrefixIcon()) 28 | 29 | @endif 30 | 31 | @if ($label = $getPrefixLabel()) 32 | 33 | {{ $label }} 34 | 35 | @endif 36 | 37 |
38 | ({$getJsonMaskConfiguration()})," : null }} 45 | state: $wire.{{ 46 | $isLazy() ? 47 | 'entangle(\'' . $getStatePath() . '\').defer' : 48 | $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') 49 | }}, 50 | })" 51 | type="text" 52 | wire:ignore 53 | @if ($isLazy()) x-on:blur="$wire.$refresh" @endif 54 | {{ $getExtraAlpineAttributeBag() }} 55 | @endunless 56 | dusk="filament.forms.{{ $getStatePath() }}" 57 | {!! ($autocapitalize = $getAutocapitalize()) ? "autocapitalize=\"{$autocapitalize}\"" : null !!} 58 | {!! ($autocomplete = $getAutocomplete()) ? "autocomplete=\"{$autocomplete}\"" : null !!} 59 | {!! $isAutofocused() ? 'autofocus' : null !!} 60 | {!! $isDisabled() ? 'disabled' : null !!} 61 | id="{{ $getId() }}" 62 | {!! ($inputMode = $getInputMode()) ? "inputmode=\"{$inputMode}\"" : null !!} 63 | {!! $datalistOptions ? "list=\"{$getId()}-list\"" : null !!} 64 | {!! ($placeholder = $getPlaceholder()) ? "placeholder=\"{$placeholder}\"" : null !!} 65 | {!! ($interval = $getStep()) ? "step=\"{$interval}\"" : null !!} 66 | @if (! $isConcealed()) 67 | {!! filled($length = $getMaxLength()) ? "maxlength=\"{$length}\"" : null !!} 68 | {!! filled($value = $getMaxValue()) ? "max=\"{$value}\"" : null !!} 69 | {!! filled($length = $getMinLength()) ? "minlength=\"{$length}\"" : null !!} 70 | {!! filled($value = $getMinValue()) ? "min=\"{$value}\"" : null !!} 71 | {!! $isRequired() ? 'required' : null !!} 72 | @endif 73 | {{ $getExtraInputAttributeBag()->class([ 74 | 'block w-full transition duration-75 rounded-lg shadow-sm focus:border-primary-600 focus:ring-1 focus:ring-inset focus:ring-primary-600 disabled:opacity-70', 75 | 'dark:bg-gray-700 dark:text-white dark:focus:border-primary-600' => config('forms.dark_mode'), 76 | 'border-gray-300' => ! $errors->has($getStatePath()), 77 | 'dark:border-gray-600' => (! $errors->has($getStatePath())) && config('forms.dark_mode'), 78 | 'border-danger-600 ring-danger-600' => $errors->has($getStatePath()), 79 | ]) }} 80 | /> 81 |
82 | 83 | @if ($label = $getSuffixLabel()) 84 | 85 | {{ $label }} 86 | 87 | @endif 88 | @if ($icon = $getSuffixIcon()) 89 | 90 | @endif 91 | 92 | @if (($suffixAction = $getSuffixAction()) && (! $suffixAction->isHidden())) 93 | {{ $suffixAction }} 94 | @endif 95 |
96 | 97 | @if ($datalistOptions) 98 | 99 | @foreach ($datalistOptions as $option) 100 | 103 | @endif 104 |
105 | -------------------------------------------------------------------------------- /src/FilamentCopyableProvider.php: -------------------------------------------------------------------------------- 1 | name(self::$name) 15 | ->hasConfigFile() 16 | ->hasViews(); 17 | } 18 | /** 19 | * @var string[] 20 | */ 21 | protected array $beforeCoreScripts = [ 22 | 'filament-copyable' => __DIR__ . '/../dist/filament-copyable.js', 23 | ]; 24 | 25 | public function boot(): void 26 | { 27 | parent::boot(); 28 | 29 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'filament-copyable'); 30 | $this->publishes([ 31 | __DIR__ . '/../config' => config_path(), 32 | ], 'filament-copyable-config'); 33 | 34 | } 35 | } -------------------------------------------------------------------------------- /src/Forms/Components/CopyableTextInput.php: -------------------------------------------------------------------------------- 1 | showOnHover = true; 17 | 18 | return $this; 19 | } 20 | 21 | /** 22 | * @return string 23 | */ 24 | public function isShowOnHover(): string 25 | { 26 | return $this->showOnHover; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | 15 | mix.setPublicPath('./dist').js('resources/js/filament-copyable.js','./dist/filament-copyable.js').version() 16 | --------------------------------------------------------------------------------