Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Button tooltips in the demo #650

Closed
benbro opened this issue May 4, 2016 · 16 comments
Closed

Button tooltips in the demo #650

benbro opened this issue May 4, 2016 · 16 comments
Labels

Comments

@benbro
Copy link
Contributor

benbro commented May 4, 2016

It'll be nice to have tooltips for the buttons in the new demos.

http://quilljs.com/
http://beta.quilljs.com/docs/formats/
http://beta.quilljs.com/docs/themes/snow/

Version: [version]
1.0-Beta.0

@alexkrolick
Copy link
Contributor

Even having title tags to get a label on hover would help quite a bit

@alexkrolick
Copy link
Contributor

Actually, it would be pretty nice to have this at v1.0. Some of our users could use a hint as to what the different toolbar items actually do - see GH toolbar:

screen shot 2016-08-22 at 12 19 32 pm

@JMacLulich
Copy link

Is it actually possible to get tooltips??

We've had the same request, it would be great to be able to attach tooltips to each of the tools in the toolbar.

@jhchen
Copy link
Member

jhchen commented Sep 21, 2016

There is now an example using bootstrap. I'm hesitant to add this Quill core since users are likely to be opinionated with UI and want to customize, which would add a lot of complexity and weight into Quill, and so is better handled by other libraries.

@jhchen jhchen closed this as completed Sep 21, 2016
@rquast
Copy link

rquast commented Nov 28, 2016

@jhchen You were right. This example tripped me up a bit when learning about quill. I just realized that the data-toggle is a bootstrap thing and you're passing in the html (with the tooltip data-toggle) rather than making quill generate it in the html.

To deal with not having to write custom html for the toolbar buttons, I did this:

    let showTooltip = (el) => {
      let tool = el.className.replace('ql-', '');
      if (tooltips[tool]) {
        console.log(tooltips[tool]);
        
        // TODO: add your tooltip code here.
      }
    };

    let tooltips = {
      'bold': 'Bold',
      'italic': 'Italic'
    };

    let toolbarElement = document.querySelector('.ql-toolbar');
    if (toolbarElement) {
      let matches = toolbarElement.querySelectorAll('button');
      for (let el of matches) {
        showTooltip(el);
      }
    }

@tunderdomb
Copy link

It would be nice to have it included. Even basic accessibility is better than nothing.
All it needs is an optional attribute on toolbar buttons, the rest could be handled by user style sheets.
Something like:

let toolbarOptions = [
    [
      { 'bold': { tooltip: "Bold (Ctrl+B)" } }, 'italic', 'underline', 'strike'
    ]
  ];

Generated via options:

<button type="button" class="ql-bold" data-tooltip="Bold (Ctrl+B)"></button>

User style sheet:

[data-tooltip] {
  content: attr(data-tooltip);
  display: none;
  // however people wanna style it..
}
[data-tooltip]:hover {
  display: block; // or something..
}

There's already an issue #1084 that touches on this, though that proposition is more generic.

@BeatriceThalo
Copy link

BeatriceThalo commented Nov 7, 2018

Any way to make Angular's transclusion and Angular Material (present in my project/component) work instead of Bootstrap?

<quill-editor [modules]="myQuillConfig" name="my-email-body" [(ngModel)]="myEmailBody">
  <ng-content select=".ql-clean" matTooltip="Remove Formatting from Selection" matTooltipPosition="above"></ng-content>
</quill-editor>

@baraniee
Copy link

baraniee commented May 13, 2019

Try adding like this

  let showTooltip = (el) => {
      let tool = el.className.replace('ql-', '');
    if (tooltips[tool]) {
      console.log(tooltips[tool]);
      $(el).attr("title",tooltips[tool]);
    }
  };
 let tooltips = {
    'bold': 'Bold (ctrl+B)',
    'italic': 'Italic (ctrl+I)',
    'underline': 'Underline (ctrl+U)',
    'strike': 'Strike'
  };

  let toolbarElement = document.querySelector('.ql-toolbar');
  if (toolbarElement) {
    let matches = toolbarElement.querySelectorAll('button');
    for (let el of matches) {
      showTooltip(el);
    }
  }

@fjrial
Copy link

fjrial commented Oct 11, 2019

Modified the previous suggestion to allow tooltips also for button with values and also pickers..

let toolbarTooltips = {
      'font': 'Select a font',
      'size': 'Select a font size',
      'header': 'Select the text style',
      'bold': 'Bold',
      'italic': 'Italic',
      'underline': 'Underline',
      'strike': 'Strikethrough',
      'color' : 'Select a text color',
      'background': 'Select a background color',
      'script': {
        'sub' : 'Subscript',
        'super': 'Superscript'
      },
      'list': {
        'ordered':'Numbered list',
        'bullet': 'Bulleted list'
      },
      'indent': {
        '-1': 'Decrease indent',
        '+1':  'Increase indent'
      },
      'direction': {
        'rtl': 'Text direction (right to left | left to right)',
        'ltr': 'Text direction (left ro right | right to left)'
      },
      'align': 'Text alignment',
      'link': 'Insert a link',
      'image': 'Insert an image',
      'formula': 'Insert a formula',
      'clean': 'Clear format',
      'add-table': 'Add a new table',
      'table-row': 'Add a row to the selected table',
      'table-column': 'Add a column to the selected table',
      'remove-table': 'Remove selected table',
      'help': 'Show help'
    };


    function showTooltips(){
      let showTooltip = (which,el) => {
        if (which=='button'){
          var tool = el.className.replace('ql-', '');
        }
        else if (which=='span'){
          var tool = el.className.replace('ql-','');
          tool=tool.substr(0,tool.indexOf(' '));
        }
        if (tool){
          //if element has value attribute.. handling is different
          //buttons without value
          if (el.value ==''){
            if (toolbarTooltips[tool])
              el.setAttribute('title',toolbarTooltips[tool]);
          }
          //buttons with value
          else if (typeof el.value !=='undefined'){
            if (toolbarTooltips[tool][el.value])
              el.setAttribute('title',toolbarTooltips[tool][el.value]);
          }
          //default
          else
            el.setAttribute('title',toolbarTooltips[tool]);
        }
      };

      let toolbarElement = document.querySelector('.ql-toolbar');
      if (toolbarElement) {
        let matchesButtons = toolbarElement.querySelectorAll('button');
        for (let el of matchesButtons) {
          showTooltip('button',el);
        }
        //for submenus inside 
        let matchesSpans = toolbarElement.querySelectorAll('.ql-toolbar > span > span');
        for (let el of matchesSpans) {
          showTooltip('span',el);
        }
      }
    }

@rbonomo
Copy link

rbonomo commented Mar 11, 2020

var toolbar = quill.container.previousSibling;
toolbar.querySelector('button.ql-bold').setAttribute('title', 'Bold');
toolbar.querySelector('button.ql-italic').setAttribute('title', 'Italic');
toolbar.querySelector('button.ql-list').setAttribute('title', 'List');
toolbar.querySelector('button.ql-link').setAttribute('title', 'Link');
toolbar.querySelector('button.ql-script').setAttribute('title', 'Superscript');
toolbar.querySelector('button.ql-clean').setAttribute('title', 'Clear Formatting');

@brankoiliccc
Copy link

brankoiliccc commented May 12, 2020

Modified previous to work with angular 9 and multi language

import { QUILL_TOOL_TIPS_SR } from '../../../../../locale/quill-tooltips_sr';
import { QUILL_TOOL_TIPS_EN } from '../../../../../locale/quill-tooltip_en';

export class myQuillComponent {

  private toolbarTooltips: any;

  constructor(public elementRef: ElementRef,
  @Inject(LOCALE_ID) protected localeId: string) { }

  public ngAfterViewChecked(): void {

   if (this.localeId === 'sr-Latn') {
        this.toolbarTooltips = QUILL_TOOL_TIPS_SR;
   } else if (this.localeId === 'en-US') {
       this.toolbarTooltips = QUILL_TOOL_TIPS_EN;
   } else {
     // default to EN
     this.toolbarTooltips = QUILL_TOOL_TIPS_EN;
   }

   if (this.toolbarTooltips) {
     const toolbarElement = this.elementRef.nativeElement.querySelector('.ql-toolbar');
     // if i want to set manuallz
     // toolbar.querySelector('button.ql-bold').setAttribute('title', 'Bold');
     // toolbar.querySelector('button.ql-italic').setAttribute('title', 'Italic');
     // toolbar.querySelector('button.ql-list').setAttribute('title', 'List');
     // toolbar.querySelector('button.ql-link').setAttribute('title', 'Link');

   if (toolbarElement) {
      let matchesButtons = toolbarElement.querySelectorAll('button');
   for (let el of matchesButtons) {
      this.showTooltips('button', el);
   }
   //for submenus inside 
   let matchesSpans = toolbarElement.querySelectorAll('.ql-toolbar > span > span');
   for (let el of matchesSpans) {
      this.showTooltips('span', el);
   }
  }
   } else {
  console.warn('No tooltips');
  }
 }
}


quill-tooltips_en.ts file
export const QUILL_TOOL_TIPS_EN: any  = {
'font': 'Select a font',
'size': 'Select a font size',
'header': 'Select the text style',
'bold': 'Bold',
'italic': 'Italic',
'underline': 'Underline',
'strike': 'Strikethrough',
'color': 'Select a text color',
'background': 'Select a background color',
'script': {
  'sub': 'Subscript',
  'super': 'Superscript'
},
'list': {
  'ordered': 'Numbered list',
  'bullet': 'Bulleted list'
},
'indent': {
  '-1': 'Decrease indent',
  '+1': 'Increase indent'
},
'direction': {
  'rtl': 'Text direction (right to left | left to right)',
  'ltr': 'Text direction (left ro right | right to left)'
},
'align': 'Text alignment',
'link': 'Insert a link',
'image': 'Insert an image',
'video': 'Insert a video',
'formula': 'Insert a formula',
'clean': 'Clear format',
'add-table': 'Add a new table',
'table-row': 'Add a row to the selected table',
'table-column': 'Add a column to the selected table',
'remove-table': 'Remove selected table',
'help': 'Show help'
};

@Messa1
Copy link

Messa1 commented Oct 2, 2022

Is there any good solution for react?

@Abidkhan1
Copy link

this.editor.container.previousSibling.querySelector('button.ql-list').setAttribute('title', 'Order list');
this.editor.container.previousSibling.querySelector('button.ql-list[value="bullet"]').setAttribute('title', 'Unorder list');

Working in my case, Tooltip without HTML Toolbar.

@gtournie
Copy link

gtournie commented Oct 21, 2023

didn't want to match all controls with a text, so I used class names to add a title attribute to all of them:

editor.previousSibling.querySelectorAll('select,button').forEach((el) => {
  let title = el.className.replace(/^ql|-/g, ' ').trim();
  if (el.value) title += ' ' + el.value;
  if (el.tagName === 'SELECT') el = el.previousSibling;
  el.setAttribute('title', title.charAt(0).toUpperCase() + title.slice(1));
});
// class="ql-bold" => "Bold"
// class="ql-script" value="super" => "Script super"

This way you'll have, at least, a default value for all of them.

I guess you could use then a map to fix some of them you don't like. Like so:

const map = {
  'bold': 'Make it bold',
  ...
}

And then insert after if (el.value) title += ' ' + el.value;
this line :

value = map[value] || map;

@cyrtii
Copy link

cyrtii commented Nov 7, 2023

For the react devs who Lazy Load their editors and use react-quill:

import React from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

function LazyEditor(props) {
  const setButtonTitle = () => {
    const toolbarElements = document.querySelectorAll(
      '.ql-toolbar button[class*="ql-"], .ql-toolbar span.ql-picker.ql-color-picker',
    );

    toolbarElements.forEach((element) => {
      if (element.tagName.toLowerCase() === 'button') {
        // Handles buttons
        const { className } = element;
        let title = className.split('ql-')[1].split(' ')[0]; // Split by 'ql-' and take the first part
        title = title.charAt(0).toUpperCase() + title.slice(1);
        const value = element.getAttribute('value');
        element.setAttribute('title', `${title}${value ? `: ${value}` : ''}`);
      } else if (
        element.tagName.toLowerCase() === 'span' &&
        element.className.includes('ql-picker')
      ) {
        // Handles spans (color pickers)
        // get the first class in the list of classes of the span
        let title = element.className.split(' ')[0];
        // remove its ql- prefix
        title = title.split('ql-')[1];
        title = title.charAt(0).toUpperCase() + title.slice(1);
        element.setAttribute('title', `${title} picker`);
      }
    });
  };

  React.useEffect(() => {
    setButtonTitle();
  }, []);

  return <ReactQuill {...props} />;
}

export default LazyEditor;

Just import it where you want and use it as if you're using <ReactQuill />.
Tip: If you get bugged by the lack of IntelliSense autocompletions just use <ReactQuill /> then temporarily switch back to <LazyEditor /> after you're done.

I believe this to be a good dynamic solution that does not require me to do much manual labor and maintenance. At least for my use case.

Hope it helps.

@Gholyo
Copy link

Gholyo commented Sep 2, 2024

const buttons = document.querySelectorAll('.ql-toolbar button');

  // Loop through each button and add a tooltip with the class name
  buttons.forEach(button => {
    const className = button.className;
    const val = button.value
    button.setAttribute('title',
     className.slice(3).charAt(0).toUpperCase() + className.slice(4) + ' ' + val)
     ;
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests