-
Notifications
You must be signed in to change notification settings - Fork 5
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
base: main
Are you sure you want to change the base?
Changes from all commits
b3fff53
b049571
5aaf952
61d9713
f911fd5
0b03fc2
dc575f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> | ||
<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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Think the use of |
||
> | ||
<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/)* |
There was a problem hiding this comment.
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
?There was a problem hiding this comment.
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 thetable-toolbar
as its similar.