feat(SW-3173): Added support for one or two columns for the list inside the RTE
Approved-by: Matilda Landström
This commit is contained in:
@@ -60,11 +60,8 @@
|
|||||||
|
|
||||||
.ul,
|
.ul,
|
||||||
.ol {
|
.ol {
|
||||||
display: grid;
|
|
||||||
gap: var(--Space-x1);
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: var(--Space-x2);
|
margin: var(--Space-x2) 0;
|
||||||
margin-bottom: var(--Space-x2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol > li::marker {
|
.ol > li::marker {
|
||||||
@@ -73,16 +70,7 @@
|
|||||||
|
|
||||||
.li {
|
.li {
|
||||||
margin-left: var(--Space-x3);
|
margin-left: var(--Space-x3);
|
||||||
}
|
margin-bottom: var(--Space-x05);
|
||||||
|
|
||||||
.heart > .li::before,
|
|
||||||
.li:has(.heart)::before {
|
|
||||||
content: url('/_static/icons/heart.svg');
|
|
||||||
position: relative;
|
|
||||||
height: 8px;
|
|
||||||
top: 3px;
|
|
||||||
margin-right: var(--Space-x1);
|
|
||||||
margin-left: calc(var(--Space-x3) * -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.heart > .li,
|
.heart > .li,
|
||||||
@@ -90,16 +78,27 @@
|
|||||||
.li:has(.check),
|
.li:has(.check),
|
||||||
.li:has(.heart) {
|
.li:has(.heart) {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heart > .li::before,
|
||||||
|
.li:has(.heart)::before,
|
||||||
|
.check > .li::before,
|
||||||
|
.li:has(.check)::before {
|
||||||
|
position: relative;
|
||||||
|
height: 8px;
|
||||||
|
top: 3px;
|
||||||
|
margin-right: var(--Space-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.check > .li::before,
|
.check > .li::before,
|
||||||
.li:has(.check)::before {
|
.li:has(.check)::before {
|
||||||
content: url('/_static/icons/check-ring.svg');
|
content: url('/_static/icons/check-ring.svg');
|
||||||
position: relative;
|
}
|
||||||
height: 8px;
|
|
||||||
top: 3px;
|
.heart > .li::before,
|
||||||
margin-right: var(--Space-x1);
|
.li:has(.heart)::before {
|
||||||
margin-left: calc(var(--Space-x3) * -1);
|
content: url('/_static/icons/heart.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.li > p {
|
.li > p {
|
||||||
@@ -127,17 +126,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.ol:has(li:nth-last-child(n + 5)),
|
.ol,
|
||||||
.ul:has(li:nth-last-child(n + 5)) {
|
.ul {
|
||||||
grid-template-columns: 1fr 1fr;
|
&.two-column,
|
||||||
grid-auto-flow: column;
|
&.two-columns,
|
||||||
|
&:has(.two-column, .two-columns) {
|
||||||
|
column-count: 2;
|
||||||
|
column-gap: var(--Space-x3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@container sidebar (max-width: 360px) {
|
@container sidebar (max-width: 360px) {
|
||||||
.ol,
|
.ol,
|
||||||
.ul {
|
.ul {
|
||||||
display: flex;
|
&.two-column,
|
||||||
flex-direction: column;
|
&.two-columns,
|
||||||
|
&:has(.two-column, .two-columns) {
|
||||||
|
column-count: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import Table from '../Table'
|
|||||||
import { Typography } from '../Typography'
|
import { Typography } from '../Typography'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
extractAvailableListClassNames,
|
||||||
hasAvailableParagraphFormat,
|
hasAvailableParagraphFormat,
|
||||||
hasAvailableULFormat,
|
|
||||||
makeCssModuleCompatibleClassName,
|
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
import styles from './jsontohtml.module.css'
|
import styles from './jsontohtml.module.css'
|
||||||
|
|
||||||
|
import { insertResponseToImageVaultAsset } from './insertResponseToImageVaultAsset'
|
||||||
import type { EmbedByUid } from './JsonToHtml'
|
import type { EmbedByUid } from './JsonToHtml'
|
||||||
import type { Attributes, RTEImageVaultAttrs } from './types/rte/attrs'
|
import type { Attributes, RTEImageVaultAttrs } from './types/rte/attrs'
|
||||||
import {
|
import {
|
||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
type RTETextNode,
|
type RTETextNode,
|
||||||
} from './types/rte/node'
|
} from './types/rte/node'
|
||||||
import type { RenderOptions } from './types/rte/option'
|
import type { RenderOptions } from './types/rte/option'
|
||||||
import { insertResponseToImageVaultAsset } from './insertResponseToImageVaultAsset'
|
|
||||||
|
|
||||||
function noNestedLinksOrReferences(node: RTENode) {
|
function noNestedLinksOrReferences(node: RTENode) {
|
||||||
if ('type' in node) {
|
if ('type' in node) {
|
||||||
@@ -255,15 +254,12 @@ export const renderOptions: RenderOptions = {
|
|||||||
fullRenderOptions: RenderOptions
|
fullRenderOptions: RenderOptions
|
||||||
) => {
|
) => {
|
||||||
const { className, ...props } = extractPossibleAttributes(node.attrs)
|
const { className, ...props } = extractPossibleAttributes(node.attrs)
|
||||||
const compatibleClassName = makeCssModuleCompatibleClassName(
|
const compatibleClassNames = extractAvailableListClassNames(className)
|
||||||
className,
|
|
||||||
'ul'
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={node.uid}
|
key={node.uid}
|
||||||
{...props}
|
{...props}
|
||||||
className={cx(styles.li, compatibleClassName)}
|
className={cx(styles.li, compatibleClassNames)}
|
||||||
>
|
>
|
||||||
{next(node.children, embeds, fullRenderOptions)}
|
{next(node.children, embeds, fullRenderOptions)}
|
||||||
</li>
|
</li>
|
||||||
@@ -277,26 +273,11 @@ export const renderOptions: RenderOptions = {
|
|||||||
fullRenderOptions: RenderOptions
|
fullRenderOptions: RenderOptions
|
||||||
) => {
|
) => {
|
||||||
const { className, ...props } = extractPossibleAttributes(node.attrs)
|
const { className, ...props } = extractPossibleAttributes(node.attrs)
|
||||||
|
const compatibleClassNames = extractAvailableListClassNames(className)
|
||||||
// Set the number of rows dynamically to create even rows for each column. We want the li:s
|
|
||||||
// to flow with the column, so therefore this is needed.
|
|
||||||
let numberOfRows: number | undefined
|
|
||||||
if (node.children.length > 4) {
|
|
||||||
const half = node.children.length / 2
|
|
||||||
numberOfRows = Math.ceil(half)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography key={node.uid} variant="Body/Paragraph/mdRegular">
|
<Typography key={node.uid} variant="Body/Paragraph/mdRegular">
|
||||||
<ol
|
<ol className={cx(styles.ol, compatibleClassNames)} {...props}>
|
||||||
className={cx(styles.ol, className)}
|
|
||||||
{...props}
|
|
||||||
style={
|
|
||||||
numberOfRows
|
|
||||||
? { gridTemplateRows: `repeat(${numberOfRows}, auto)` }
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{next(node.children, embeds, fullRenderOptions)}
|
{next(node.children, embeds, fullRenderOptions)}
|
||||||
</ol>
|
</ol>
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -340,8 +321,9 @@ export const renderOptions: RenderOptions = {
|
|||||||
let propsClassName = className
|
let propsClassName = className
|
||||||
|
|
||||||
if (className) {
|
if (className) {
|
||||||
if (hasAvailableULFormat(className)) {
|
const availableClassNames = extractAvailableListClassNames(className)
|
||||||
propsClassName = styles[className]
|
if (availableClassNames.length) {
|
||||||
|
propsClassName = cx(availableClassNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,8 +343,9 @@ export const renderOptions: RenderOptions = {
|
|||||||
let propsClassName = className
|
let propsClassName = className
|
||||||
|
|
||||||
if (className) {
|
if (className) {
|
||||||
if (hasAvailableULFormat(className)) {
|
const availableClassNames = extractAvailableListClassNames(className)
|
||||||
propsClassName = styles[className]
|
if (availableClassNames.length) {
|
||||||
|
propsClassName = cx(availableClassNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,32 +639,11 @@ export const renderOptions: RenderOptions = {
|
|||||||
fullRenderOptions: RenderOptions
|
fullRenderOptions: RenderOptions
|
||||||
) => {
|
) => {
|
||||||
const { className, ...props } = extractPossibleAttributes(node.attrs)
|
const { className, ...props } = extractPossibleAttributes(node.attrs)
|
||||||
const compatibleClassName = makeCssModuleCompatibleClassName(
|
const compatibleClassNames = extractAvailableListClassNames(className)
|
||||||
className,
|
|
||||||
'ul'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set the number of rows dynamically to create even rows for each column. We want the li:s
|
|
||||||
// to flow with the column, so therefore this is needed.
|
|
||||||
let numberOfRows: number | undefined
|
|
||||||
if (node.children.length > 4) {
|
|
||||||
const half = node.children.length / 2
|
|
||||||
numberOfRows = Math.ceil(half)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography key={node.uid} variant="Body/Paragraph/mdRegular">
|
<Typography key={node.uid} variant="Body/Paragraph/mdRegular">
|
||||||
<ul
|
<ul className={cx(styles.ul, compatibleClassNames)} {...props}>
|
||||||
className={cx(styles.ul, compatibleClassName)}
|
|
||||||
{...props}
|
|
||||||
style={
|
|
||||||
numberOfRows
|
|
||||||
? {
|
|
||||||
gridTemplateRows: `repeat(${numberOfRows}, auto)`,
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{next(node.children, embeds, fullRenderOptions)}
|
{next(node.children, embeds, fullRenderOptions)}
|
||||||
</ul>
|
</ul>
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -747,8 +709,9 @@ export const renderOptions: RenderOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (className) {
|
if (className) {
|
||||||
if (hasAvailableULFormat(className)) {
|
const availableClassNames = extractAvailableListClassNames(className)
|
||||||
propsClassName = styles[className]
|
if (availableClassNames.length) {
|
||||||
|
propsClassName = cx(availableClassNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const AVAILABLE_LIST_FORMATS = [
|
||||||
|
'heart',
|
||||||
|
'check',
|
||||||
|
'two-column',
|
||||||
|
'two-columns',
|
||||||
|
]
|
||||||
@@ -63,11 +63,6 @@ export enum AvailableParagraphFormatEnum {
|
|||||||
'subtitle-2' = 'subtitle-2',
|
'subtitle-2' = 'subtitle-2',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AvailableULFormatEnum {
|
|
||||||
'heart' = 'heart',
|
|
||||||
'check' = 'check',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ContentBlockType =
|
export type ContentBlockType =
|
||||||
| 'AccountPage'
|
| 'AccountPage'
|
||||||
| 'CampaignOverviewPage'
|
| 'CampaignOverviewPage'
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import { renderOptions } from './renderOptions'
|
|||||||
|
|
||||||
import styles from './jsontohtml.module.css'
|
import styles from './jsontohtml.module.css'
|
||||||
|
|
||||||
import type { Node, Embeds } from './JsonToHtml'
|
import type { Embeds, Node } from './JsonToHtml'
|
||||||
|
|
||||||
import {
|
import { EmbedByUid } from './JsonToHtml'
|
||||||
AvailableParagraphFormatEnum,
|
import { AVAILABLE_LIST_FORMATS } from './types/rte/constants'
|
||||||
AvailableULFormatEnum,
|
import { AvailableParagraphFormatEnum, RTETypeEnum } from './types/rte/enums'
|
||||||
RTETypeEnum,
|
|
||||||
} from './types/rte/enums'
|
|
||||||
import {
|
import {
|
||||||
RTEMarkType,
|
RTEMarkType,
|
||||||
type RTENode,
|
type RTENode,
|
||||||
@@ -19,7 +17,6 @@ import {
|
|||||||
type RTETextNode,
|
type RTETextNode,
|
||||||
} from './types/rte/node'
|
} from './types/rte/node'
|
||||||
import type { RenderOptions } from './types/rte/option'
|
import type { RenderOptions } from './types/rte/option'
|
||||||
import { EmbedByUid } from './JsonToHtml'
|
|
||||||
|
|
||||||
export function groupEmbedsByUid(embedsArray: Node<Embeds>[]) {
|
export function groupEmbedsByUid(embedsArray: Node<Embeds>[]) {
|
||||||
const embedsByUid = embedsArray.reduce<EmbedByUid>((acc, embed) => {
|
const embedsByUid = embedsArray.reduce<EmbedByUid>((acc, embed) => {
|
||||||
@@ -109,11 +106,14 @@ export function hasAvailableParagraphFormat(className?: string) {
|
|||||||
return Object.keys(AvailableParagraphFormatEnum).includes(className)
|
return Object.keys(AvailableParagraphFormatEnum).includes(className)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasAvailableULFormat(className?: string) {
|
export function extractAvailableListClassNames(className?: string) {
|
||||||
if (!className) {
|
if (!className) {
|
||||||
return false
|
return []
|
||||||
}
|
}
|
||||||
return Object.keys(AvailableULFormatEnum).includes(className)
|
const classNames = className.split(' ')
|
||||||
|
return classNames
|
||||||
|
.filter((name) => AVAILABLE_LIST_FORMATS.includes(name))
|
||||||
|
.map((item) => styles[item] || item)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nodeToHtml(
|
export function nodeToHtml(
|
||||||
@@ -165,20 +165,3 @@ export function nodesToHtml(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeCssModuleCompatibleClassName(
|
|
||||||
className: string | undefined,
|
|
||||||
formatType: 'ul'
|
|
||||||
): string {
|
|
||||||
if (!className) return ''
|
|
||||||
|
|
||||||
if (formatType === 'ul' && hasAvailableULFormat(className)) {
|
|
||||||
// TODO: REMOVE
|
|
||||||
// @ats-expect-error: We want to set css modules classNames even if it does not correspond
|
|
||||||
// to an existing class in the module style sheet. Due to our css modules plugin for
|
|
||||||
// typescript, we cannot do this without the ts-ignore
|
|
||||||
return styles[className] || className
|
|
||||||
}
|
|
||||||
|
|
||||||
return className
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user