Skip to content

Commit

Permalink
Make toolbar sticky
Browse files Browse the repository at this point in the history
  • Loading branch information
rawilk committed Dec 20, 2023
1 parent b821004 commit 27a5ca0
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 27 deletions.
13 changes: 13 additions & 0 deletions resources/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@
.ql-snow .ql-picker.ql-placeholders .ql-picker-item:not([data-value]) {
@apply hidden;
}

.ql-snow.ql-toolbar {
@apply bg-white
dark:bg-white/5
rounded-t-lg;

&.sticky {
@apply z-[100]
rounded-t-none
shadow-md;
top: var(--sticky-offset, theme('spacing.12'));
}
}
2 changes: 1 addition & 1 deletion resources/dist/filament-quill.css

Large diffs are not rendered by default.

52 changes: 26 additions & 26 deletions resources/dist/filament-quill.js

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions resources/js/quill.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getLabel,
isFunction,
getImageUrls,
stickyObserver,
} from './utils.js';
import ImageUploader from './custom-handlers/image-uploader';
import InsertBr from './custom-handlers/insert-br';
Expand All @@ -27,6 +28,7 @@ export default function quill({
hasHistory = false,
onTextChangedHandler = undefined,
onInit = undefined,
stickyToolbar = false,
}) {
return {
state,
Expand All @@ -39,8 +41,10 @@ export default function quill({
hasHistory,
onTextChangedHandler,
onInit,
stickyToolbar,
editorInstance: undefined,
labelClickHandler: undefined,
stickyObserverInstance: undefined,

init() {
// Setting a small timeout so the icons aren't flashing as huge elements
Expand All @@ -62,13 +66,25 @@ export default function quill({
this.labelClickHandler = () => this.focus();

getLabel(this.statePath)?.addEventListener('click', this.labelClickHandler);

if (this.stickyToolbar) {
this.stickyObserverInstance = stickyObserver(
this.$refs.stickyToolbar,
this.$refs.toolbar,
'.fi-topbar.sticky',
);
}
},

destroy() {
getStyle(this.statePath)?.remove();
getLabel(this.statePath)?.removeEventListener('click', this.labelClickHandler);

this.editorInstance = this.$root._editor = undefined;

if (this.stickyObserverInstance) {
this.stickyObserverInstance.disconnect();
}
},

initEditor(content) {
Expand All @@ -81,6 +97,7 @@ export default function quill({

_this.$root._editor = _this.editorInstance = new Quill(_this.$refs.quill, {
theme: _this.options.theme,
scrollingContainer: 'html',
placeholder: _this.placeholder,
modules: _this.getModules(),
});
Expand Down
25 changes: 25 additions & 0 deletions resources/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,28 @@ export const getStyle = editorId => document.head.querySelector(`style#quill--${
export const getLabel = editorId => document.querySelector(`label[for="${editorId}"]`);

export const getImageUrls = delta => delta.ops.filter(op => op.insert && op.insert.image).map(op => op.insert.image);

export const stickyObserver = (anchor, el, topbarSelector) => {
if (! anchor) {
return;
}

const topbar = document.querySelector(topbarSelector);
const offsetHeight = topbar?.offsetHeight ?? 0;

const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el?.classList.remove('sticky');
el?.style.removeProperty('--sticky-offset');
} else {
el?.classList.add('sticky');
el?.style.setProperty('--sticky-offset', `${offsetHeight + 2}px`);
}
}, { threshold: .25, rootMargin: `${offsetHeight}px` });
});

observer.observe(anchor);

return observer;
};
8 changes: 8 additions & 0 deletions resources/views/partials/toolbar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
$placeholders = $getPlaceholders();
@endphp

@if ($hasStickyToolbar)
{{--
To prevent the "sticky" class being added/removed rapidly with the intersection
observer, we're going to attach the observer to a dummy element instead.
--}}
<div x-ref="stickyToolbar" class="w-0 h-0" wire:ignore></div>
@endif

<div
id="quill-toolbar-{{ $id }}"
x-ref="toolbar"
Expand Down
2 changes: 2 additions & 0 deletions resources/views/quill-editor.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
$textChangeHandler = $getOnTextChangeHandler();
$onInitCallback = $getOnInitCallback();
$hasHistory = $hasToolbarButton([\Rawilk\FilamentQuill\Enums\ToolbarButton::Undo, \Rawilk\FilamentQuill\Enums\ToolbarButton::Redo]);
$hasStickyToolbar = $hasStickyToolbar();
// To make our `prefer-lowest` tests pass, we're checking if the panel has `spa` mode enabled
// here like this instead, since some earlier versions of filament 3.0 don't appear
Expand Down Expand Up @@ -78,6 +79,7 @@ class="fi-quill fi-disabled quill-content prose max-w-none dark:prose-invert blo
wireId: @js($this->getId()),
allowImages: @js($hasToolbarButton(\Rawilk\FilamentQuill\Enums\ToolbarButton::Image)),
hasHistory: @js($hasHistory),
stickyToolbar: @js($hasStickyToolbar),
})"
@if ($hasHistory)
x-on:quill-history-clear.window="clearHistory"
Expand Down
14 changes: 14 additions & 0 deletions src/Filament/Forms/Components/QuillEditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class QuillEditor extends Field implements CanBeLengthConstrained, HasFileAttach

protected ?array $cachedToolbarButtons = null;

protected bool|Closure|null $stickyToolbar = true;

// Default toolbar
protected array|Closure $toolbarButtons = [
ToolbarButton::Font,
Expand Down Expand Up @@ -79,6 +81,13 @@ public function shouldLoadStyles(): bool
return $this->shouldLoadStyles ?? config('filament-quill.load_styles', true);
}

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

return $this;
}

public function getMinHeight(): ?string
{
return $this->evaluate($this->minHeight);
Expand Down Expand Up @@ -136,4 +145,9 @@ public function hasToolbarButton(string|array|ToolbarButton $button): bool

return in_array($buttonValue, $this->getToolbarButtons(), true);
}

public function hasStickyToolbar(): bool
{
return $this->evaluate($this->stickyToolbar) === true;
}
}

0 comments on commit 27a5ca0

Please sign in to comment.