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

feat(rich-text-editor) - single click actions #633

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<h1>gux-rich-text-editor</h1>
<gux-rich-text-editor-beta>
<gux-rich-text-editor-action-group slot="typographical-emphasis">
<gux-rich-text-editor-action action="bold"></gux-rich-text-editor-action>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be gux-rich-text-editor-button?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the designs they are referenced as actions. Also wanted to keep it consistent with the name we have for actions in the table-toolbar as its similar.

<gux-rich-text-editor-action action="italic"></gux-rich-text-editor-action>
<gux-rich-text-editor-action
action="underline"
></gux-rich-text-editor-action>
<gux-rich-text-editor-action action="strike"></gux-rich-text-editor-action>
</gux-rich-text-editor-action-group>
<gux-rich-text-editor-action-group slot="text-styling">
<gux-rich-text-editor-action
action="clearFormatting"
></gux-rich-text-editor-action>
</gux-rich-text-editor-action-group>
<gux-rich-text-editor-action-group slot="lists-indentation">
<gux-rich-text-editor-action
action="bulletList"
></gux-rich-text-editor-action>
<gux-rich-text-editor-action
action="orderedList"
></gux-rich-text-editor-action>
</gux-rich-text-editor-action-group>
<gux-rich-text-editor-action-group slot="inserting">
<gux-rich-text-editor-action
action="codeblock"
></gux-rich-text-editor-action>
<gux-rich-text-editor-action
action="blockQuote"
></gux-rich-text-editor-action>
</gux-rich-text-editor-action-group>
<gux-rich-text-editor-action-group slot="global-actions">
<gux-rich-text-editor-action action="undo"></gux-rich-text-editor-action>
<gux-rich-text-editor-action action="redo"></gux-rich-text-editor-action>
<gux-rich-text-editor-action action="delete"></gux-rich-text-editor-action>
</gux-rich-text-editor-action-group>
<div class="editorElement" slot="editor"></div>
</gux-rich-text-editor-beta>

<h2>Disabled</h2>
<gux-rich-text-editor-beta disabled>
<gux-rich-text-editor-action-group slot="typographical-emphasis">
<gux-rich-text-editor-action action="bold"></gux-rich-text-editor-action>
<gux-rich-text-editor-action action="italic"></gux-rich-text-editor-action>
<gux-rich-text-editor-action
action="underline"
></gux-rich-text-editor-action>
<gux-rich-text-editor-action
action="strike"
></gux-rich-text-editor-action> </gux-rich-text-editor-action-group
>v>
</gux-rich-text-editor-beta>

<style
onload="(function () {
(async () => {
const { Editor } = await import('https://cdn.jsdelivr.net/npm/@tiptap/[email protected]/+esm');
const StarterKit = (await import('https://cdn.jsdelivr.net/npm/@tiptap/[email protected]/+esm')).default;
const Underline = (await import('https://cdn.jsdelivr.net/npm/@tiptap/[email protected]/+esm')).default;

// Initialize the editor
const editor = new Editor({
element: document.querySelector('.editorElement'),
extensions: [StarterKit, Underline],
content: 'Start typing here...',
injectCSS: false,
editorProps: {
attributes: {
class: 'editor-styles'
},
},
});

// Map the action types to DOM elements and editor commands
const actionsConfig = [
{ action: 'bold', command: 'toggleBold', selector: 'gux-rich-text-editor-action[action=bold]' },
{ action: 'italic', command: 'toggleItalic', selector: 'gux-rich-text-editor-action[action=italic]' },
{ action: 'strike', command: 'toggleStrike', selector: 'gux-rich-text-editor-action[action=strike]' },
{ action: 'underline', command: 'toggleUnderline', selector: 'gux-rich-text-editor-action[action=underline]' },
{ action: 'codeBlock', command: 'toggleCodeBlock', selector: 'gux-rich-text-editor-action[action=codeblock]' },
{ action: 'blockquote', command: 'toggleBlockquote', selector: 'gux-rich-text-editor-action[action=blockQuote]' },
{ action: 'orderedList', command: 'toggleOrderedList', selector: 'gux-rich-text-editor-action[action=orderedList]' },
{ action: 'bulletList', command: 'toggleBulletList', selector: 'gux-rich-text-editor-action[action=bulletList]' },
{ action: 'clearFormatting', command: 'unsetAllMarks', selector: 'gux-rich-text-editor-action[action=clearFormatting]' },
{ action: 'undo', command: 'undo', selector: 'gux-rich-text-editor-action[action=undo]' },
{ action: 'redo', command: 'redo', selector: 'gux-rich-text-editor-action[action=redo]' },
{ action: 'delete', command: 'delete', selector: 'gux-rich-text-editor-action[action=delete]' }
];

// Add event listeners and store action elements
const actions = actionsConfig.map(({ action, command, selector }) => {
const element = document.querySelector(selector);

// Add click event listener for toggling editor command
element.addEventListener('click', () => {
command == 'delete' ? editor.commands.deleteNode('paragraph') : editor.chain().focus()[command]().run();
});

return { element, action };
});

// Function to update the active state of each action
function updateActionState() {
actions.forEach(({ element, action }) => {
const isActive = editor.isActive(action);
element.isActive = isActive;
});
}

// Add a single event listener for updating action states
editor.on('transaction', updateActionState);
editor.on('selectionUpdate', updateActionState);



})();

})()"
>
.not-used {
-custom-noop: noop;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
:host {
display: flex;
flex-direction: row;
}

:host([slot='global-actions']) {
::slotted(gux-rich-text-editor-action:last-child) {
margin-left: 20px; //Need to confirm with UX.
}
}

.gux-action-group-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 4px;
align-items: flex-start;
justify-content: flex-start;
}

.gux-action-group-divider {
display: flex;
align-items: center;
justify-content: center;
width: 9px;
height: 32px;

.gux-divider {
width: 1px;
height: 20px;
background-color: #c6c8ce;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Component, h, Prop, Element, Host } from '@stencil/core';

/**
* @slot actions - Slot for gux-rich-text-editor-actions
*/

@Component({
tag: 'gux-rich-text-editor-action-group',
styleUrl: 'gux-rich-text-editor-action-group.scss',
shadow: true
})
export class GuxRichTextEditorActionGroup {
@Element()
root: HTMLElement;

@Prop()
hideActionDivider: boolean = false;

private shouldHideDivider(): boolean {
return this.hideActionDivider || this.isGlobalActionsSlot();
}

private isGlobalActionsSlot(): boolean {
return this.root.getAttribute('slot') === 'global-actions';
}

private renderActionGroupDivider(): JSX.Element {
if (!this.shouldHideDivider()) {
return (
<div class="gux-action-group-divider">
<div class="gux-divider"></div>
gavin-everett-genesys marked this conversation as resolved.
Show resolved Hide resolved
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably do this with one div.

) as JSX.Element;
}
}

render(): JSX.Element {
return (
<Host>
<div class="gux-action-group-container">
<slot></slot>
</div>
{this.renderActionGroupDivider()}
</Host>
) as JSX.Element;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# gux-rich-text-editor-action-group



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| ------------------- | --------------------- | ----------- | --------- | ------- |
| `hideActionDivider` | `hide-action-divider` | | `boolean` | `false` |


## Slots

| Slot | Description |
| ----------- | ------------------------------------- |
| `"actions"` | Slot for gux-rich-text-editor-actions |


----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
:host {
display: block;
}

gux-button-slot {
width: var(--gse-ui-button-icon-only-width);
height: var(--gse-ui-button-default-height);

button.gux-is-active {
color: var(--gse-ui-button-ghost-active-foregroundColor);
background-color: var(--gse-ui-button-ghost-active-backgroundColor);
}

gux-icon {
width: var(--gse-ui-button-icon-size);
height: var(--gse-ui-button-icon-size);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Component, Element, Prop, h } from '@stencil/core';
import { trackComponent } from '@utils/tracking/usage';

import { buildI18nForComponent, GetI18nValue } from 'i18n';
import translationResources from './i18n/en.json';
import { GuxRichTextEditorActionTypes } from './gux-rich-text-editor-action.types';
import {
calculateDisabledState,
returnActionTypeIcon
} from '../gux-rich-text-editor.service';

@Component({
tag: 'gux-rich-text-editor-action',
styleUrl: 'gux-rich-text-editor-action.scss',
shadow: true
})
export class GuxRichTextEditorAction {
private i18n: GetI18nValue;

@Element()
root: HTMLElement;

@Prop()
action: GuxRichTextEditorActionTypes;

@Prop({ mutable: true })
disabled: boolean = false;

@Prop()
isActive: boolean = false;

async componentWillLoad(): Promise<void> {
trackComponent(this.root, { variant: this.action });
this.i18n = await buildI18nForComponent(this.root, translationResources);
this.disabled = calculateDisabledState(this.root);
}

private renderTooltip(): JSX.Element {
if (!this.disabled) {
return (
<gux-tooltip>
<div slot="content">{this.i18n(this.action)}</div>
</gux-tooltip>
) as JSX.Element;
}
}

private renderActionButton(): JSX.Element {
return (
<gux-button-slot accent="ghost" icon-only>
<button
type="button"
disabled={this.disabled}
class={{ 'gux-is-active': this.isActive }}
gavin-everett-genesys marked this conversation as resolved.
Show resolved Hide resolved
aria-pressed={this.isActive.toString()}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an aria-label or some way to describe the button as the icon is decorative?
I know the label is in the tooltip but to got to tab past the button into the tooltip to get the label.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think the use of screenreader-text property on the the icon will suffice 🤔

>
<gux-icon
icon-name={returnActionTypeIcon(this.action)}
screenreader-text={this.i18n(this.action)}
></gux-icon>
</button>
{this.renderTooltip()}
</gux-button-slot>
);
}

render(): JSX.Element {
return this.renderActionButton();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type GuxRichTextEditorActionTypes =
| 'bold'
| 'italic'
| 'underline'
| 'strike'
| 'codeblock'
| 'blockQuote'
| 'orderedList'
| 'bulletList'
| 'clearFormatting'
| 'undo'
| 'redo'
| 'delete';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"codeblock": "Code Block",
"blockQuote": "Block Quote",
"orderedList": "Ordered List",
"bulletList": "Bullet List",
"clearFormatting": "Clear Formatting",
"undo": "Undo",
"redo": "Redo",
"delete": "Delete"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# gux-rich-text-editor-action



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| ---------- | ----------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `action` | `action` | | `"blockQuote" \| "bold" \| "bulletList" \| "clearFormatting" \| "codeblock" \| "delete" \| "italic" \| "orderedList" \| "redo" \| "strike" \| "underline" \| "undo"` | `undefined` |
| `disabled` | `disabled` | | `boolean` | `false` |
| `isActive` | `is-active` | | `boolean` | `false` |


## Dependencies

### Depends on

- [gux-tooltip](../../../stable/gux-tooltip)
- [gux-button-slot](../../../stable/gux-button-slot)
- [gux-icon](../../../stable/gux-icon)

### Graph
```mermaid
graph TD;
gux-rich-text-editor-action --> gux-tooltip
gux-rich-text-editor-action --> gux-button-slot
gux-rich-text-editor-action --> gux-icon
style gux-rich-text-editor-action fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Loading