Skip to content

Commit

Permalink
feat(plasma-new-hope): add Note component
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanKuzmich committed Dec 25, 2024
1 parent e450fce commit 6e4c90e
Show file tree
Hide file tree
Showing 20 changed files with 894 additions and 1 deletion.
37 changes: 37 additions & 0 deletions packages/plasma-new-hope/src/components/Note/Note.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { styled } from '@linaria/react';
import { css } from '@linaria/core';

export const base = css`
position: relative;
display: flex;
box-sizing: border-box;
`;

export const ContentBefore = styled.div``;

export const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
box-sizing: border-box;
`;

export const Title = styled.div``;
export const TitleHelper = styled.div`
visibility: hidden;
position: absolute;
z-index: -9999;
opacity: 0;
`;

export const Text = styled.span`
display: block;
position: relative;
`;
export const TextHelper = styled.span`
visibility: hidden;
position: absolute;
z-index: -9999;
opacity: 0;
top: 0;
left: 0;
`;
41 changes: 41 additions & 0 deletions packages/plasma-new-hope/src/components/Note/Note.tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export const classes = {
stretch: 'note-stretch',
contentAlignCenter: 'note-content-align-center',
};

export const privateTokens = {
width: '--plasma_private-note-width',
height: '--plasma_private-note-height',
contentWidthWithOffset: '--plasma_private-note-content-width-with-offset',
};

export const tokens = {
background: '--plasma-note-background',
color: '--plasma-note-color',
contentBeforeColor: '--plasma-note-content-before-color',

padding: '--plasma-note-padding',
paddingScalable: '--plasma-note-padding-scalable',
borderRadius: '--plasma-note-border-radius',
gap: '--plasma-note-gap',
gapScalable: '--plasma-note-gap-scalable',
contentGap: '--plasma-note-content-gap',

fixedContentBeforeWidth: '--plasma-note-fixed-content-before-width',
fixedContentBeforeHeight: '--plasma-note-fixed-content-before-height',
fixedContentBeforePadding: '--plasma-note-fixed-content-before-padding',

titleFontFamily: '--plasma-note-title-font-family',
titleFontSize: '--plasma-note-title-font-size',
titleFontStyle: '--plasma-note-title-font-style',
titleFontWeight: '--plasma-note-title-font-weight',
titleLetterSpacing: '--plasma-note-title-letter-spacing',
titleLineHeight: '--plasma-note-title-line-height',

textFontFamily: '--plasma-note-text-font-family',
textFontSize: '--plasma-note-text-font-size',
textFontStyle: '--plasma-note-text-font-style',
textFontWeight: '--plasma-note-text-font-weight',
textLetterSpacing: '--plasma-note-text-letter-spacing',
textLineHeight: '--plasma-note-text-line-height',
};
167 changes: 167 additions & 0 deletions packages/plasma-new-hope/src/components/Note/Note.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { forwardRef, useEffect, useLayoutEffect, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { useResizeObserver } from '@salutejs/plasma-core';

import { canUseDOM, cx, getSizeValueFromProp } from '../../utils';
import type { RootProps } from '../../engines';

import type { NoteProps } from './Note.types';
import { base as viewCSS } from './variations/_view/base';
import { base as sizeCSS } from './variations/_size/base';
import { base, ContentBefore, ContentWrapper, Text, TextHelper, Title, TitleHelper } from './Note.styles';
import { classes, privateTokens, tokens } from './Note.tokens';

export const noteRoot = (Root: RootProps<HTMLDivElement, NoteProps>) =>
forwardRef<HTMLDivElement, NoteProps>(
(
{
className,
style,
title,
text,
contentBefore,
contentBeforeSizing = 'fixed',
size,
view,
stretch,
width,
height,
...rest
},
outerRef,
) => {
const [innerText, setInnerText] = useState(text);
const [contentBeforeWidth, setContentBeforeWidth] = useState(
contentBefore ? `var(${tokens.fixedContentBeforeWidth})` : '0',
);

const contentWrapperRef = useRef<HTMLDivElement>(null);
const contentBeforeRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLDivElement>(null);
const titleHelperRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLSpanElement>(null);
const textHelperRef = useRef<HTMLSpanElement>(null);
const textRenderHelperRef = useRef<HTMLSpanElement>(null);

const innerWidth = width ? getSizeValueFromProp(width) : 'fit-content';
const innerHeight = height ? getSizeValueFromProp(height) : 'fit-content';

const contentGapToken = contentBeforeSizing === 'scalable' ? tokens.gapScalable : tokens.gap;
const contentWidthWithOffsetToken = contentBefore
? `calc(100% - ${contentBeforeWidth} - var(${contentGapToken}))`
: '100%';

const getTruncatedText = () => {
if (!canUseDOM || !text || !contentWrapperRef?.current || !textRenderHelperRef?.current) {
return;
}

const contentHeight = contentWrapperRef.current.offsetHeight;
const titleHeight = titleHelperRef.current?.offsetHeight || 0;

const contentGap = Number(window.getComputedStyle(contentWrapperRef.current).rowGap.replace('px', ''));

const textAvailableHeight = contentHeight - titleHeight - contentGap;

textRenderHelperRef.current.textContent = text;

if (textRenderHelperRef.current.offsetHeight <= textAvailableHeight) {
setInnerText(text);
return;
}

let fullText = `${text.slice(0, -3)}...`;

for (let i = text.length - 1; i >= 0; i -= 1) {
textRenderHelperRef.current.textContent = fullText;

if (textRenderHelperRef.current.offsetHeight <= textAvailableHeight) {
break;
}

fullText = `${fullText.slice(0, i)}...`;
}

setInnerText(fullText);
};

useResizeObserver(contentWrapperRef, getTruncatedText);

useLayoutEffect(() => {
getTruncatedText();
}, [text, contentBefore, contentBeforeSizing, stretch]);

useEffect(() => {
if (!contentBeforeRef?.current) {
return;
}

if (contentBeforeSizing === 'scalable') {
setContentBeforeWidth(`${contentBeforeRef.current.offsetWidth}px`);
return;
}

setContentBeforeWidth(`var(${tokens.fixedContentBeforeWidth})`);
}, [contentBefore, contentBeforeSizing]);

return (
<Root
ref={outerRef}
className={cx(
className,
stretch && classes.stretch,
contentBeforeSizing === 'scalable' && classes.contentAlignCenter,
)}
view={view}
size={size}
style={
{
...style,
[privateTokens.width]: innerWidth,
[privateTokens.height]: innerHeight,
[privateTokens.contentWidthWithOffset]: contentWidthWithOffsetToken,
} as CSSProperties
}
{...rest}
>
{contentBefore && <ContentBefore ref={contentBeforeRef}>{contentBefore}</ContentBefore>}
<ContentWrapper ref={contentWrapperRef}>
{title && (
<>
<Title ref={titleRef}>{title}</Title>
<TitleHelper ref={titleHelperRef}>C</TitleHelper>
</>
)}
{innerText && (
<>
<Text ref={textRef}>
{innerText}
<TextHelper ref={textRenderHelperRef}>C</TextHelper>
</Text>
<TextHelper ref={textHelperRef}>C</TextHelper>
</>
)}
</ContentWrapper>
</Root>
);
},
);

export const noteConfig = {
name: 'Note',
tag: 'div',
layout: noteRoot,
base,
variations: {
view: {
css: viewCSS,
},
size: {
css: sizeCSS,
},
},
defaults: {
view: 'default',
size: 'm',
},
};
41 changes: 41 additions & 0 deletions packages/plasma-new-hope/src/components/Note/Note.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { HTMLAttributes, ReactNode } from 'react';

export type NoteProps = {
/**
* Заголовок к Note.
*/
title?: string;
/**
* Текст к Note.
*/
text?: string;
/**
* Слот под иконку слева от контента.
*/
contentBefore?: ReactNode;
/**
* Размерность слота под иконку.
* @default 'fixed'
*/
contentBeforeSizing?: 'fixed' | 'scalable';
/**
* Компонент растягивается на всю доступную ширину и высоту.
*/
stretch?: boolean;
/**
* Ширина компонента.
*/
width?: string | number;
/**
* Высота компонента.
*/
height?: string | number;
/**
* Вид компонента.
*/
view?: string;
/**
* Размер компонента.
*/
size?: string;
} & HTMLAttributes<HTMLDivElement>;
2 changes: 2 additions & 0 deletions packages/plasma-new-hope/src/components/Note/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { noteRoot, noteConfig } from './Note';
export { tokens as noteTokens, classes as noteClasses } from './Note.tokens';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { css } from '@linaria/core';

import { classes, privateTokens, tokens } from '../../Note.tokens';
import { ContentBefore, ContentWrapper, Text, TextHelper, Title, TitleHelper } from '../../Note.styles';
import { applyEllipsis } from '../../../../mixins';

export const base = css`
padding: var(${tokens.padding});
border-radius: var(${tokens.borderRadius});
height: var(${privateTokens.height});
width: var(${privateTokens.width});
gap: var(${tokens.gap});
&.${classes.stretch} {
width: 100%;
height: 100%;
}
${ContentBefore} {
width: var(${tokens.fixedContentBeforeWidth});
height: var(${tokens.fixedContentBeforeHeight});
padding: var(${tokens.fixedContentBeforePadding});
box-sizing: border-box;
}
&.${classes.contentAlignCenter} {
align-items: center;
padding: var(${tokens.paddingScalable});
gap: var(${tokens.gapScalable});
${ContentBefore} {
width: unset;
height: unset;
padding: unset;
}
}
${ContentWrapper} {
width: var(${privateTokens.contentWidthWithOffset});
gap: var(${tokens.contentGap});
}
${Title}, ${TitleHelper} {
width: 100%;
min-height: var(${tokens.titleLineHeight});
font-family: var(${tokens.titleFontFamily});
font-size: var(${tokens.titleFontSize});
font-style: var(${tokens.titleFontStyle});
font-weight: var(${tokens.titleFontWeight});
letter-spacing: var(${tokens.titleLetterSpacing});
line-height: var(${tokens.titleLineHeight});
${applyEllipsis()}
}
${Text}, ${TextHelper} {
font-family: var(${tokens.textFontFamily});
font-size: var(${tokens.textFontSize});
font-style: var(${tokens.textFontStyle});
font-weight: var(${tokens.textFontWeight});
letter-spacing: var(${tokens.textLetterSpacing});
line-height: var(${tokens.textLineHeight});
word-break: break-all;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[
"--plasma-note-padding",
"--plasma-note-padding-scalable",
"--plasma-note-border-radius",
"--plasma-note-gap",
"--plasma-note-gap-scalable",
"--plasma-note-content-gap",
"--plasma-note-fixed-content-before-width",
"--plasma-note-fixed-content-before-height",
"--plasma-note-fixed-content-before-padding",
"--plasma-note-title-font-family",
"--plasma-note-title-font-size",
"--plasma-note-title-font-style",
"--plasma-note-title-font-weight",
"--plasma-note-title-letter-spacing",
"--plasma-note-title-line-height",
"--plasma-note-text-font-family",
"--plasma-note-text-font-size",
"--plasma-note-text-font-style",
"--plasma-note-text-font-weight",
"--plasma-note-text-letter-spacing",
"--plasma-note-text-line-height"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { css } from '@linaria/core';

import { tokens } from '../../Note.tokens';
import { ContentBefore } from '../../Note.styles';

export const base = css`
background: var(${tokens.background});
color: var(${tokens.color});
${ContentBefore} {
color: var(${tokens.contentBeforeColor});
fill: var(${tokens.contentBeforeColor});
}
`;
Loading

0 comments on commit 6e4c90e

Please sign in to comment.