Compare commits
5 Commits
v0.0.1
...
content-su
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c1b9748c6 | |||
| 6c73b0fa61 | |||
| e4e6bc5af4 | |||
| 028ce21c4c | |||
| 915c56351b |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,3 +1,12 @@
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
node_modules
|
||||
*.log
|
||||
storybook-static
|
||||
**/node_modules
|
||||
**/.turbo
|
||||
coverage
|
||||
.DS_Store
|
||||
**/storybook-static
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../stories/**/*.stories.@(ts|tsx)"],
|
||||
addons: ["@storybook/addon-essentials"],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,25 +0,0 @@
|
||||
:root {
|
||||
--Default-BgLight: #ffffff;
|
||||
--Default-BgDarken: #f2f2f2;
|
||||
--Default-StrokeDividers: #e0e0e0;
|
||||
--Default-White: #ffffff;
|
||||
--Text-Primary: #1d1d1d;
|
||||
--Controls-Primary: #1d1d1d;
|
||||
--Controls-Plashes: #f5f5f5;
|
||||
--Shadow-Z100: 0 10px 24px rgba(20, 20, 20, 0.14);
|
||||
--Accent-Primary: #5152ba;
|
||||
--Opacity-BlackOpacity45: rgba(0, 0, 0, 0.45);
|
||||
--corner-S: 8px;
|
||||
--corner-M: 12px;
|
||||
--Corner-XL: 32px;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#storybook-root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #f6f8fb;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
|
||||
import "antd/dist/reset.css";
|
||||
import "../src/react/video-player/tach-video-js.css";
|
||||
import "./preview.css";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
layout: "padded",
|
||||
controls: {
|
||||
expanded: true,
|
||||
sort: "requiredFirst",
|
||||
},
|
||||
options: {
|
||||
storySort: {
|
||||
order: ["React", "Angular"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
8
.yarnrc.yml
Normal file
8
.yarnrc.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
packageExtensions:
|
||||
ng-zorro-antd@*:
|
||||
peerDependencies:
|
||||
rxjs: ">=7.0.0"
|
||||
215
README.md
215
README.md
@@ -1,193 +1,60 @@
|
||||
# @tach/video-player
|
||||
# hublib-web
|
||||
|
||||
Single package with three entrypoints:
|
||||
Monorepo for shared UI packages distributed with Yarn workspaces.
|
||||
|
||||
- `@tach/video-player/core`
|
||||
- `@tach/video-player/react`
|
||||
- `@tach/video-player/angular`
|
||||
External projects install these packages directly from Git tags. Package `dist/` artifacts must be committed.
|
||||
|
||||
This folder is prepared to be moved into a dedicated repository as-is.
|
||||
## Structure
|
||||
|
||||
## What Is Included
|
||||
- `packages/tach-typography` - typography tokens and adapters for React/Angular.
|
||||
- `packages/video-player` - cross-framework video player runtime and adapters.
|
||||
- `packages/content-suggestions` - content text/title with mention/tag/link parsing for React/Angular.
|
||||
|
||||
- Build pipeline to `dist/`:
|
||||
- `npm run build`
|
||||
- `npm run typecheck`
|
||||
- Storybook playground for framework entrypoints:
|
||||
- `npm run storybook`
|
||||
- `npm run build-storybook`
|
||||
- `dist`-first flow for git-based installation:
|
||||
- `dist/` is committed to the repository.
|
||||
- consumers do not depend on `prepare` at install time.
|
||||
- Version bump scripts:
|
||||
- `npm run release:patch`
|
||||
- `npm run release:minor`
|
||||
- `npm run release:major`
|
||||
- Optional framework peers:
|
||||
- React peers are optional if only `core/angular` is used.
|
||||
- Angular peers are optional if only `core/react` is used.
|
||||
## Package READMEs
|
||||
|
||||
## Repo Transfer Checklist
|
||||
- [tach-typography](./packages/tach-typography/README.md)
|
||||
- [video-player](./packages/video-player/README.md)
|
||||
- [content-suggestions](./packages/content-suggestions/README.md)
|
||||
|
||||
1. Create a new repository (for example `tach/video-player`).
|
||||
2. Copy everything from this folder to the new repo root.
|
||||
3. In the new repo run:
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
corepack enable
|
||||
yarn set version 4.12.0
|
||||
yarn install
|
||||
yarn build
|
||||
yarn test
|
||||
```
|
||||
|
||||
4. Commit and push (including `dist/`):
|
||||
## Add a new package
|
||||
|
||||
1. Create a folder inside `packages/<name>`.
|
||||
2. Add `package.json` with a unique package name.
|
||||
3. Link internal dependencies with `workspace:*` (or `workspace:^`).
|
||||
4. Run `yarn install` and then `yarn build`.
|
||||
|
||||
## Installation from Git (SSH)
|
||||
|
||||
This repository is consumed by tag-based Git dependencies.
|
||||
|
||||
- Full install guide: [docs/git-installation.md](./docs/git-installation.md)
|
||||
- Release workflow: [docs/release-policy.md](./docs/release-policy.md)
|
||||
|
||||
Command template:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: initial @tach/video-player package"
|
||||
git push origin main
|
||||
yarn add "@hublib-web/<package>@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/<package>&tag=<package>-vX.Y.Z"
|
||||
```
|
||||
|
||||
5. Create a release tag:
|
||||
## Releases
|
||||
|
||||
```bash
|
||||
git tag v0.1.0
|
||||
git push origin v0.1.0
|
||||
```
|
||||
Releases are done with Git tags (no Changesets, no npm publish).
|
||||
|
||||
## Build Output
|
||||
Short flow:
|
||||
|
||||
`npm run build` compiles TypeScript into `dist/` and copies required assets (`css/scss/svg/...`).
|
||||
`dist/` must be committed before creating a new version tag.
|
||||
1. Bump version in `packages/<package>/package.json`.
|
||||
2. Build package and commit updated `dist/` artifacts.
|
||||
3. Create an annotated package tag: `<package>-vX.Y.Z`.
|
||||
4. Push commit and tags to origin.
|
||||
|
||||
Entrypoints are exported from `dist`:
|
||||
|
||||
- `@tach/video-player/core`
|
||||
- `@tach/video-player/react`
|
||||
- `@tach/video-player/angular`
|
||||
|
||||
## Storybook
|
||||
|
||||
Local Storybook exposes two pages for development and prop testing:
|
||||
|
||||
- `React/VideoPlayer` - React component entrypoint with controls for player props.
|
||||
- `Angular/VideoPlayerAdapter` - adapter entrypoint with controls for runtime options (`attach/update`).
|
||||
|
||||
Run Storybook:
|
||||
|
||||
```bash
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
Build static Storybook:
|
||||
|
||||
```bash
|
||||
npm run build-storybook
|
||||
```
|
||||
|
||||
## Installation From Git (Without npm Registry)
|
||||
|
||||
### npm
|
||||
|
||||
By tag:
|
||||
|
||||
```bash
|
||||
npm i git+ssh://git@github.com/<org>/video-player.git#v0.1.0
|
||||
```
|
||||
|
||||
By commit:
|
||||
|
||||
```bash
|
||||
npm i git+ssh://git@github.com/<org>/video-player.git#<commit-sha>
|
||||
```
|
||||
|
||||
By semver tag range:
|
||||
|
||||
```bash
|
||||
npm i github:<org>/video-player#semver:^0.1.0
|
||||
```
|
||||
|
||||
### pnpm
|
||||
|
||||
```bash
|
||||
pnpm add git+ssh://git@github.com/<org>/video-player.git#v0.1.0
|
||||
```
|
||||
|
||||
### yarn
|
||||
|
||||
```bash
|
||||
yarn add git+ssh://git@github.com/<org>/video-player.git#v0.1.0
|
||||
```
|
||||
|
||||
## Versioning Workflow
|
||||
|
||||
1. Build and bump version:
|
||||
|
||||
```bash
|
||||
npm run release:patch
|
||||
# or release:minor / release:major
|
||||
```
|
||||
|
||||
2. Commit generated `dist` changes and `package.json` version bump:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "chore(release): vX.Y.Z"
|
||||
```
|
||||
|
||||
3. Push commit and tags:
|
||||
|
||||
```bash
|
||||
git push origin main --follow-tags
|
||||
```
|
||||
|
||||
4. Consumers update git tag/version in their `package.json`.
|
||||
|
||||
## Why Optional Peers
|
||||
|
||||
Consumers install only the framework they need:
|
||||
|
||||
- React app: install `react` and `react-dom`, use `@tach/video-player/react`.
|
||||
- Angular app: install `@angular/*`, use `@tach/video-player/angular`.
|
||||
- Shared utilities only: use `@tach/video-player/core`.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
### Core
|
||||
|
||||
```ts
|
||||
import {
|
||||
isHlsSource,
|
||||
selectPlaybackEngine,
|
||||
VideoPlayerRuntime,
|
||||
setVideoPlayerTokenProvider,
|
||||
} from "@tach/video-player/core";
|
||||
|
||||
setVideoPlayerTokenProvider(async () => {
|
||||
// Provide host-app token retrieval here.
|
||||
return null;
|
||||
});
|
||||
|
||||
const runtime = new VideoPlayerRuntime();
|
||||
await runtime.init({
|
||||
container: document.getElementById("player")!,
|
||||
source: { src: "https://example.com/video.m3u8" },
|
||||
});
|
||||
```
|
||||
|
||||
### React
|
||||
|
||||
```tsx
|
||||
import VideoPlayer from "@tach/video-player/react";
|
||||
```
|
||||
|
||||
### Angular
|
||||
|
||||
```ts
|
||||
import { AngularVideoPlayerAdapter } from "@tach/video-player/angular";
|
||||
|
||||
const adapter = new AngularVideoPlayerAdapter();
|
||||
await adapter.attach(containerElement, {
|
||||
source: { src: "https://example.com/video.m3u8" },
|
||||
});
|
||||
```
|
||||
|
||||
`AngularVideoPlayerAdapter` is intentionally framework-light: it wraps `VideoPlayerRuntime` (`attach/update/destroy/on`) and does not depend on React code.
|
||||
Detailed policy and examples: [docs/release-policy.md](./docs/release-policy.md).
|
||||
|
||||
1
dist/core/player-runtime.js.map
vendored
1
dist/core/player-runtime.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import Player from "video.js/dist/types/player";
|
||||
interface VideoQRScannerPluginProps {
|
||||
player: Player | null;
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* Интервал между запусками сканирования в миллисекундах.
|
||||
* По умолчанию 200 мс.
|
||||
*/
|
||||
scanInterval?: number;
|
||||
/**
|
||||
* Масштаб для сканирования (0 < scanningScale <= 1).
|
||||
* Значение меньше 1 уменьшает разрешение для ускорения обработки.
|
||||
* По умолчанию 1.
|
||||
*/
|
||||
scanningScale?: number;
|
||||
/**
|
||||
* Количество неудачных попыток обнаружения QR-кода до снятия маски.
|
||||
* По умолчанию 2.a
|
||||
*/
|
||||
maxFailedAttempts?: number;
|
||||
}
|
||||
declare const VideoQRScannerPlugin: React.FC<VideoQRScannerPluginProps>;
|
||||
export default VideoQRScannerPlugin;
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/react/video-player/components/qr-scanner/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAKxE,OAAO,MAAM,MAAM,4BAA4B,CAAC;AAIhD,UAAU,yBAAyB;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAqR7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
||||
@@ -1,209 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Popover } from "antd";
|
||||
import QrScanner from "qr-scanner";
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
const VideoQRScannerPlugin = ({ player, enabled = true, scanInterval = 200, scanningScale = 1, maxFailedAttempts = 2, }) => {
|
||||
const canvasRef = useRef(null);
|
||||
const [detectedQr, setDetectedQr] = useState(null);
|
||||
const animationFrameRef = useRef(null);
|
||||
const ignoredQRCodes = useRef(new Set());
|
||||
const [playerEl, setPlayerEl] = useState(null);
|
||||
const lastScanTimeRef = useRef(0);
|
||||
const isScanningRef = useRef(false);
|
||||
// Счётчик неудачных попыток обнаружения QR, если код уже отображается
|
||||
const failedAttemptsRef = useRef(0);
|
||||
// Для получения актуального значения detectedQr внутри асинхронного колбэка
|
||||
const detectedQrRef = useRef(null);
|
||||
useEffect(() => {
|
||||
detectedQrRef.current = detectedQr;
|
||||
}, [detectedQr]);
|
||||
// Изначально устанавливаем контейнер для портала как элемент плеера
|
||||
useEffect(() => {
|
||||
if (player) {
|
||||
setPlayerEl(player.el());
|
||||
}
|
||||
}, [player]);
|
||||
// Обновление контейнера при переходе в полноэкранный режим
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
if (document.fullscreenElement) {
|
||||
setPlayerEl(document.fullscreenElement);
|
||||
}
|
||||
else if (player) {
|
||||
setPlayerEl(player.el());
|
||||
}
|
||||
};
|
||||
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||
return () => {
|
||||
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
||||
};
|
||||
}, [player]);
|
||||
const scanFrame = useCallback(async () => {
|
||||
if (!player || player.paused() || !canvasRef.current || !enabled)
|
||||
return;
|
||||
const now = performance.now();
|
||||
if (now - lastScanTimeRef.current < scanInterval) {
|
||||
animationFrameRef.current = requestAnimationFrame(scanFrame);
|
||||
return;
|
||||
}
|
||||
lastScanTimeRef.current = now;
|
||||
// Предотвращаем параллельное выполнение сканирования
|
||||
if (isScanningRef.current) {
|
||||
animationFrameRef.current = requestAnimationFrame(scanFrame);
|
||||
return;
|
||||
}
|
||||
isScanningRef.current = true;
|
||||
const videoEl = player.tech(true).el();
|
||||
const canvas = canvasRef.current;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) {
|
||||
isScanningRef.current = false;
|
||||
return;
|
||||
}
|
||||
// Получаем оригинальные размеры видео
|
||||
const originalVideoWidth = videoEl.videoWidth;
|
||||
const originalVideoHeight = videoEl.videoHeight;
|
||||
// Устанавливаем canvas с пониженным разрешением для сканирования
|
||||
canvas.width = originalVideoWidth * scanningScale;
|
||||
canvas.height = originalVideoHeight * scanningScale;
|
||||
// Рисуем видео в canvas с пониженным разрешением
|
||||
ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height);
|
||||
try {
|
||||
const result = await QrScanner.scanImage(canvas, {
|
||||
returnDetailedScanResult: true,
|
||||
});
|
||||
if (result &&
|
||||
result.data &&
|
||||
result.cornerPoints?.length === 4 &&
|
||||
!ignoredQRCodes.current.has(result.data)) {
|
||||
// Сброс неудачных попыток при успешном обнаружении
|
||||
failedAttemptsRef.current = 0;
|
||||
// Преобразуем координаты из масштабированного canvas в координаты оригинального видео
|
||||
const points = result.cornerPoints.map(p => ({
|
||||
x: p.x / scanningScale,
|
||||
y: p.y / scanningScale,
|
||||
}));
|
||||
const minX = Math.min(...points.map(p => p.x));
|
||||
const minY = Math.min(...points.map(p => p.y));
|
||||
const maxX = Math.max(...points.map(p => p.x));
|
||||
const maxY = Math.max(...points.map(p => p.y));
|
||||
// Получаем размеры отображаемого видео
|
||||
const rect = videoEl.getBoundingClientRect();
|
||||
const displayScale = Math.min(rect.width / originalVideoWidth, rect.height / originalVideoHeight);
|
||||
const offsetX = (rect.width - originalVideoWidth * displayScale) / 2;
|
||||
const offsetY = (rect.height - originalVideoHeight * displayScale) / 2;
|
||||
const qrWidth = (maxX - minX) * displayScale;
|
||||
const qrHeight = (maxY - minY) * displayScale;
|
||||
const padding = 8;
|
||||
const x = minX * displayScale + offsetX - padding;
|
||||
const y = minY * displayScale + offsetY - padding;
|
||||
setDetectedQr({
|
||||
data: result.data,
|
||||
position: new DOMRect(x, y, qrWidth + padding * 2, qrHeight + padding * 2),
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Если код не найден, и он уже отображался, даем maxFailedAttempts попыток
|
||||
if (detectedQrRef.current) {
|
||||
failedAttemptsRef.current += 1;
|
||||
if (failedAttemptsRef.current >= maxFailedAttempts) {
|
||||
setDetectedQr(null);
|
||||
failedAttemptsRef.current = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// В случае ошибки аналогичная логика: если код уже отображался — даем maxFailedAttempts попыток
|
||||
if (detectedQrRef.current) {
|
||||
failedAttemptsRef.current += 1;
|
||||
if (failedAttemptsRef.current >= maxFailedAttempts) {
|
||||
setDetectedQr(null);
|
||||
failedAttemptsRef.current = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
isScanningRef.current = false;
|
||||
}
|
||||
animationFrameRef.current = requestAnimationFrame(scanFrame);
|
||||
}, [player, enabled, scanInterval, scanningScale, maxFailedAttempts]);
|
||||
useEffect(() => {
|
||||
if (!player || !enabled)
|
||||
return;
|
||||
const startScanning = () => {
|
||||
if (animationFrameRef.current)
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
animationFrameRef.current = requestAnimationFrame(scanFrame);
|
||||
};
|
||||
const stopScanning = () => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
animationFrameRef.current = null;
|
||||
}
|
||||
setDetectedQr(null);
|
||||
};
|
||||
player.on("play", startScanning);
|
||||
player.on("pause", stopScanning);
|
||||
player.on("ended", stopScanning);
|
||||
window.addEventListener("resize", startScanning);
|
||||
return () => {
|
||||
stopScanning();
|
||||
player.off("play", startScanning);
|
||||
player.off("pause", stopScanning);
|
||||
player.off("ended", stopScanning);
|
||||
window.removeEventListener("resize", startScanning);
|
||||
};
|
||||
}, [player, enabled, scanFrame]);
|
||||
const ignoreQRCode = () => {
|
||||
if (detectedQr) {
|
||||
ignoredQRCodes.current.add(detectedQr.data);
|
||||
setDetectedQr(null);
|
||||
}
|
||||
};
|
||||
return (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { display: "none" } }), detectedQr &&
|
||||
playerEl &&
|
||||
createPortal(_jsx(Popover, { getPopupContainer: () => playerEl, content: _jsx("a", { href: detectedQr.data, target: "_blank", rel: "noreferrer", referrerPolicy: "no-referrer", style: { color: "#1677ff" }, children: detectedQr.data }), placement: "top", children: _jsxs("div", { style: {
|
||||
position: "absolute",
|
||||
left: detectedQr.position.x,
|
||||
top: detectedQr.position.y,
|
||||
width: detectedQr.position.width,
|
||||
height: detectedQr.position.height,
|
||||
pointerEvents: "auto",
|
||||
cursor: "pointer",
|
||||
zIndex: 10,
|
||||
}, children: [["top-left", "top-right", "bottom-left", "bottom-right"].map(corner => (_jsx("div", { style: {
|
||||
position: "absolute",
|
||||
width: 20,
|
||||
height: 20,
|
||||
border: "3px solid var(--Accent-Primary)",
|
||||
borderRadius: 4,
|
||||
...(corner.includes("top") ? { top: 0 } : { bottom: 0 }),
|
||||
...(corner.includes("left") ? { left: 0 } : { right: 0 }),
|
||||
borderTop: corner.includes("bottom")
|
||||
? "none"
|
||||
: "3px solid var(--Accent-Primary)",
|
||||
borderBottom: corner.includes("top")
|
||||
? "none"
|
||||
: "3px solid var(--Accent-Primary)",
|
||||
borderLeft: corner.includes("right")
|
||||
? "none"
|
||||
: "3px solid var(--Accent-Primary)",
|
||||
borderRight: corner.includes("left")
|
||||
? "none"
|
||||
: "3px solid var(--Accent-Primary)",
|
||||
} }, corner))), _jsx(CloseOutlined, { style: {
|
||||
color: "#ff4d4f",
|
||||
position: "absolute",
|
||||
top: "-8px",
|
||||
right: "-8px",
|
||||
background: "white",
|
||||
borderRadius: "50%",
|
||||
padding: "2px",
|
||||
cursor: "pointer",
|
||||
}, onClick: ignoreQRCode })] }) }), playerEl)] }));
|
||||
};
|
||||
export default VideoQRScannerPlugin;
|
||||
//# sourceMappingURL=index.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/react/video-player/components/video-js/plugins/settings/index.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,+BAA+B,CAAC;AAEtD,OAAO,MAAM,MAAM,4BAA4B,CAAC;AAYhD,OAAO,gBAAgB,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,UAAU,qBAAqB;CAE9B;AAGD,UAAU,oBAAqB,SAAQ,aAAa;IACnD,UAAU,EAAE,SAAS,CAAC;CACtB;AAED,QAAA,MAAM,UAAU,EAAmC,OAAO,MAAM,CAAC;AAKjE,cAAM,cAAe,SAAQ,UAAU;IACtC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAa;IAGnC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,gBAAgB,CAA6B;gBAEzC,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,qBAAqB;IAMxE;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA8D5B;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAInB;;;;;;OAMG;YACW,OAAO;CA8CrB;AAID,eAAe,cAAc,CAAC"}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/react/video-player/components/video-js/plugins/settings/selectors/text-track-selector/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAS3D,UAAU,kBAAmB,SAAQ,yBAAyB;CAAG;AAEjE,QAAA,MAAM,UAAU,GACf,QAAQ,aAAa,EACrB,UAAS,kBAA6B;;;;;;;;;;;;;;;CAgDtC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../../../src/react/video-player/components/video-js/plugins/settings/selectors/text-track-selector/index.ts"],"names":[],"mappings":"AAGA,MAAM,QAAQ,GAAG;IAChB,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,KAAK;IACf,UAAU,EAAE,KAAK;IACjB,SAAS,EAAE,eAAe;CAC1B,CAAC;AAIF,MAAM,UAAU,GAAG,CAClB,MAAqB,EACrB,UAA8B,QAAQ,EACrC,EAAE;IACH,MAAM,SAAS,GAAG,GAAG,EAAE;QACtB,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC;QAC/C,MAAM,UAAU,GAAG,EAAE,CAAC;QAEtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YAC7C,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,SAAS;YACV,CAAC;YAED,MAAM,SAAS,GAAG;gBACjB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK;gBAC1B,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK;gBAC1B,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS;gBAC1C,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;aACtC,CAAC;YAEF,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,UAAU,CAAC;IACnB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,KAAgB,EAAE,EAAE;QACrC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC;QAE/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YAC7C,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,SAAS;YACV,CAAC;YAED,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK;gBAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,CAAC;;gBACnE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,UAAU,CAAC;QACtC,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,GAAG,EAAE;QACrB,OAAO;YACN,GAAG,OAAO;YACV,OAAO,EAAE,SAAS,EAAE,CAAC,MAAM,GAAG,CAAC;SAC/B,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACN,SAAS;QACT,QAAQ;KACR,CAAC;AACH,CAAC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
||||
@@ -1,51 +0,0 @@
|
||||
import Player from "video.js/dist/types/player";
|
||||
import { SkipButtonsOptions } from "./plugins/skip-buttons";
|
||||
export interface IVideoJSSource {
|
||||
src: string;
|
||||
type: string;
|
||||
}
|
||||
export type PreloadType = "auto" | "metadata" | "none" | "visibility";
|
||||
export interface IVideoJSOptions {
|
||||
autoplay: boolean;
|
||||
controls: boolean;
|
||||
responsive: boolean;
|
||||
aspectRatio?: string;
|
||||
preload: PreloadType;
|
||||
fluid: boolean;
|
||||
muted: boolean;
|
||||
sources: IVideoJSSource[];
|
||||
poster?: string;
|
||||
preferHQ?: boolean;
|
||||
/** Включить детальное логирование */
|
||||
debug?: boolean;
|
||||
}
|
||||
export interface IVideoJSProps {
|
||||
options: IVideoJSOptions;
|
||||
onReady?: (player: VideoJsPlayer) => void;
|
||||
className?: string;
|
||||
classNames?: string[];
|
||||
initialTime?: number;
|
||||
full?: boolean;
|
||||
withRewind?: boolean;
|
||||
}
|
||||
export interface VideoJsLiveTracker {
|
||||
isLive_: boolean;
|
||||
atLiveEdge?: () => boolean;
|
||||
startTracking: () => void;
|
||||
trigger: (event: string) => void;
|
||||
}
|
||||
export type VideoJsPlayer = Player & {
|
||||
liveTracker?: VideoJsLiveTracker;
|
||||
settingsMenu?: () => void;
|
||||
mobileUi?: () => void;
|
||||
bigPlayPauseButton?: () => void;
|
||||
skipButtons?: (options: SkipButtonsOptions) => void;
|
||||
subscribeToSegmentChange: (callback: (segment: any) => void) => void;
|
||||
subscribeToDuration: (callback: (duration: number) => void) => void;
|
||||
subscribeToPlayStart: (callback: () => void) => void;
|
||||
subscribeToPlayStarted: (callback: () => void) => void;
|
||||
subscribeToManifestLoaded: (callback: () => void) => void;
|
||||
mediaduration: () => number | undefined;
|
||||
textTracks: () => TextTrack[];
|
||||
};
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/react/video-player/components/video-js/types.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,4BAA4B,CAAC;AAEhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAGD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACpD,wBAAwB,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,CAAC;IACrE,mBAAmB,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;IACpE,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACrD,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACvD,yBAAyB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAC1D,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,UAAU,EAAE,MAAM,SAAS,EAAE,CAAC;CAC9B,CAAC"}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/react/video-player/components/with-errors/index.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAMrD,OAAO,UAAU,MAAM,eAAe,CAAC;AACvC,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAO/C,MAAM,UAAU,GAAG,CAAC,EACnB,UAAU,GAAG,KAAK,EAClB,QAAQ,EACR,GAAG,KAAK,EACqB,EAAE,EAAE;IAEjC,OAAO,CACN,eACC,SAAS,EAAE,MAAM,CAAC,SAAS,EAC3B,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,aAEhD,UAAU,IAAI,KAAC,UAAU,IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,GAAI,EACtD,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,IACtC,CACN,CAAC;AACH,CAAC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/react/video-player/components/with-mouse-events/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAIjC,OAAO,MAAM,MAAM,4BAA4B,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAiB,MAAM,mBAAmB,CAAC;AAGjE,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC3D,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/C,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,eAAe,GAAI,sEAO7B,qBAAqB,4CAkEvB,CAAC"}
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/react/video-player/components/with-observation/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAKjC,OAAO,MAAM,MAAM,4BAA4B,CAAC;AAGhD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IACnE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,eAAe,GAAI,iDAM7B,qBAAqB,4CAsDvB,CAAC"}
|
||||
43
docs/git-installation.md
Normal file
43
docs/git-installation.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Git installation (SSH + tags)
|
||||
|
||||
This repository is consumed directly from Git tags, not from npm.
|
||||
|
||||
## Requirements
|
||||
|
||||
- You have SSH access to the repository (`git@github.com:ORG/REPO.git`).
|
||||
- Release tags already exist in remote.
|
||||
- `dist/` is committed in the tagged commit.
|
||||
|
||||
## Tag naming
|
||||
|
||||
Use package-specific tags:
|
||||
|
||||
- `tach-typography-vX.Y.Z`
|
||||
- `video-player-vX.Y.Z`
|
||||
- `content-suggestions-vX.Y.Z`
|
||||
|
||||
## Yarn template
|
||||
|
||||
Always wrap the dependency string in quotes because of `&tag=`.
|
||||
|
||||
```bash
|
||||
yarn add "@hublib-web/<package>@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/<package>&tag=<package>-vX.Y.Z"
|
||||
```
|
||||
|
||||
## Package examples
|
||||
|
||||
```bash
|
||||
yarn add "@hublib-web/tach-typography@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/tach-typography&tag=tach-typography-v0.1.0"
|
||||
yarn add "@hublib-web/video-player@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/video-player&tag=video-player-v0.1.0"
|
||||
yarn add "@hublib-web/content-suggestions@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/content-suggestions&tag=content-suggestions-v0.1.0"
|
||||
```
|
||||
|
||||
## Update package version in a consumer app
|
||||
|
||||
```bash
|
||||
yarn up "@hublib-web/video-player@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/video-player&tag=video-player-v0.2.0"
|
||||
```
|
||||
|
||||
## Related docs
|
||||
|
||||
- Release policy: [release-policy.md](./release-policy.md)
|
||||
54
docs/release-policy.md
Normal file
54
docs/release-policy.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Release policy
|
||||
|
||||
This monorepo is released through Git tags. We do not use Changesets or npm publish.
|
||||
|
||||
## Principles
|
||||
|
||||
- Every release points to a commit where the package `dist/` is already built and committed.
|
||||
- Tags are package-specific, so each package can be released independently.
|
||||
- Consumers install by tag using Git SSH URLs.
|
||||
|
||||
## Tag format
|
||||
|
||||
- `tach-typography-vX.Y.Z`
|
||||
- `video-player-vX.Y.Z`
|
||||
- `content-suggestions-vX.Y.Z`
|
||||
|
||||
`X.Y.Z` must match the version in the package's `package.json`.
|
||||
|
||||
## Release checklist (single package)
|
||||
|
||||
1. Update package version in `packages/<package>/package.json`.
|
||||
2. Build package:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/<package> build
|
||||
```
|
||||
|
||||
3. Verify that `dist/` and `package.json` contain the release changes.
|
||||
4. Commit release files:
|
||||
|
||||
```bash
|
||||
git add packages/<package>/package.json packages/<package>/dist
|
||||
git commit -m "release(<package>): vX.Y.Z"
|
||||
```
|
||||
|
||||
5. Create annotated tag:
|
||||
|
||||
```bash
|
||||
git tag -a <package>-vX.Y.Z -m "@hublib-web/<package> vX.Y.Z"
|
||||
```
|
||||
|
||||
6. Push commit and tags:
|
||||
|
||||
```bash
|
||||
git push origin main --follow-tags
|
||||
```
|
||||
|
||||
## Multi-package release
|
||||
|
||||
If one commit releases several packages, create one tag per package on the same commit.
|
||||
|
||||
## Consumer installation
|
||||
|
||||
Use the package-specific tag in dependency spec. See [git-installation.md](./git-installation.md).
|
||||
21
eslint.config.mjs
Normal file
21
eslint.config.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
import js from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["**/dist/**", "**/node_modules/**"],
|
||||
},
|
||||
];
|
||||
5994
package-lock.json
generated
5994
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
95
package.json
95
package.json
@@ -1,88 +1,27 @@
|
||||
{
|
||||
"name": "@tach/video-player",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/react/index.js",
|
||||
"types": "./dist/react/index.d.ts",
|
||||
"sideEffects": true,
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
"name": "@hublib-web/root",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "node ./scripts/build.mjs",
|
||||
"typecheck": "tsc -p ./tsconfig.build.json --noEmit",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"prepack": "npm run build",
|
||||
"release:patch": "npm run build && npm version patch",
|
||||
"release:minor": "npm run build && npm version minor",
|
||||
"release:major": "npm run build && npm version major"
|
||||
},
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
"./core": {
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"import": "./dist/core/index.js",
|
||||
"default": "./dist/core/index.js"
|
||||
},
|
||||
"./react": {
|
||||
"types": "./dist/react/index.d.ts",
|
||||
"import": "./dist/react/index.js",
|
||||
"default": "./dist/react/index.js"
|
||||
},
|
||||
"./angular": {
|
||||
"types": "./dist/angular/index.d.ts",
|
||||
"import": "./dist/angular/index.js",
|
||||
"default": "./dist/angular/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "5.3.7",
|
||||
"antd": "5.14.2",
|
||||
"classnames": "2.5.1",
|
||||
"hls.js": "1.6.14",
|
||||
"lodash": "4.17.21",
|
||||
"qr-scanner": "1.4.2",
|
||||
"react-device-detect": "2.2.3",
|
||||
"video.js": "8.23.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
|
||||
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
|
||||
"@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/common": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/forms": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
"build": "yarn workspaces foreach -A -p --topological-dev run build",
|
||||
"typecheck": "yarn workspaces foreach -A -p --topological-dev run typecheck",
|
||||
"test": "yarn workspaces foreach -A -p --topological-dev run test",
|
||||
"clean": "yarn workspaces foreach -A -p --topological-dev run clean",
|
||||
"lint": "yarn workspaces foreach -A -p --topological-dev run lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/react": "8.6.14",
|
||||
"@storybook/react-vite": "8.6.14",
|
||||
"@types/node": "22.12.0",
|
||||
"@types/react": "19.0.2",
|
||||
"@types/react-dom": "19.0.2",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"sass": "1.83.4",
|
||||
"@types/node": "^24.6.1",
|
||||
"eslint": "^9.37.0",
|
||||
"prettier": "^3.6.2",
|
||||
"storybook": "8.6.14",
|
||||
"typescript": "5.9.2",
|
||||
"vite": "5.4.14"
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.0",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
18
packages/content-suggestions/.storybook/main.ts
Normal file
18
packages/content-suggestions/.storybook/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(ts|tsx)"],
|
||||
addons: ["@storybook/addon-essentials"],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
19
packages/content-suggestions/.storybook/preview.ts
Normal file
19
packages/content-suggestions/.storybook/preview.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
|
||||
import "antd/dist/reset.css";
|
||||
import "@hublib-web/tach-typography/styles.css";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
layout: "padded",
|
||||
controls: {
|
||||
expanded: true,
|
||||
sort: "requiredFirst",
|
||||
},
|
||||
actions: {
|
||||
argTypesRegex: "^on[A-Z].*",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
126
packages/content-suggestions/README.md
Normal file
126
packages/content-suggestions/README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# @hublib-web/content-suggestions
|
||||
|
||||
Cross-framework content text/title renderer with support for mentions, tags and links.
|
||||
|
||||
## Features
|
||||
|
||||
- Shared parser in `core` (`findAllEntities`, `findMentions`, `findTags`, `findLinks`).
|
||||
- React UI components in `react`:
|
||||
- `ContentText`
|
||||
- `ContentTextWithSuggestions`
|
||||
- `ContentTitleWithSuggestions`
|
||||
- Angular adapter in `angular` for rendering/tokenization integration.
|
||||
- Depends on `@hublib-web/tach-typography` for visual consistency.
|
||||
- Business logic (API requests for mentions/tags) stays in consumer application.
|
||||
|
||||
## Install from Git (SSH tag)
|
||||
|
||||
```bash
|
||||
yarn add "@hublib-web/content-suggestions@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/content-suggestions&tag=content-suggestions-v0.1.0"
|
||||
```
|
||||
|
||||
`@hublib-web/tach-typography` is a peer dependency, so install a compatible tag for it as well.
|
||||
|
||||
## Install inside this monorepo
|
||||
|
||||
```bash
|
||||
yarn add @hublib-web/content-suggestions
|
||||
```
|
||||
|
||||
## Release this package
|
||||
|
||||
1. Bump `version` in `packages/content-suggestions/package.json`.
|
||||
2. Build package artifacts:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/content-suggestions build
|
||||
```
|
||||
|
||||
3. Commit release files:
|
||||
|
||||
```bash
|
||||
git add packages/content-suggestions/package.json packages/content-suggestions/dist
|
||||
git commit -m "release(content-suggestions): v0.1.0"
|
||||
```
|
||||
|
||||
4. Create and push tag:
|
||||
|
||||
```bash
|
||||
git tag -a content-suggestions-v0.1.0 -m "@hublib-web/content-suggestions v0.1.0"
|
||||
git push origin main --follow-tags
|
||||
```
|
||||
|
||||
Detailed docs:
|
||||
|
||||
- [Release policy](../../docs/release-policy.md)
|
||||
- [Git installation](../../docs/git-installation.md)
|
||||
|
||||
## React usage
|
||||
|
||||
```tsx
|
||||
import { ContentTextWithSuggestions } from "@hublib-web/content-suggestions/react";
|
||||
|
||||
<ContentTextWithSuggestions text={text} />;
|
||||
```
|
||||
|
||||
With app-specific mention business logic:
|
||||
|
||||
```tsx
|
||||
<ContentTextWithSuggestions
|
||||
text={text}
|
||||
renderMention={(entity) => <MyMention entity={entity} />}
|
||||
renderTag={(entity) => <MyTagLink entity={entity} />}
|
||||
/>;
|
||||
```
|
||||
|
||||
By default, tags are rendered as plain styled text (not links).
|
||||
|
||||
## Angular usage
|
||||
|
||||
Tokenization helper:
|
||||
|
||||
```ts
|
||||
import { createAngularContentTokens } from "@hublib-web/content-suggestions/angular";
|
||||
|
||||
const tokens = createAngularContentTokens(text);
|
||||
```
|
||||
|
||||
Ready-to-use UI renderer (React-like props API):
|
||||
|
||||
```ts
|
||||
import { AngularContentTextWithSuggestionsRenderer } from "@hublib-web/content-suggestions/angular";
|
||||
|
||||
const renderer = new AngularContentTextWithSuggestionsRenderer();
|
||||
renderer.attach(containerElement, {
|
||||
text,
|
||||
weight: "normal",
|
||||
ellipsis: { count: 180, expandable: true },
|
||||
blur: false,
|
||||
onView: () => {
|
||||
// analytics
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Title variant with defaults (`weight: "bold"`, `ellipsis: { rows: 2 }`):
|
||||
|
||||
```ts
|
||||
import { AngularContentTitleWithSuggestionsRenderer } from "@hublib-web/content-suggestions/angular";
|
||||
|
||||
const titleRenderer = new AngularContentTitleWithSuggestionsRenderer();
|
||||
titleRenderer.attach(titleContainer, { text: title });
|
||||
```
|
||||
|
||||
## Storybook (dev/design system)
|
||||
|
||||
Run from repository root:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/content-suggestions storybook
|
||||
```
|
||||
|
||||
Build static Storybook:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/content-suggestions storybook:build
|
||||
```
|
||||
492
packages/content-suggestions/dist/angular/index.cjs
vendored
Normal file
492
packages/content-suggestions/dist/angular/index.cjs
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
'use strict';
|
||||
|
||||
// src/core/parser.ts
|
||||
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
var parseMention = (mention) => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId
|
||||
};
|
||||
};
|
||||
var findMentions = (text) => {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0]
|
||||
};
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
var findTags = (content) => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", "")
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findLinks = (content) => {
|
||||
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link"
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findAllEntities = (content) => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
|
||||
// src/angular/index.ts
|
||||
var LINK_COLOR = "#1677ff";
|
||||
var READ_MORE_TEXT = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E";
|
||||
var toKebabCase = (value) => value.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
||||
var applyStyleObject = (element, styles) => {
|
||||
if (!styles) {
|
||||
return;
|
||||
}
|
||||
for (const [key, rawValue] of Object.entries(styles)) {
|
||||
if (rawValue === null || rawValue === void 0) {
|
||||
continue;
|
||||
}
|
||||
const styleValue = typeof rawValue === "number" ? `${rawValue}px` : String(rawValue);
|
||||
if (key.startsWith("--")) {
|
||||
element.style.setProperty(key, styleValue);
|
||||
continue;
|
||||
}
|
||||
const cssKey = key.includes("-") ? key : toKebabCase(key);
|
||||
element.style.setProperty(cssKey, styleValue);
|
||||
}
|
||||
};
|
||||
var toNode = (value) => {
|
||||
if (value === null || value === void 0) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Node) {
|
||||
return value;
|
||||
}
|
||||
return document.createTextNode(String(value));
|
||||
};
|
||||
var resolveEllipsisSymbol = (symbol, expanded) => {
|
||||
if (typeof symbol === "function") {
|
||||
return symbol(expanded);
|
||||
}
|
||||
return symbol ?? READ_MORE_TEXT;
|
||||
};
|
||||
var buildAngularTagHref = (entity) => {
|
||||
return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;
|
||||
};
|
||||
var createAngularContentTokens = (inputText) => {
|
||||
const text = inputText ?? "";
|
||||
const entities = findAllEntities(text);
|
||||
let cursor = 0;
|
||||
const tokens = [];
|
||||
for (const entity of entities) {
|
||||
if (entity.start > cursor) {
|
||||
tokens.push({
|
||||
kind: "text",
|
||||
text: text.slice(cursor, entity.start),
|
||||
start: cursor,
|
||||
end: entity.start
|
||||
});
|
||||
}
|
||||
if (entity.type === "mention") {
|
||||
tokens.push({
|
||||
kind: "mention",
|
||||
entity
|
||||
});
|
||||
} else if (entity.type === "tag") {
|
||||
tokens.push({
|
||||
kind: "tag",
|
||||
entity
|
||||
});
|
||||
} else {
|
||||
tokens.push({
|
||||
kind: "link",
|
||||
entity
|
||||
});
|
||||
}
|
||||
cursor = entity.end;
|
||||
}
|
||||
if (cursor < text.length) {
|
||||
tokens.push({
|
||||
kind: "text",
|
||||
text: text.slice(cursor),
|
||||
start: cursor,
|
||||
end: text.length
|
||||
});
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
var AngularContentSuggestionsAdapter = class {
|
||||
snapshot(inputText) {
|
||||
const text = inputText ?? "";
|
||||
const entities = findAllEntities(text);
|
||||
const tokens = createAngularContentTokens(text);
|
||||
return {
|
||||
text,
|
||||
entities,
|
||||
tokens
|
||||
};
|
||||
}
|
||||
};
|
||||
var AngularContentTextRenderer = class {
|
||||
host = null;
|
||||
props = {};
|
||||
expanded = false;
|
||||
viewed = false;
|
||||
hostInsideView = false;
|
||||
observer = null;
|
||||
attach(host, props = {}) {
|
||||
this.destroy();
|
||||
this.host = host;
|
||||
this.props = { ...props };
|
||||
this.expanded = false;
|
||||
this.viewed = false;
|
||||
this.hostInsideView = false;
|
||||
this.initObserver();
|
||||
this.render();
|
||||
return this.getState();
|
||||
}
|
||||
update(nextProps) {
|
||||
this.props = {
|
||||
...this.props,
|
||||
...nextProps
|
||||
};
|
||||
this.render();
|
||||
return this.getState();
|
||||
}
|
||||
destroy() {
|
||||
if (this.observer && this.host) {
|
||||
this.observer.unobserve(this.host);
|
||||
this.observer.disconnect();
|
||||
}
|
||||
this.observer = null;
|
||||
if (this.host) {
|
||||
this.host.innerHTML = "";
|
||||
}
|
||||
this.host = null;
|
||||
this.props = {};
|
||||
this.expanded = false;
|
||||
this.viewed = false;
|
||||
this.hostInsideView = false;
|
||||
}
|
||||
getState() {
|
||||
return {
|
||||
props: { ...this.props },
|
||||
snapshot: this.createSnapshot(),
|
||||
expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText())
|
||||
};
|
||||
}
|
||||
getText() {
|
||||
return this.props.text ?? "";
|
||||
}
|
||||
createSnapshot() {
|
||||
const text = this.getText();
|
||||
const entities = findAllEntities(text);
|
||||
const tokens = createAngularContentTokens(text);
|
||||
return {
|
||||
text,
|
||||
entities,
|
||||
tokens
|
||||
};
|
||||
}
|
||||
resolveEllipsisConfig() {
|
||||
const ellipsis = this.props.ellipsis;
|
||||
if (!ellipsis || typeof ellipsis !== "object") {
|
||||
return null;
|
||||
}
|
||||
return ellipsis;
|
||||
}
|
||||
getMergedExpanded(ellipsisConfig, text) {
|
||||
const controlledExpanded = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0;
|
||||
if (controlledExpanded !== void 0) {
|
||||
return controlledExpanded;
|
||||
}
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
if (count <= 0 || text.length <= count) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this.expanded;
|
||||
}
|
||||
createDefaultMentionNode(entity) {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = LINK_COLOR;
|
||||
span.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
span.textContent = entity.displayText;
|
||||
return span;
|
||||
}
|
||||
createDefaultTagNode(entity) {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = LINK_COLOR;
|
||||
span.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
span.textContent = entity.text;
|
||||
return span;
|
||||
}
|
||||
createDefaultLinkNode(entity) {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = entity.url;
|
||||
anchor.target = "_blank";
|
||||
anchor.referrerPolicy = "no-referrer";
|
||||
anchor.style.color = LINK_COLOR;
|
||||
anchor.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
anchor.textContent = entity.text;
|
||||
return anchor;
|
||||
}
|
||||
renderEntity(entity, index) {
|
||||
if (entity.type === "mention") {
|
||||
const customNode2 = this.props.renderMention?.(entity, index);
|
||||
return toNode(customNode2) ?? this.createDefaultMentionNode(entity);
|
||||
}
|
||||
if (entity.type === "tag") {
|
||||
const customNode2 = this.props.renderTag?.(entity, index);
|
||||
return toNode(customNode2) ?? this.createDefaultTagNode(entity);
|
||||
}
|
||||
const customNode = this.props.renderLink?.(entity, index);
|
||||
return toNode(customNode) ?? this.createDefaultLinkNode(entity);
|
||||
}
|
||||
buildTextNodes(text, entities, upto = null) {
|
||||
let lastIndex = 0;
|
||||
const nodes = [];
|
||||
for (const [index, entity] of entities.entries()) {
|
||||
if (upto !== null && entity.start >= upto) {
|
||||
break;
|
||||
}
|
||||
const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;
|
||||
if (entity.start > lastIndex && lastIndex < textEnd) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex, textEnd)));
|
||||
}
|
||||
if (upto === null || entity.end <= upto) {
|
||||
const entityNode = this.renderEntity(entity, index);
|
||||
if (entityNode) {
|
||||
nodes.push(entityNode);
|
||||
}
|
||||
}
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
if (upto === null) {
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex)));
|
||||
}
|
||||
} else if (lastIndex < upto) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex, upto)));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
applyBaseParagraphStyle(element) {
|
||||
element.style.whiteSpace = "pre-wrap";
|
||||
element.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
element.style.setProperty("-webkit-touch-callout", "default");
|
||||
element.style.setProperty("-webkit-user-select", "text");
|
||||
element.style.setProperty("-khtml-user-select", "text");
|
||||
element.style.setProperty("-moz-user-select", "text");
|
||||
element.style.setProperty("-ms-user-select", "text");
|
||||
element.style.setProperty("user-select", "text");
|
||||
if (this.props.blur) {
|
||||
element.style.filter = "blur(3px)";
|
||||
element.style.setProperty("-webkit-user-select", "none");
|
||||
element.style.setProperty("-khtml-user-select", "none");
|
||||
element.style.setProperty("-moz-user-select", "none");
|
||||
element.style.setProperty("-ms-user-select", "none");
|
||||
element.style.setProperty("user-select", "none");
|
||||
element.style.pointerEvents = "none";
|
||||
}
|
||||
applyStyleObject(element, this.props.style);
|
||||
}
|
||||
buildReadMoreButton(symbol) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = resolveEllipsisSymbol(symbol, this.expanded);
|
||||
button.style.border = "0";
|
||||
button.style.background = "none";
|
||||
button.style.padding = "0";
|
||||
button.style.marginLeft = "6px";
|
||||
button.style.cursor = "pointer";
|
||||
button.style.color = LINK_COLOR;
|
||||
button.style.fontWeight = "700";
|
||||
button.onclick = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.handleExpand();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
handleExpand() {
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
if (!ellipsisConfig) {
|
||||
return;
|
||||
}
|
||||
this.expanded = true;
|
||||
ellipsisConfig.onExpand?.(true);
|
||||
this.render();
|
||||
}
|
||||
computeEntitySafeCutoff(count, entities) {
|
||||
let cutoff = count;
|
||||
let extended = true;
|
||||
while (extended) {
|
||||
extended = false;
|
||||
for (const entity of entities) {
|
||||
if (entity.start < cutoff && entity.end > cutoff) {
|
||||
cutoff = entity.end;
|
||||
extended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cutoff;
|
||||
}
|
||||
render() {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
const text = this.getText();
|
||||
const entities = findAllEntities(text);
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text);
|
||||
const wrapper = document.createElement("div");
|
||||
const paragraph = document.createElement("div");
|
||||
if (this.props.className) {
|
||||
paragraph.className = this.props.className;
|
||||
}
|
||||
this.applyBaseParagraphStyle(paragraph);
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
const shouldTruncate = !mergedExpanded && count > 0 && text.length > count;
|
||||
if (shouldTruncate) {
|
||||
const cutoff = this.computeEntitySafeCutoff(count, entities);
|
||||
const nodes = this.buildTextNodes(text, entities, cutoff);
|
||||
paragraph.append(...nodes);
|
||||
paragraph.append(document.createTextNode("\u2026"));
|
||||
if (ellipsisConfig.expandable) {
|
||||
paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol));
|
||||
}
|
||||
} else {
|
||||
paragraph.append(...this.buildTextNodes(text, entities));
|
||||
}
|
||||
} else {
|
||||
paragraph.append(...this.buildTextNodes(text, entities));
|
||||
if (ellipsisConfig && "rows" in ellipsisConfig && !mergedExpanded) {
|
||||
paragraph.style.display = "-webkit-box";
|
||||
paragraph.style.setProperty("-webkit-box-orient", "vertical");
|
||||
paragraph.style.setProperty("-webkit-line-clamp", String(ellipsisConfig.rows));
|
||||
paragraph.style.overflow = "hidden";
|
||||
if (ellipsisConfig.expandable) {
|
||||
wrapper.append(paragraph);
|
||||
wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol));
|
||||
this.host.replaceChildren(wrapper);
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
wrapper.append(paragraph);
|
||||
this.host.replaceChildren(wrapper);
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
}
|
||||
initObserver() {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
if (typeof IntersectionObserver === "undefined") {
|
||||
this.hostInsideView = true;
|
||||
return;
|
||||
}
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
if (entry.isIntersecting && !this.hostInsideView) {
|
||||
this.hostInsideView = true;
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText());
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
this.observer.observe(this.host);
|
||||
}
|
||||
emitOnViewIfNeeded(mergedExpanded) {
|
||||
if (!mergedExpanded || this.viewed || !this.hostInsideView) {
|
||||
return;
|
||||
}
|
||||
this.viewed = true;
|
||||
this.props.onView?.();
|
||||
}
|
||||
};
|
||||
var AngularContentTextWithSuggestionsRenderer = class extends AngularContentTextRenderer {
|
||||
};
|
||||
var AngularContentTitleWithSuggestionsRenderer = class {
|
||||
renderer = new AngularContentTextWithSuggestionsRenderer();
|
||||
attach(host, props = {}) {
|
||||
return this.renderer.attach(host, this.normalizeProps(props));
|
||||
}
|
||||
update(props) {
|
||||
return this.renderer.update(this.normalizeProps(props));
|
||||
}
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
}
|
||||
getState() {
|
||||
return this.renderer.getState();
|
||||
}
|
||||
normalizeProps(props) {
|
||||
return {
|
||||
...props,
|
||||
weight: "bold",
|
||||
blur: props.blur ?? false,
|
||||
ellipsis: props.ellipsis === void 0 ? { rows: 2 } : props.ellipsis
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
exports.AngularContentSuggestionsAdapter = AngularContentSuggestionsAdapter;
|
||||
exports.AngularContentTextRenderer = AngularContentTextRenderer;
|
||||
exports.AngularContentTextWithSuggestionsRenderer = AngularContentTextWithSuggestionsRenderer;
|
||||
exports.AngularContentTitleWithSuggestionsRenderer = AngularContentTitleWithSuggestionsRenderer;
|
||||
exports.buildAngularTagHref = buildAngularTagHref;
|
||||
exports.createAngularContentTokens = createAngularContentTokens;
|
||||
exports.toKebabCase = toKebabCase;
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
packages/content-suggestions/dist/angular/index.cjs.map
vendored
Normal file
1
packages/content-suggestions/dist/angular/index.cjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
117
packages/content-suggestions/dist/angular/index.d.cts
vendored
Normal file
117
packages/content-suggestions/dist/angular/index.d.cts
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
import { C as ContentEntity, M as MentionEntity, T as TagEntity, L as LinkEntity } from '../types-BRt4hd7A.cjs';
|
||||
|
||||
interface AngularTextToken {
|
||||
kind: "text";
|
||||
text: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
interface AngularMentionToken {
|
||||
kind: "mention";
|
||||
entity: MentionEntity;
|
||||
}
|
||||
interface AngularTagToken {
|
||||
kind: "tag";
|
||||
entity: TagEntity;
|
||||
}
|
||||
interface AngularLinkToken {
|
||||
kind: "link";
|
||||
entity: LinkEntity;
|
||||
}
|
||||
type AngularContentToken = AngularTextToken | AngularMentionToken | AngularTagToken | AngularLinkToken;
|
||||
interface AngularContentSnapshot {
|
||||
text: string;
|
||||
entities: ContentEntity[];
|
||||
tokens: AngularContentToken[];
|
||||
}
|
||||
type AngularEllipsisSymbol = string | ((expanded: boolean) => string);
|
||||
type AngularCountEllipsisConfig = {
|
||||
count: number;
|
||||
rows?: never;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
symbol?: AngularEllipsisSymbol;
|
||||
onExpand?: (expanded: boolean) => void;
|
||||
};
|
||||
type AngularRowsEllipsisConfig = {
|
||||
rows: number;
|
||||
count?: never;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
symbol?: AngularEllipsisSymbol;
|
||||
onExpand?: (expanded: boolean) => void;
|
||||
};
|
||||
type AngularContentEllipsisConfig = AngularCountEllipsisConfig | AngularRowsEllipsisConfig | false;
|
||||
type AngularRenderResult = Node | string | number | null | undefined;
|
||||
type AngularMentionRenderer = (entity: MentionEntity, index: number) => AngularRenderResult;
|
||||
type AngularTagRenderer = (entity: TagEntity, index: number) => AngularRenderResult;
|
||||
type AngularLinkRenderer = (entity: LinkEntity, index: number) => AngularRenderResult;
|
||||
interface AngularContentTextProps {
|
||||
className?: string;
|
||||
weight?: "normal" | "bold";
|
||||
text?: string | null;
|
||||
ellipsis?: AngularContentEllipsisConfig;
|
||||
blur?: boolean;
|
||||
style?: Record<string, string | number> | null;
|
||||
onView?: () => void;
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
renderLink?: AngularLinkRenderer;
|
||||
}
|
||||
type AngularContentTextWithSuggestionsProps = Omit<AngularContentTextProps, "renderMention" | "renderTag"> & {
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
};
|
||||
type AngularContentTitleWithSuggestionsProps = Omit<AngularContentTextWithSuggestionsProps, "weight">;
|
||||
interface AngularContentTextRendererState {
|
||||
props: AngularContentTextProps;
|
||||
snapshot: AngularContentSnapshot;
|
||||
expanded: boolean;
|
||||
}
|
||||
declare const toKebabCase: (value: string) => string;
|
||||
declare const buildAngularTagHref: (entity: TagEntity) => string;
|
||||
|
||||
declare const createAngularContentTokens: (inputText: string | null | undefined) => AngularContentToken[];
|
||||
declare class AngularContentSuggestionsAdapter {
|
||||
snapshot(inputText: string | null | undefined): AngularContentSnapshot;
|
||||
}
|
||||
declare class AngularContentTextRenderer {
|
||||
private host;
|
||||
private props;
|
||||
private expanded;
|
||||
private viewed;
|
||||
private hostInsideView;
|
||||
private observer;
|
||||
attach(host: HTMLElement, props?: AngularContentTextProps): AngularContentTextRendererState;
|
||||
update(nextProps: AngularContentTextProps): AngularContentTextRendererState;
|
||||
destroy(): void;
|
||||
getState(): AngularContentTextRendererState;
|
||||
private getText;
|
||||
private createSnapshot;
|
||||
private resolveEllipsisConfig;
|
||||
private getMergedExpanded;
|
||||
private createDefaultMentionNode;
|
||||
private createDefaultTagNode;
|
||||
private createDefaultLinkNode;
|
||||
private renderEntity;
|
||||
private buildTextNodes;
|
||||
private applyBaseParagraphStyle;
|
||||
private buildReadMoreButton;
|
||||
private handleExpand;
|
||||
private computeEntitySafeCutoff;
|
||||
private render;
|
||||
private initObserver;
|
||||
private emitOnViewIfNeeded;
|
||||
}
|
||||
declare class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer {
|
||||
}
|
||||
declare class AngularContentTitleWithSuggestionsRenderer {
|
||||
private readonly renderer;
|
||||
attach(host: HTMLElement, props?: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState;
|
||||
update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState;
|
||||
destroy(): void;
|
||||
getState(): AngularContentTextRendererState;
|
||||
private normalizeProps;
|
||||
}
|
||||
|
||||
export { type AngularContentEllipsisConfig, type AngularContentSnapshot, AngularContentSuggestionsAdapter, type AngularContentTextProps, AngularContentTextRenderer, type AngularContentTextRendererState, type AngularContentTextWithSuggestionsProps, AngularContentTextWithSuggestionsRenderer, type AngularContentTitleWithSuggestionsProps, AngularContentTitleWithSuggestionsRenderer, type AngularContentToken, type AngularCountEllipsisConfig, type AngularEllipsisSymbol, type AngularLinkRenderer, type AngularLinkToken, type AngularMentionRenderer, type AngularMentionToken, type AngularRenderResult, type AngularRowsEllipsisConfig, type AngularTagRenderer, type AngularTagToken, type AngularTextToken, buildAngularTagHref, createAngularContentTokens, toKebabCase };
|
||||
117
packages/content-suggestions/dist/angular/index.d.ts
vendored
Normal file
117
packages/content-suggestions/dist/angular/index.d.ts
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
import { C as ContentEntity, M as MentionEntity, T as TagEntity, L as LinkEntity } from '../types-BRt4hd7A.js';
|
||||
|
||||
interface AngularTextToken {
|
||||
kind: "text";
|
||||
text: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
interface AngularMentionToken {
|
||||
kind: "mention";
|
||||
entity: MentionEntity;
|
||||
}
|
||||
interface AngularTagToken {
|
||||
kind: "tag";
|
||||
entity: TagEntity;
|
||||
}
|
||||
interface AngularLinkToken {
|
||||
kind: "link";
|
||||
entity: LinkEntity;
|
||||
}
|
||||
type AngularContentToken = AngularTextToken | AngularMentionToken | AngularTagToken | AngularLinkToken;
|
||||
interface AngularContentSnapshot {
|
||||
text: string;
|
||||
entities: ContentEntity[];
|
||||
tokens: AngularContentToken[];
|
||||
}
|
||||
type AngularEllipsisSymbol = string | ((expanded: boolean) => string);
|
||||
type AngularCountEllipsisConfig = {
|
||||
count: number;
|
||||
rows?: never;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
symbol?: AngularEllipsisSymbol;
|
||||
onExpand?: (expanded: boolean) => void;
|
||||
};
|
||||
type AngularRowsEllipsisConfig = {
|
||||
rows: number;
|
||||
count?: never;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
symbol?: AngularEllipsisSymbol;
|
||||
onExpand?: (expanded: boolean) => void;
|
||||
};
|
||||
type AngularContentEllipsisConfig = AngularCountEllipsisConfig | AngularRowsEllipsisConfig | false;
|
||||
type AngularRenderResult = Node | string | number | null | undefined;
|
||||
type AngularMentionRenderer = (entity: MentionEntity, index: number) => AngularRenderResult;
|
||||
type AngularTagRenderer = (entity: TagEntity, index: number) => AngularRenderResult;
|
||||
type AngularLinkRenderer = (entity: LinkEntity, index: number) => AngularRenderResult;
|
||||
interface AngularContentTextProps {
|
||||
className?: string;
|
||||
weight?: "normal" | "bold";
|
||||
text?: string | null;
|
||||
ellipsis?: AngularContentEllipsisConfig;
|
||||
blur?: boolean;
|
||||
style?: Record<string, string | number> | null;
|
||||
onView?: () => void;
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
renderLink?: AngularLinkRenderer;
|
||||
}
|
||||
type AngularContentTextWithSuggestionsProps = Omit<AngularContentTextProps, "renderMention" | "renderTag"> & {
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
};
|
||||
type AngularContentTitleWithSuggestionsProps = Omit<AngularContentTextWithSuggestionsProps, "weight">;
|
||||
interface AngularContentTextRendererState {
|
||||
props: AngularContentTextProps;
|
||||
snapshot: AngularContentSnapshot;
|
||||
expanded: boolean;
|
||||
}
|
||||
declare const toKebabCase: (value: string) => string;
|
||||
declare const buildAngularTagHref: (entity: TagEntity) => string;
|
||||
|
||||
declare const createAngularContentTokens: (inputText: string | null | undefined) => AngularContentToken[];
|
||||
declare class AngularContentSuggestionsAdapter {
|
||||
snapshot(inputText: string | null | undefined): AngularContentSnapshot;
|
||||
}
|
||||
declare class AngularContentTextRenderer {
|
||||
private host;
|
||||
private props;
|
||||
private expanded;
|
||||
private viewed;
|
||||
private hostInsideView;
|
||||
private observer;
|
||||
attach(host: HTMLElement, props?: AngularContentTextProps): AngularContentTextRendererState;
|
||||
update(nextProps: AngularContentTextProps): AngularContentTextRendererState;
|
||||
destroy(): void;
|
||||
getState(): AngularContentTextRendererState;
|
||||
private getText;
|
||||
private createSnapshot;
|
||||
private resolveEllipsisConfig;
|
||||
private getMergedExpanded;
|
||||
private createDefaultMentionNode;
|
||||
private createDefaultTagNode;
|
||||
private createDefaultLinkNode;
|
||||
private renderEntity;
|
||||
private buildTextNodes;
|
||||
private applyBaseParagraphStyle;
|
||||
private buildReadMoreButton;
|
||||
private handleExpand;
|
||||
private computeEntitySafeCutoff;
|
||||
private render;
|
||||
private initObserver;
|
||||
private emitOnViewIfNeeded;
|
||||
}
|
||||
declare class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer {
|
||||
}
|
||||
declare class AngularContentTitleWithSuggestionsRenderer {
|
||||
private readonly renderer;
|
||||
attach(host: HTMLElement, props?: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState;
|
||||
update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState;
|
||||
destroy(): void;
|
||||
getState(): AngularContentTextRendererState;
|
||||
private normalizeProps;
|
||||
}
|
||||
|
||||
export { type AngularContentEllipsisConfig, type AngularContentSnapshot, AngularContentSuggestionsAdapter, type AngularContentTextProps, AngularContentTextRenderer, type AngularContentTextRendererState, type AngularContentTextWithSuggestionsProps, AngularContentTextWithSuggestionsRenderer, type AngularContentTitleWithSuggestionsProps, AngularContentTitleWithSuggestionsRenderer, type AngularContentToken, type AngularCountEllipsisConfig, type AngularEllipsisSymbol, type AngularLinkRenderer, type AngularLinkToken, type AngularMentionRenderer, type AngularMentionToken, type AngularRenderResult, type AngularRowsEllipsisConfig, type AngularTagRenderer, type AngularTagToken, type AngularTextToken, buildAngularTagHref, createAngularContentTokens, toKebabCase };
|
||||
484
packages/content-suggestions/dist/angular/index.js
vendored
Normal file
484
packages/content-suggestions/dist/angular/index.js
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
// src/core/parser.ts
|
||||
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
var parseMention = (mention) => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId
|
||||
};
|
||||
};
|
||||
var findMentions = (text) => {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0]
|
||||
};
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
var findTags = (content) => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", "")
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findLinks = (content) => {
|
||||
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link"
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findAllEntities = (content) => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
|
||||
// src/angular/index.ts
|
||||
var LINK_COLOR = "#1677ff";
|
||||
var READ_MORE_TEXT = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E";
|
||||
var toKebabCase = (value) => value.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
||||
var applyStyleObject = (element, styles) => {
|
||||
if (!styles) {
|
||||
return;
|
||||
}
|
||||
for (const [key, rawValue] of Object.entries(styles)) {
|
||||
if (rawValue === null || rawValue === void 0) {
|
||||
continue;
|
||||
}
|
||||
const styleValue = typeof rawValue === "number" ? `${rawValue}px` : String(rawValue);
|
||||
if (key.startsWith("--")) {
|
||||
element.style.setProperty(key, styleValue);
|
||||
continue;
|
||||
}
|
||||
const cssKey = key.includes("-") ? key : toKebabCase(key);
|
||||
element.style.setProperty(cssKey, styleValue);
|
||||
}
|
||||
};
|
||||
var toNode = (value) => {
|
||||
if (value === null || value === void 0) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Node) {
|
||||
return value;
|
||||
}
|
||||
return document.createTextNode(String(value));
|
||||
};
|
||||
var resolveEllipsisSymbol = (symbol, expanded) => {
|
||||
if (typeof symbol === "function") {
|
||||
return symbol(expanded);
|
||||
}
|
||||
return symbol ?? READ_MORE_TEXT;
|
||||
};
|
||||
var buildAngularTagHref = (entity) => {
|
||||
return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;
|
||||
};
|
||||
var createAngularContentTokens = (inputText) => {
|
||||
const text = inputText ?? "";
|
||||
const entities = findAllEntities(text);
|
||||
let cursor = 0;
|
||||
const tokens = [];
|
||||
for (const entity of entities) {
|
||||
if (entity.start > cursor) {
|
||||
tokens.push({
|
||||
kind: "text",
|
||||
text: text.slice(cursor, entity.start),
|
||||
start: cursor,
|
||||
end: entity.start
|
||||
});
|
||||
}
|
||||
if (entity.type === "mention") {
|
||||
tokens.push({
|
||||
kind: "mention",
|
||||
entity
|
||||
});
|
||||
} else if (entity.type === "tag") {
|
||||
tokens.push({
|
||||
kind: "tag",
|
||||
entity
|
||||
});
|
||||
} else {
|
||||
tokens.push({
|
||||
kind: "link",
|
||||
entity
|
||||
});
|
||||
}
|
||||
cursor = entity.end;
|
||||
}
|
||||
if (cursor < text.length) {
|
||||
tokens.push({
|
||||
kind: "text",
|
||||
text: text.slice(cursor),
|
||||
start: cursor,
|
||||
end: text.length
|
||||
});
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
var AngularContentSuggestionsAdapter = class {
|
||||
snapshot(inputText) {
|
||||
const text = inputText ?? "";
|
||||
const entities = findAllEntities(text);
|
||||
const tokens = createAngularContentTokens(text);
|
||||
return {
|
||||
text,
|
||||
entities,
|
||||
tokens
|
||||
};
|
||||
}
|
||||
};
|
||||
var AngularContentTextRenderer = class {
|
||||
host = null;
|
||||
props = {};
|
||||
expanded = false;
|
||||
viewed = false;
|
||||
hostInsideView = false;
|
||||
observer = null;
|
||||
attach(host, props = {}) {
|
||||
this.destroy();
|
||||
this.host = host;
|
||||
this.props = { ...props };
|
||||
this.expanded = false;
|
||||
this.viewed = false;
|
||||
this.hostInsideView = false;
|
||||
this.initObserver();
|
||||
this.render();
|
||||
return this.getState();
|
||||
}
|
||||
update(nextProps) {
|
||||
this.props = {
|
||||
...this.props,
|
||||
...nextProps
|
||||
};
|
||||
this.render();
|
||||
return this.getState();
|
||||
}
|
||||
destroy() {
|
||||
if (this.observer && this.host) {
|
||||
this.observer.unobserve(this.host);
|
||||
this.observer.disconnect();
|
||||
}
|
||||
this.observer = null;
|
||||
if (this.host) {
|
||||
this.host.innerHTML = "";
|
||||
}
|
||||
this.host = null;
|
||||
this.props = {};
|
||||
this.expanded = false;
|
||||
this.viewed = false;
|
||||
this.hostInsideView = false;
|
||||
}
|
||||
getState() {
|
||||
return {
|
||||
props: { ...this.props },
|
||||
snapshot: this.createSnapshot(),
|
||||
expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText())
|
||||
};
|
||||
}
|
||||
getText() {
|
||||
return this.props.text ?? "";
|
||||
}
|
||||
createSnapshot() {
|
||||
const text = this.getText();
|
||||
const entities = findAllEntities(text);
|
||||
const tokens = createAngularContentTokens(text);
|
||||
return {
|
||||
text,
|
||||
entities,
|
||||
tokens
|
||||
};
|
||||
}
|
||||
resolveEllipsisConfig() {
|
||||
const ellipsis = this.props.ellipsis;
|
||||
if (!ellipsis || typeof ellipsis !== "object") {
|
||||
return null;
|
||||
}
|
||||
return ellipsis;
|
||||
}
|
||||
getMergedExpanded(ellipsisConfig, text) {
|
||||
const controlledExpanded = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0;
|
||||
if (controlledExpanded !== void 0) {
|
||||
return controlledExpanded;
|
||||
}
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
if (count <= 0 || text.length <= count) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this.expanded;
|
||||
}
|
||||
createDefaultMentionNode(entity) {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = LINK_COLOR;
|
||||
span.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
span.textContent = entity.displayText;
|
||||
return span;
|
||||
}
|
||||
createDefaultTagNode(entity) {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = LINK_COLOR;
|
||||
span.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
span.textContent = entity.text;
|
||||
return span;
|
||||
}
|
||||
createDefaultLinkNode(entity) {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = entity.url;
|
||||
anchor.target = "_blank";
|
||||
anchor.referrerPolicy = "no-referrer";
|
||||
anchor.style.color = LINK_COLOR;
|
||||
anchor.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
anchor.textContent = entity.text;
|
||||
return anchor;
|
||||
}
|
||||
renderEntity(entity, index) {
|
||||
if (entity.type === "mention") {
|
||||
const customNode2 = this.props.renderMention?.(entity, index);
|
||||
return toNode(customNode2) ?? this.createDefaultMentionNode(entity);
|
||||
}
|
||||
if (entity.type === "tag") {
|
||||
const customNode2 = this.props.renderTag?.(entity, index);
|
||||
return toNode(customNode2) ?? this.createDefaultTagNode(entity);
|
||||
}
|
||||
const customNode = this.props.renderLink?.(entity, index);
|
||||
return toNode(customNode) ?? this.createDefaultLinkNode(entity);
|
||||
}
|
||||
buildTextNodes(text, entities, upto = null) {
|
||||
let lastIndex = 0;
|
||||
const nodes = [];
|
||||
for (const [index, entity] of entities.entries()) {
|
||||
if (upto !== null && entity.start >= upto) {
|
||||
break;
|
||||
}
|
||||
const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;
|
||||
if (entity.start > lastIndex && lastIndex < textEnd) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex, textEnd)));
|
||||
}
|
||||
if (upto === null || entity.end <= upto) {
|
||||
const entityNode = this.renderEntity(entity, index);
|
||||
if (entityNode) {
|
||||
nodes.push(entityNode);
|
||||
}
|
||||
}
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
if (upto === null) {
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex)));
|
||||
}
|
||||
} else if (lastIndex < upto) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex, upto)));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
applyBaseParagraphStyle(element) {
|
||||
element.style.whiteSpace = "pre-wrap";
|
||||
element.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
element.style.setProperty("-webkit-touch-callout", "default");
|
||||
element.style.setProperty("-webkit-user-select", "text");
|
||||
element.style.setProperty("-khtml-user-select", "text");
|
||||
element.style.setProperty("-moz-user-select", "text");
|
||||
element.style.setProperty("-ms-user-select", "text");
|
||||
element.style.setProperty("user-select", "text");
|
||||
if (this.props.blur) {
|
||||
element.style.filter = "blur(3px)";
|
||||
element.style.setProperty("-webkit-user-select", "none");
|
||||
element.style.setProperty("-khtml-user-select", "none");
|
||||
element.style.setProperty("-moz-user-select", "none");
|
||||
element.style.setProperty("-ms-user-select", "none");
|
||||
element.style.setProperty("user-select", "none");
|
||||
element.style.pointerEvents = "none";
|
||||
}
|
||||
applyStyleObject(element, this.props.style);
|
||||
}
|
||||
buildReadMoreButton(symbol) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = resolveEllipsisSymbol(symbol, this.expanded);
|
||||
button.style.border = "0";
|
||||
button.style.background = "none";
|
||||
button.style.padding = "0";
|
||||
button.style.marginLeft = "6px";
|
||||
button.style.cursor = "pointer";
|
||||
button.style.color = LINK_COLOR;
|
||||
button.style.fontWeight = "700";
|
||||
button.onclick = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.handleExpand();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
handleExpand() {
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
if (!ellipsisConfig) {
|
||||
return;
|
||||
}
|
||||
this.expanded = true;
|
||||
ellipsisConfig.onExpand?.(true);
|
||||
this.render();
|
||||
}
|
||||
computeEntitySafeCutoff(count, entities) {
|
||||
let cutoff = count;
|
||||
let extended = true;
|
||||
while (extended) {
|
||||
extended = false;
|
||||
for (const entity of entities) {
|
||||
if (entity.start < cutoff && entity.end > cutoff) {
|
||||
cutoff = entity.end;
|
||||
extended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cutoff;
|
||||
}
|
||||
render() {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
const text = this.getText();
|
||||
const entities = findAllEntities(text);
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text);
|
||||
const wrapper = document.createElement("div");
|
||||
const paragraph = document.createElement("div");
|
||||
if (this.props.className) {
|
||||
paragraph.className = this.props.className;
|
||||
}
|
||||
this.applyBaseParagraphStyle(paragraph);
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
const shouldTruncate = !mergedExpanded && count > 0 && text.length > count;
|
||||
if (shouldTruncate) {
|
||||
const cutoff = this.computeEntitySafeCutoff(count, entities);
|
||||
const nodes = this.buildTextNodes(text, entities, cutoff);
|
||||
paragraph.append(...nodes);
|
||||
paragraph.append(document.createTextNode("\u2026"));
|
||||
if (ellipsisConfig.expandable) {
|
||||
paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol));
|
||||
}
|
||||
} else {
|
||||
paragraph.append(...this.buildTextNodes(text, entities));
|
||||
}
|
||||
} else {
|
||||
paragraph.append(...this.buildTextNodes(text, entities));
|
||||
if (ellipsisConfig && "rows" in ellipsisConfig && !mergedExpanded) {
|
||||
paragraph.style.display = "-webkit-box";
|
||||
paragraph.style.setProperty("-webkit-box-orient", "vertical");
|
||||
paragraph.style.setProperty("-webkit-line-clamp", String(ellipsisConfig.rows));
|
||||
paragraph.style.overflow = "hidden";
|
||||
if (ellipsisConfig.expandable) {
|
||||
wrapper.append(paragraph);
|
||||
wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol));
|
||||
this.host.replaceChildren(wrapper);
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
wrapper.append(paragraph);
|
||||
this.host.replaceChildren(wrapper);
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
}
|
||||
initObserver() {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
if (typeof IntersectionObserver === "undefined") {
|
||||
this.hostInsideView = true;
|
||||
return;
|
||||
}
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
if (entry.isIntersecting && !this.hostInsideView) {
|
||||
this.hostInsideView = true;
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText());
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
this.observer.observe(this.host);
|
||||
}
|
||||
emitOnViewIfNeeded(mergedExpanded) {
|
||||
if (!mergedExpanded || this.viewed || !this.hostInsideView) {
|
||||
return;
|
||||
}
|
||||
this.viewed = true;
|
||||
this.props.onView?.();
|
||||
}
|
||||
};
|
||||
var AngularContentTextWithSuggestionsRenderer = class extends AngularContentTextRenderer {
|
||||
};
|
||||
var AngularContentTitleWithSuggestionsRenderer = class {
|
||||
renderer = new AngularContentTextWithSuggestionsRenderer();
|
||||
attach(host, props = {}) {
|
||||
return this.renderer.attach(host, this.normalizeProps(props));
|
||||
}
|
||||
update(props) {
|
||||
return this.renderer.update(this.normalizeProps(props));
|
||||
}
|
||||
destroy() {
|
||||
this.renderer.destroy();
|
||||
}
|
||||
getState() {
|
||||
return this.renderer.getState();
|
||||
}
|
||||
normalizeProps(props) {
|
||||
return {
|
||||
...props,
|
||||
weight: "bold",
|
||||
blur: props.blur ?? false,
|
||||
ellipsis: props.ellipsis === void 0 ? { rows: 2 } : props.ellipsis
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { AngularContentSuggestionsAdapter, AngularContentTextRenderer, AngularContentTextWithSuggestionsRenderer, AngularContentTitleWithSuggestionsRenderer, buildAngularTagHref, createAngularContentTokens, toKebabCase };
|
||||
//# sourceMappingURL=index.js.map
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/content-suggestions/dist/angular/index.js.map
vendored
Normal file
1
packages/content-suggestions/dist/angular/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
97
packages/content-suggestions/dist/core/index.cjs
vendored
Normal file
97
packages/content-suggestions/dist/core/index.cjs
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
'use strict';
|
||||
|
||||
// src/core/parser.ts
|
||||
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
var parseMention = (mention) => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId
|
||||
};
|
||||
};
|
||||
var findMentions = (text) => {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0]
|
||||
};
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
var findTags = (content) => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", "")
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findLinks = (content) => {
|
||||
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link"
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findAllEntities = (content) => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
var processContent = (content) => {
|
||||
const processedText = content.replace(mentionLinkRegexp, (match) => {
|
||||
const parsed = parseMention(match);
|
||||
return parsed ? parsed.mention : match;
|
||||
});
|
||||
const tags = findTags(content).map((tag) => tag.tag);
|
||||
return {
|
||||
processedText,
|
||||
tags
|
||||
};
|
||||
};
|
||||
|
||||
exports.findAllEntities = findAllEntities;
|
||||
exports.findLinks = findLinks;
|
||||
exports.findMentions = findMentions;
|
||||
exports.findTags = findTags;
|
||||
exports.mentionLinkRegexp = mentionLinkRegexp;
|
||||
exports.parseMention = parseMention;
|
||||
exports.processContent = processContent;
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
packages/content-suggestions/dist/core/index.cjs.map
vendored
Normal file
1
packages/content-suggestions/dist/core/index.cjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12
packages/content-suggestions/dist/core/index.d.cts
vendored
Normal file
12
packages/content-suggestions/dist/core/index.d.cts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { C as ContentEntity, L as LinkEntity, M as MentionEntity, T as TagEntity, P as ParsedMention, a as ProcessedContent } from '../types-BRt4hd7A.cjs';
|
||||
export { B as BaseEntity } from '../types-BRt4hd7A.cjs';
|
||||
|
||||
declare const mentionLinkRegexp: RegExp;
|
||||
declare const parseMention: (mention: string) => ParsedMention | null;
|
||||
declare const findMentions: (text: string) => MentionEntity[];
|
||||
declare const findTags: (content: string) => TagEntity[];
|
||||
declare const findLinks: (content: string) => LinkEntity[];
|
||||
declare const findAllEntities: (content: string) => ContentEntity[];
|
||||
declare const processContent: (content: string) => ProcessedContent;
|
||||
|
||||
export { ContentEntity, LinkEntity, MentionEntity, ParsedMention, ProcessedContent, TagEntity, findAllEntities, findLinks, findMentions, findTags, mentionLinkRegexp, parseMention, processContent };
|
||||
12
packages/content-suggestions/dist/core/index.d.ts
vendored
Normal file
12
packages/content-suggestions/dist/core/index.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { C as ContentEntity, L as LinkEntity, M as MentionEntity, T as TagEntity, P as ParsedMention, a as ProcessedContent } from '../types-BRt4hd7A.js';
|
||||
export { B as BaseEntity } from '../types-BRt4hd7A.js';
|
||||
|
||||
declare const mentionLinkRegexp: RegExp;
|
||||
declare const parseMention: (mention: string) => ParsedMention | null;
|
||||
declare const findMentions: (text: string) => MentionEntity[];
|
||||
declare const findTags: (content: string) => TagEntity[];
|
||||
declare const findLinks: (content: string) => LinkEntity[];
|
||||
declare const findAllEntities: (content: string) => ContentEntity[];
|
||||
declare const processContent: (content: string) => ProcessedContent;
|
||||
|
||||
export { ContentEntity, LinkEntity, MentionEntity, ParsedMention, ProcessedContent, TagEntity, findAllEntities, findLinks, findMentions, findTags, mentionLinkRegexp, parseMention, processContent };
|
||||
89
packages/content-suggestions/dist/core/index.js
vendored
Normal file
89
packages/content-suggestions/dist/core/index.js
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// src/core/parser.ts
|
||||
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
var parseMention = (mention) => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId
|
||||
};
|
||||
};
|
||||
var findMentions = (text) => {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0]
|
||||
};
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
var findTags = (content) => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", "")
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findLinks = (content) => {
|
||||
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link"
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findAllEntities = (content) => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
var processContent = (content) => {
|
||||
const processedText = content.replace(mentionLinkRegexp, (match) => {
|
||||
const parsed = parseMention(match);
|
||||
return parsed ? parsed.mention : match;
|
||||
});
|
||||
const tags = findTags(content).map((tag) => tag.tag);
|
||||
return {
|
||||
processedText,
|
||||
tags
|
||||
};
|
||||
};
|
||||
|
||||
export { findAllEntities, findLinks, findMentions, findTags, mentionLinkRegexp, parseMention, processContent };
|
||||
//# sourceMappingURL=index.js.map
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/content-suggestions/dist/core/index.js.map
vendored
Normal file
1
packages/content-suggestions/dist/core/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
396
packages/content-suggestions/dist/react/index.cjs
vendored
Normal file
396
packages/content-suggestions/dist/react/index.cjs
vendored
Normal file
@@ -0,0 +1,396 @@
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var react = require('@hublib-web/tach-typography/react');
|
||||
var jsxRuntime = require('react/jsx-runtime');
|
||||
|
||||
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
||||
|
||||
var React__default = /*#__PURE__*/_interopDefault(React);
|
||||
|
||||
// src/react/components/content-text.tsx
|
||||
|
||||
// src/core/parser.ts
|
||||
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
var parseMention = (mention) => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId
|
||||
};
|
||||
};
|
||||
var findMentions = (text) => {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0]
|
||||
};
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
var findTags = (content) => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", "")
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findLinks = (content) => {
|
||||
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link"
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findAllEntities = (content) => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
var processContent = (content) => {
|
||||
const processedText = content.replace(mentionLinkRegexp, (match) => {
|
||||
const parsed = parseMention(match);
|
||||
return parsed ? parsed.mention : match;
|
||||
});
|
||||
const tags = findTags(content).map((tag) => tag.tag);
|
||||
return {
|
||||
processedText,
|
||||
tags
|
||||
};
|
||||
};
|
||||
var joinClassName = (...values) => values.filter(Boolean).join(" ");
|
||||
var baseSelectableStyle = {
|
||||
whiteSpace: "pre-wrap",
|
||||
WebkitTouchCallout: "default",
|
||||
WebkitUserSelect: "text",
|
||||
KhtmlUserSelect: "text",
|
||||
MozUserSelect: "text",
|
||||
msUserSelect: "text",
|
||||
userSelect: "text"
|
||||
};
|
||||
var blurStyle = {
|
||||
filter: "blur(3px)",
|
||||
WebkitUserSelect: "none",
|
||||
KhtmlUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
userSelect: "none",
|
||||
pointerEvents: "none"
|
||||
};
|
||||
var ReadMoreButton = ({ onClick, symbol = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E", expanded = false, TitleComponent }) => /* @__PURE__ */ jsxRuntime.jsx(
|
||||
TitleComponent,
|
||||
{
|
||||
onClick: (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onClick(event);
|
||||
},
|
||||
weight: "bold",
|
||||
level: 5,
|
||||
children: typeof symbol === "function" ? symbol(expanded) : symbol
|
||||
}
|
||||
);
|
||||
var ContentText = React__default.default.memo(
|
||||
({
|
||||
text,
|
||||
className,
|
||||
ellipsis = false,
|
||||
blur = false,
|
||||
weight = "normal",
|
||||
style,
|
||||
onView,
|
||||
renderMention,
|
||||
renderTag,
|
||||
renderLink,
|
||||
ParagraphComponent = react.TachTypography.Paragraph.Body,
|
||||
TextComponent = react.TachTypography.Text.Body,
|
||||
TitleComponent = react.TachTypography.Title.Body,
|
||||
...props
|
||||
}) => {
|
||||
const containerRef = React.useRef(null);
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const [containerInsideView, setContainerInsideView] = React.useState(false);
|
||||
const [viewed, setViewed] = React.useState(false);
|
||||
const content = text || "";
|
||||
const entities = React.useMemo(() => findAllEntities(content), [content]);
|
||||
const wrapWithKey = (node, key) => {
|
||||
if (React__default.default.isValidElement(node)) {
|
||||
return React__default.default.cloneElement(node, { key });
|
||||
}
|
||||
return /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: node }, key);
|
||||
};
|
||||
const buildMentionNode = (entity, index) => {
|
||||
const defaultNode = /* @__PURE__ */ jsxRuntime.jsx(TextComponent, { color: "link", weight, children: entity.displayText });
|
||||
const customNode = renderMention?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `mention-${entity.start}-${index}`);
|
||||
};
|
||||
const buildTagNode = (entity, index) => {
|
||||
const defaultNode = /* @__PURE__ */ jsxRuntime.jsx(TextComponent, { color: "link", weight, children: entity.text });
|
||||
const customNode = renderTag?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `tag-${entity.start}-${index}`);
|
||||
};
|
||||
const buildLinkNode = (entity, index) => {
|
||||
const defaultNode = /* @__PURE__ */ jsxRuntime.jsx(
|
||||
react.TachTypography.Link.Body,
|
||||
{
|
||||
target: "_blank",
|
||||
referrerPolicy: "no-referrer",
|
||||
color: "link",
|
||||
weight,
|
||||
href: entity.url,
|
||||
children: entity.text
|
||||
}
|
||||
);
|
||||
const customNode = renderLink?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `link-${entity.start}-${index}`);
|
||||
};
|
||||
const buildParts = (upto = null) => {
|
||||
let lastIndex = 0;
|
||||
const nodes = [];
|
||||
for (const [i, entity] of entities.entries()) {
|
||||
if (upto !== null && entity.start >= upto) {
|
||||
break;
|
||||
}
|
||||
const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;
|
||||
if (entity.start > lastIndex && lastIndex < textEnd) {
|
||||
nodes.push(content.slice(lastIndex, textEnd));
|
||||
}
|
||||
if (upto === null || entity.end <= upto) {
|
||||
if (entity.type === "mention") {
|
||||
nodes.push(buildMentionNode(entity, i));
|
||||
} else if (entity.type === "tag") {
|
||||
nodes.push(buildTagNode(entity, i));
|
||||
} else if (entity.type === "link") {
|
||||
nodes.push(buildLinkNode(entity, i));
|
||||
}
|
||||
}
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
if (upto === null) {
|
||||
if (lastIndex < content.length) {
|
||||
nodes.push(content.slice(lastIndex));
|
||||
}
|
||||
} else if (lastIndex < upto) {
|
||||
nodes.push(content.slice(lastIndex, upto));
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
const ellipsisConfig = ellipsis && typeof ellipsis === "object" ? ellipsis : null;
|
||||
const expandedFromProps = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0;
|
||||
const isExpandedControlled = expandedFromProps !== void 0;
|
||||
const mergedExpanded = isExpandedControlled ? expandedFromProps : expanded;
|
||||
const handleExpand = React.useCallback(
|
||||
(event) => {
|
||||
if (!isExpandedControlled) {
|
||||
setExpanded(true);
|
||||
}
|
||||
ellipsisConfig?.onExpand?.(event, { expanded: true });
|
||||
},
|
||||
[ellipsisConfig, isExpandedControlled]
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (isExpandedControlled) {
|
||||
return;
|
||||
}
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
if (content.length <= count && !expanded) {
|
||||
setExpanded(true);
|
||||
}
|
||||
}
|
||||
}, [content.length, ellipsisConfig, expanded, isExpandedControlled]);
|
||||
React.useEffect(() => {
|
||||
if (mergedExpanded && !viewed && containerInsideView) {
|
||||
setViewed(true);
|
||||
onView?.();
|
||||
}
|
||||
}, [mergedExpanded, viewed, containerInsideView, onView]);
|
||||
React.useEffect(() => {
|
||||
const ref = containerRef.current;
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
if (entry.isIntersecting && !containerInsideView) {
|
||||
setContainerInsideView(true);
|
||||
}
|
||||
}, { threshold: 0.5 });
|
||||
observer.observe(ref);
|
||||
return () => {
|
||||
observer.unobserve(ref);
|
||||
};
|
||||
}, [containerInsideView]);
|
||||
const mergedStyle = {
|
||||
...baseSelectableStyle,
|
||||
...blur ? blurStyle : void 0,
|
||||
...style
|
||||
};
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const { count, expandable } = ellipsisConfig;
|
||||
if (!mergedExpanded && count && content.length > count) {
|
||||
let cutoff = count;
|
||||
let extended = true;
|
||||
while (extended) {
|
||||
extended = false;
|
||||
for (const entity of entities) {
|
||||
if (entity.start < cutoff && entity.end > cutoff) {
|
||||
cutoff = entity.end;
|
||||
extended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const truncatedNodes = buildParts(cutoff);
|
||||
return /* @__PURE__ */ jsxRuntime.jsxs(
|
||||
ParagraphComponent,
|
||||
{
|
||||
ref: containerRef,
|
||||
weight,
|
||||
className: joinClassName(className),
|
||||
style: mergedStyle,
|
||||
...props,
|
||||
children: [
|
||||
truncatedNodes,
|
||||
"\u2026",
|
||||
expandable && /* @__PURE__ */ jsxRuntime.jsx(
|
||||
ReadMoreButton,
|
||||
{
|
||||
symbol: ellipsisConfig.symbol,
|
||||
onClick: handleExpand,
|
||||
expanded: mergedExpanded,
|
||||
TitleComponent
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
if (ellipsisConfig && "rows" in ellipsisConfig) {
|
||||
const paragraphEllipsis = mergedExpanded ? false : {
|
||||
...ellipsisConfig,
|
||||
symbol: ellipsisConfig.expandable ? /* @__PURE__ */ jsxRuntime.jsx(
|
||||
ReadMoreButton,
|
||||
{
|
||||
symbol: ellipsisConfig.symbol,
|
||||
onClick: handleExpand,
|
||||
expanded: mergedExpanded,
|
||||
TitleComponent
|
||||
}
|
||||
) : ellipsisConfig.symbol
|
||||
};
|
||||
return /* @__PURE__ */ jsxRuntime.jsx(
|
||||
ParagraphComponent,
|
||||
{
|
||||
ref: containerRef,
|
||||
weight,
|
||||
className: joinClassName(className),
|
||||
style: mergedStyle,
|
||||
ellipsis: paragraphEllipsis,
|
||||
...props,
|
||||
children: buildParts()
|
||||
}
|
||||
);
|
||||
}
|
||||
return /* @__PURE__ */ jsxRuntime.jsx(
|
||||
ParagraphComponent,
|
||||
{
|
||||
ref: containerRef,
|
||||
weight,
|
||||
className: joinClassName(className),
|
||||
style: mergedStyle,
|
||||
...props,
|
||||
children: buildParts()
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
ContentText.displayName = "ContentText";
|
||||
var DefaultMention = ({ entity }) => /* @__PURE__ */ jsxRuntime.jsx(react.TachTypography.Text.Body, { color: "link", children: entity.displayText });
|
||||
var DefaultTag = ({ entity }) => /* @__PURE__ */ jsxRuntime.jsx(react.TachTypography.Text.Body, { color: "link", children: entity.text });
|
||||
var ContentTextWithSuggestions = ({
|
||||
renderMention,
|
||||
renderTag,
|
||||
...props
|
||||
}) => {
|
||||
return /* @__PURE__ */ jsxRuntime.jsx(
|
||||
ContentText,
|
||||
{
|
||||
...props,
|
||||
renderMention: (entity, index) => renderMention ? renderMention(entity, index) : /* @__PURE__ */ jsxRuntime.jsx(DefaultMention, { entity }),
|
||||
renderTag: (entity, index) => renderTag ? renderTag(entity, index) : /* @__PURE__ */ jsxRuntime.jsx(DefaultTag, { entity })
|
||||
}
|
||||
);
|
||||
};
|
||||
var ContentTitleWithSuggestions = ({
|
||||
text,
|
||||
ellipsis,
|
||||
blur = false,
|
||||
...rest
|
||||
}) => {
|
||||
const normalizedEllipsis = ellipsis === void 0 ? { rows: 2 } : ellipsis;
|
||||
const textProps = text === void 0 ? {} : { text };
|
||||
return /* @__PURE__ */ jsxRuntime.jsx(
|
||||
ContentTextWithSuggestions,
|
||||
{
|
||||
weight: "bold",
|
||||
blur,
|
||||
ellipsis: normalizedEllipsis,
|
||||
...textProps,
|
||||
...rest
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
exports.ContentText = ContentText;
|
||||
exports.ContentTextWithSuggestions = ContentTextWithSuggestions;
|
||||
exports.ContentTitleWithSuggestions = ContentTitleWithSuggestions;
|
||||
exports.findAllEntities = findAllEntities;
|
||||
exports.findLinks = findLinks;
|
||||
exports.findMentions = findMentions;
|
||||
exports.findTags = findTags;
|
||||
exports.mentionLinkRegexp = mentionLinkRegexp;
|
||||
exports.parseMention = parseMention;
|
||||
exports.processContent = processContent;
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
packages/content-suggestions/dist/react/index.cjs.map
vendored
Normal file
1
packages/content-suggestions/dist/react/index.cjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
48
packages/content-suggestions/dist/react/index.d.cts
vendored
Normal file
48
packages/content-suggestions/dist/react/index.d.cts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { ComponentProps, ReactNode } from 'react';
|
||||
import { EllipsisConfig } from 'antd/lib/typography/Base';
|
||||
import { M as MentionEntity, T as TagEntity, L as LinkEntity } from '../types-BRt4hd7A.cjs';
|
||||
export { B as BaseEntity, C as ContentEntity, P as ParsedMention, a as ProcessedContent } from '../types-BRt4hd7A.cjs';
|
||||
import { TachTypography } from '@hublib-web/tach-typography/react';
|
||||
import * as react_jsx_runtime from 'react/jsx-runtime';
|
||||
export { findAllEntities, findLinks, findMentions, findTags, mentionLinkRegexp, parseMention, processContent } from '../core/index.cjs';
|
||||
|
||||
type CustomEllipsisConfig = ({
|
||||
count: number;
|
||||
rows?: never;
|
||||
expandable?: boolean;
|
||||
} & Partial<EllipsisConfig>) | ({
|
||||
rows: number;
|
||||
count?: never;
|
||||
expandable?: boolean;
|
||||
} & Partial<EllipsisConfig>) | false;
|
||||
type ParagraphBodyProps = ComponentProps<typeof TachTypography.Paragraph.Body>;
|
||||
type TextBodyProps = ComponentProps<typeof TachTypography.Text.Body>;
|
||||
type TitleBodyProps = ComponentProps<typeof TachTypography.Title.Body>;
|
||||
interface ContentTextProps {
|
||||
className?: string;
|
||||
weight?: TextBodyProps["weight"];
|
||||
text?: string | null;
|
||||
ellipsis?: CustomEllipsisConfig;
|
||||
blur?: boolean;
|
||||
style?: TextBodyProps["style"];
|
||||
onView?: () => void;
|
||||
renderMention?: (entity: MentionEntity, index: number) => ReactNode;
|
||||
renderTag?: (entity: TagEntity, index: number) => ReactNode;
|
||||
renderLink?: (entity: LinkEntity, index: number) => ReactNode;
|
||||
ParagraphComponent?: React.ComponentType<ParagraphBodyProps>;
|
||||
TextComponent?: React.ComponentType<TextBodyProps>;
|
||||
TitleComponent?: React.ComponentType<TitleBodyProps>;
|
||||
}
|
||||
declare const ContentText: React.NamedExoticComponent<ContentTextProps>;
|
||||
|
||||
type BaseContentTextProps = Omit<ComponentProps<typeof ContentText>, "renderMention" | "renderTag"> & {
|
||||
renderMention?: (entity: MentionEntity, index: number) => React.ReactNode;
|
||||
renderTag?: (entity: TagEntity, index: number) => React.ReactNode;
|
||||
};
|
||||
declare const ContentTextWithSuggestions: ({ renderMention, renderTag, ...props }: BaseContentTextProps) => react_jsx_runtime.JSX.Element;
|
||||
|
||||
interface ContentTitleWithSuggestionsProps extends Omit<ComponentProps<typeof ContentTextWithSuggestions>, "weight"> {
|
||||
}
|
||||
declare const ContentTitleWithSuggestions: ({ text, ellipsis, blur, ...rest }: ContentTitleWithSuggestionsProps) => react_jsx_runtime.JSX.Element;
|
||||
|
||||
export { ContentText, type ContentTextProps, ContentTextWithSuggestions, ContentTitleWithSuggestions, LinkEntity, MentionEntity, TagEntity };
|
||||
48
packages/content-suggestions/dist/react/index.d.ts
vendored
Normal file
48
packages/content-suggestions/dist/react/index.d.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { ComponentProps, ReactNode } from 'react';
|
||||
import { EllipsisConfig } from 'antd/lib/typography/Base';
|
||||
import { M as MentionEntity, T as TagEntity, L as LinkEntity } from '../types-BRt4hd7A.js';
|
||||
export { B as BaseEntity, C as ContentEntity, P as ParsedMention, a as ProcessedContent } from '../types-BRt4hd7A.js';
|
||||
import { TachTypography } from '@hublib-web/tach-typography/react';
|
||||
import * as react_jsx_runtime from 'react/jsx-runtime';
|
||||
export { findAllEntities, findLinks, findMentions, findTags, mentionLinkRegexp, parseMention, processContent } from '../core/index.js';
|
||||
|
||||
type CustomEllipsisConfig = ({
|
||||
count: number;
|
||||
rows?: never;
|
||||
expandable?: boolean;
|
||||
} & Partial<EllipsisConfig>) | ({
|
||||
rows: number;
|
||||
count?: never;
|
||||
expandable?: boolean;
|
||||
} & Partial<EllipsisConfig>) | false;
|
||||
type ParagraphBodyProps = ComponentProps<typeof TachTypography.Paragraph.Body>;
|
||||
type TextBodyProps = ComponentProps<typeof TachTypography.Text.Body>;
|
||||
type TitleBodyProps = ComponentProps<typeof TachTypography.Title.Body>;
|
||||
interface ContentTextProps {
|
||||
className?: string;
|
||||
weight?: TextBodyProps["weight"];
|
||||
text?: string | null;
|
||||
ellipsis?: CustomEllipsisConfig;
|
||||
blur?: boolean;
|
||||
style?: TextBodyProps["style"];
|
||||
onView?: () => void;
|
||||
renderMention?: (entity: MentionEntity, index: number) => ReactNode;
|
||||
renderTag?: (entity: TagEntity, index: number) => ReactNode;
|
||||
renderLink?: (entity: LinkEntity, index: number) => ReactNode;
|
||||
ParagraphComponent?: React.ComponentType<ParagraphBodyProps>;
|
||||
TextComponent?: React.ComponentType<TextBodyProps>;
|
||||
TitleComponent?: React.ComponentType<TitleBodyProps>;
|
||||
}
|
||||
declare const ContentText: React.NamedExoticComponent<ContentTextProps>;
|
||||
|
||||
type BaseContentTextProps = Omit<ComponentProps<typeof ContentText>, "renderMention" | "renderTag"> & {
|
||||
renderMention?: (entity: MentionEntity, index: number) => React.ReactNode;
|
||||
renderTag?: (entity: TagEntity, index: number) => React.ReactNode;
|
||||
};
|
||||
declare const ContentTextWithSuggestions: ({ renderMention, renderTag, ...props }: BaseContentTextProps) => react_jsx_runtime.JSX.Element;
|
||||
|
||||
interface ContentTitleWithSuggestionsProps extends Omit<ComponentProps<typeof ContentTextWithSuggestions>, "weight"> {
|
||||
}
|
||||
declare const ContentTitleWithSuggestions: ({ text, ellipsis, blur, ...rest }: ContentTitleWithSuggestionsProps) => react_jsx_runtime.JSX.Element;
|
||||
|
||||
export { ContentText, type ContentTextProps, ContentTextWithSuggestions, ContentTitleWithSuggestions, LinkEntity, MentionEntity, TagEntity };
|
||||
381
packages/content-suggestions/dist/react/index.js
vendored
Normal file
381
packages/content-suggestions/dist/react/index.js
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
import React, { useRef, useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { TachTypography } from '@hublib-web/tach-typography/react';
|
||||
import { jsxs, jsx } from 'react/jsx-runtime';
|
||||
|
||||
// src/react/components/content-text.tsx
|
||||
|
||||
// src/core/parser.ts
|
||||
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
var parseMention = (mention) => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId
|
||||
};
|
||||
};
|
||||
var findMentions = (text) => {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0]
|
||||
};
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
var findTags = (content) => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", "")
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findLinks = (content) => {
|
||||
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results = [];
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link"
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
var findAllEntities = (content) => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
var processContent = (content) => {
|
||||
const processedText = content.replace(mentionLinkRegexp, (match) => {
|
||||
const parsed = parseMention(match);
|
||||
return parsed ? parsed.mention : match;
|
||||
});
|
||||
const tags = findTags(content).map((tag) => tag.tag);
|
||||
return {
|
||||
processedText,
|
||||
tags
|
||||
};
|
||||
};
|
||||
var joinClassName = (...values) => values.filter(Boolean).join(" ");
|
||||
var baseSelectableStyle = {
|
||||
whiteSpace: "pre-wrap",
|
||||
WebkitTouchCallout: "default",
|
||||
WebkitUserSelect: "text",
|
||||
KhtmlUserSelect: "text",
|
||||
MozUserSelect: "text",
|
||||
msUserSelect: "text",
|
||||
userSelect: "text"
|
||||
};
|
||||
var blurStyle = {
|
||||
filter: "blur(3px)",
|
||||
WebkitUserSelect: "none",
|
||||
KhtmlUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
userSelect: "none",
|
||||
pointerEvents: "none"
|
||||
};
|
||||
var ReadMoreButton = ({ onClick, symbol = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E", expanded = false, TitleComponent }) => /* @__PURE__ */ jsx(
|
||||
TitleComponent,
|
||||
{
|
||||
onClick: (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onClick(event);
|
||||
},
|
||||
weight: "bold",
|
||||
level: 5,
|
||||
children: typeof symbol === "function" ? symbol(expanded) : symbol
|
||||
}
|
||||
);
|
||||
var ContentText = React.memo(
|
||||
({
|
||||
text,
|
||||
className,
|
||||
ellipsis = false,
|
||||
blur = false,
|
||||
weight = "normal",
|
||||
style,
|
||||
onView,
|
||||
renderMention,
|
||||
renderTag,
|
||||
renderLink,
|
||||
ParagraphComponent = TachTypography.Paragraph.Body,
|
||||
TextComponent = TachTypography.Text.Body,
|
||||
TitleComponent = TachTypography.Title.Body,
|
||||
...props
|
||||
}) => {
|
||||
const containerRef = useRef(null);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [containerInsideView, setContainerInsideView] = useState(false);
|
||||
const [viewed, setViewed] = useState(false);
|
||||
const content = text || "";
|
||||
const entities = useMemo(() => findAllEntities(content), [content]);
|
||||
const wrapWithKey = (node, key) => {
|
||||
if (React.isValidElement(node)) {
|
||||
return React.cloneElement(node, { key });
|
||||
}
|
||||
return /* @__PURE__ */ jsx(React.Fragment, { children: node }, key);
|
||||
};
|
||||
const buildMentionNode = (entity, index) => {
|
||||
const defaultNode = /* @__PURE__ */ jsx(TextComponent, { color: "link", weight, children: entity.displayText });
|
||||
const customNode = renderMention?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `mention-${entity.start}-${index}`);
|
||||
};
|
||||
const buildTagNode = (entity, index) => {
|
||||
const defaultNode = /* @__PURE__ */ jsx(TextComponent, { color: "link", weight, children: entity.text });
|
||||
const customNode = renderTag?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `tag-${entity.start}-${index}`);
|
||||
};
|
||||
const buildLinkNode = (entity, index) => {
|
||||
const defaultNode = /* @__PURE__ */ jsx(
|
||||
TachTypography.Link.Body,
|
||||
{
|
||||
target: "_blank",
|
||||
referrerPolicy: "no-referrer",
|
||||
color: "link",
|
||||
weight,
|
||||
href: entity.url,
|
||||
children: entity.text
|
||||
}
|
||||
);
|
||||
const customNode = renderLink?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `link-${entity.start}-${index}`);
|
||||
};
|
||||
const buildParts = (upto = null) => {
|
||||
let lastIndex = 0;
|
||||
const nodes = [];
|
||||
for (const [i, entity] of entities.entries()) {
|
||||
if (upto !== null && entity.start >= upto) {
|
||||
break;
|
||||
}
|
||||
const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;
|
||||
if (entity.start > lastIndex && lastIndex < textEnd) {
|
||||
nodes.push(content.slice(lastIndex, textEnd));
|
||||
}
|
||||
if (upto === null || entity.end <= upto) {
|
||||
if (entity.type === "mention") {
|
||||
nodes.push(buildMentionNode(entity, i));
|
||||
} else if (entity.type === "tag") {
|
||||
nodes.push(buildTagNode(entity, i));
|
||||
} else if (entity.type === "link") {
|
||||
nodes.push(buildLinkNode(entity, i));
|
||||
}
|
||||
}
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
if (upto === null) {
|
||||
if (lastIndex < content.length) {
|
||||
nodes.push(content.slice(lastIndex));
|
||||
}
|
||||
} else if (lastIndex < upto) {
|
||||
nodes.push(content.slice(lastIndex, upto));
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
const ellipsisConfig = ellipsis && typeof ellipsis === "object" ? ellipsis : null;
|
||||
const expandedFromProps = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0;
|
||||
const isExpandedControlled = expandedFromProps !== void 0;
|
||||
const mergedExpanded = isExpandedControlled ? expandedFromProps : expanded;
|
||||
const handleExpand = useCallback(
|
||||
(event) => {
|
||||
if (!isExpandedControlled) {
|
||||
setExpanded(true);
|
||||
}
|
||||
ellipsisConfig?.onExpand?.(event, { expanded: true });
|
||||
},
|
||||
[ellipsisConfig, isExpandedControlled]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (isExpandedControlled) {
|
||||
return;
|
||||
}
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
if (content.length <= count && !expanded) {
|
||||
setExpanded(true);
|
||||
}
|
||||
}
|
||||
}, [content.length, ellipsisConfig, expanded, isExpandedControlled]);
|
||||
useEffect(() => {
|
||||
if (mergedExpanded && !viewed && containerInsideView) {
|
||||
setViewed(true);
|
||||
onView?.();
|
||||
}
|
||||
}, [mergedExpanded, viewed, containerInsideView, onView]);
|
||||
useEffect(() => {
|
||||
const ref = containerRef.current;
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
if (entry.isIntersecting && !containerInsideView) {
|
||||
setContainerInsideView(true);
|
||||
}
|
||||
}, { threshold: 0.5 });
|
||||
observer.observe(ref);
|
||||
return () => {
|
||||
observer.unobserve(ref);
|
||||
};
|
||||
}, [containerInsideView]);
|
||||
const mergedStyle = {
|
||||
...baseSelectableStyle,
|
||||
...blur ? blurStyle : void 0,
|
||||
...style
|
||||
};
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const { count, expandable } = ellipsisConfig;
|
||||
if (!mergedExpanded && count && content.length > count) {
|
||||
let cutoff = count;
|
||||
let extended = true;
|
||||
while (extended) {
|
||||
extended = false;
|
||||
for (const entity of entities) {
|
||||
if (entity.start < cutoff && entity.end > cutoff) {
|
||||
cutoff = entity.end;
|
||||
extended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const truncatedNodes = buildParts(cutoff);
|
||||
return /* @__PURE__ */ jsxs(
|
||||
ParagraphComponent,
|
||||
{
|
||||
ref: containerRef,
|
||||
weight,
|
||||
className: joinClassName(className),
|
||||
style: mergedStyle,
|
||||
...props,
|
||||
children: [
|
||||
truncatedNodes,
|
||||
"\u2026",
|
||||
expandable && /* @__PURE__ */ jsx(
|
||||
ReadMoreButton,
|
||||
{
|
||||
symbol: ellipsisConfig.symbol,
|
||||
onClick: handleExpand,
|
||||
expanded: mergedExpanded,
|
||||
TitleComponent
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
if (ellipsisConfig && "rows" in ellipsisConfig) {
|
||||
const paragraphEllipsis = mergedExpanded ? false : {
|
||||
...ellipsisConfig,
|
||||
symbol: ellipsisConfig.expandable ? /* @__PURE__ */ jsx(
|
||||
ReadMoreButton,
|
||||
{
|
||||
symbol: ellipsisConfig.symbol,
|
||||
onClick: handleExpand,
|
||||
expanded: mergedExpanded,
|
||||
TitleComponent
|
||||
}
|
||||
) : ellipsisConfig.symbol
|
||||
};
|
||||
return /* @__PURE__ */ jsx(
|
||||
ParagraphComponent,
|
||||
{
|
||||
ref: containerRef,
|
||||
weight,
|
||||
className: joinClassName(className),
|
||||
style: mergedStyle,
|
||||
ellipsis: paragraphEllipsis,
|
||||
...props,
|
||||
children: buildParts()
|
||||
}
|
||||
);
|
||||
}
|
||||
return /* @__PURE__ */ jsx(
|
||||
ParagraphComponent,
|
||||
{
|
||||
ref: containerRef,
|
||||
weight,
|
||||
className: joinClassName(className),
|
||||
style: mergedStyle,
|
||||
...props,
|
||||
children: buildParts()
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
ContentText.displayName = "ContentText";
|
||||
var DefaultMention = ({ entity }) => /* @__PURE__ */ jsx(TachTypography.Text.Body, { color: "link", children: entity.displayText });
|
||||
var DefaultTag = ({ entity }) => /* @__PURE__ */ jsx(TachTypography.Text.Body, { color: "link", children: entity.text });
|
||||
var ContentTextWithSuggestions = ({
|
||||
renderMention,
|
||||
renderTag,
|
||||
...props
|
||||
}) => {
|
||||
return /* @__PURE__ */ jsx(
|
||||
ContentText,
|
||||
{
|
||||
...props,
|
||||
renderMention: (entity, index) => renderMention ? renderMention(entity, index) : /* @__PURE__ */ jsx(DefaultMention, { entity }),
|
||||
renderTag: (entity, index) => renderTag ? renderTag(entity, index) : /* @__PURE__ */ jsx(DefaultTag, { entity })
|
||||
}
|
||||
);
|
||||
};
|
||||
var ContentTitleWithSuggestions = ({
|
||||
text,
|
||||
ellipsis,
|
||||
blur = false,
|
||||
...rest
|
||||
}) => {
|
||||
const normalizedEllipsis = ellipsis === void 0 ? { rows: 2 } : ellipsis;
|
||||
const textProps = text === void 0 ? {} : { text };
|
||||
return /* @__PURE__ */ jsx(
|
||||
ContentTextWithSuggestions,
|
||||
{
|
||||
weight: "bold",
|
||||
blur,
|
||||
ellipsis: normalizedEllipsis,
|
||||
...textProps,
|
||||
...rest
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export { ContentText, ContentTextWithSuggestions, ContentTitleWithSuggestions, findAllEntities, findLinks, findMentions, findTags, mentionLinkRegexp, parseMention, processContent };
|
||||
//# sourceMappingURL=index.js.map
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/content-suggestions/dist/react/index.js.map
vendored
Normal file
1
packages/content-suggestions/dist/react/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
29
packages/content-suggestions/dist/types-BRt4hd7A.d.cts
vendored
Normal file
29
packages/content-suggestions/dist/types-BRt4hd7A.d.cts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
interface BaseEntity {
|
||||
start: number;
|
||||
end: number;
|
||||
text: string;
|
||||
}
|
||||
interface MentionEntity extends BaseEntity {
|
||||
type: "mention";
|
||||
displayText: string;
|
||||
userId?: string;
|
||||
}
|
||||
interface TagEntity extends BaseEntity {
|
||||
type: "tag";
|
||||
tag: string;
|
||||
}
|
||||
interface LinkEntity extends BaseEntity {
|
||||
type: "link";
|
||||
url: string;
|
||||
}
|
||||
type ContentEntity = MentionEntity | TagEntity | LinkEntity;
|
||||
interface ParsedMention {
|
||||
mention: string;
|
||||
id: string;
|
||||
}
|
||||
interface ProcessedContent {
|
||||
processedText: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export type { BaseEntity as B, ContentEntity as C, LinkEntity as L, MentionEntity as M, ParsedMention as P, TagEntity as T, ProcessedContent as a };
|
||||
29
packages/content-suggestions/dist/types-BRt4hd7A.d.ts
vendored
Normal file
29
packages/content-suggestions/dist/types-BRt4hd7A.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
interface BaseEntity {
|
||||
start: number;
|
||||
end: number;
|
||||
text: string;
|
||||
}
|
||||
interface MentionEntity extends BaseEntity {
|
||||
type: "mention";
|
||||
displayText: string;
|
||||
userId?: string;
|
||||
}
|
||||
interface TagEntity extends BaseEntity {
|
||||
type: "tag";
|
||||
tag: string;
|
||||
}
|
||||
interface LinkEntity extends BaseEntity {
|
||||
type: "link";
|
||||
url: string;
|
||||
}
|
||||
type ContentEntity = MentionEntity | TagEntity | LinkEntity;
|
||||
interface ParsedMention {
|
||||
mention: string;
|
||||
id: string;
|
||||
}
|
||||
interface ProcessedContent {
|
||||
processedText: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export type { BaseEntity as B, ContentEntity as C, LinkEntity as L, MentionEntity as M, ParsedMention as P, TagEntity as T, ProcessedContent as a };
|
||||
105
packages/content-suggestions/package.json
Normal file
105
packages/content-suggestions/package.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "@hublib-web/content-suggestions",
|
||||
"version": "0.1.1",
|
||||
"description": "Content text/title with mentions, tags and links for React and Angular",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./dist/core/index.cjs",
|
||||
"module": "./dist/core/index.js",
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"react": [
|
||||
"dist/react/index.d.ts"
|
||||
],
|
||||
"angular": [
|
||||
"dist/angular/index.d.ts"
|
||||
],
|
||||
"core": [
|
||||
"dist/core/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"import": "./dist/core/index.js",
|
||||
"require": "./dist/core/index.cjs"
|
||||
},
|
||||
"./core": {
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"import": "./dist/core/index.js",
|
||||
"require": "./dist/core/index.cjs"
|
||||
},
|
||||
"./react": {
|
||||
"types": "./dist/react/index.d.ts",
|
||||
"import": "./dist/react/index.js",
|
||||
"require": "./dist/react/index.cjs"
|
||||
},
|
||||
"./angular": {
|
||||
"types": "./dist/angular/index.d.ts",
|
||||
"import": "./dist/angular/index.js",
|
||||
"require": "./dist/angular/index.cjs"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn clean && tsup",
|
||||
"clean": "rm -rf dist storybook-static",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"lint": "eslint src --ext .ts,.tsx",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "storybook build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=17.0.0",
|
||||
"@angular/core": ">=17.0.0",
|
||||
"@hublib-web/tach-typography": ">=0.1.0",
|
||||
"antd": ">=5.0.0",
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/common": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/core": {
|
||||
"optional": true
|
||||
},
|
||||
"antd": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"rxjs": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/common": "^20.3.17",
|
||||
"@angular/core": "^20.3.17",
|
||||
"@hublib-web/tach-typography": "workspace:*",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/react": "8.6.14",
|
||||
"@storybook/react-vite": "8.6.14",
|
||||
"@types/react": "^19.2.2",
|
||||
"antd": "^5.29.3",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"storybook": "8.6.14",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "6.4.1"
|
||||
}
|
||||
}
|
||||
643
packages/content-suggestions/src/angular/index.ts
Normal file
643
packages/content-suggestions/src/angular/index.ts
Normal file
@@ -0,0 +1,643 @@
|
||||
import type { ContentEntity, LinkEntity, MentionEntity, TagEntity } from "../core";
|
||||
|
||||
import { findAllEntities } from "../core";
|
||||
|
||||
export interface AngularTextToken {
|
||||
kind: "text";
|
||||
text: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export interface AngularMentionToken {
|
||||
kind: "mention";
|
||||
entity: MentionEntity;
|
||||
}
|
||||
|
||||
export interface AngularTagToken {
|
||||
kind: "tag";
|
||||
entity: TagEntity;
|
||||
}
|
||||
|
||||
export interface AngularLinkToken {
|
||||
kind: "link";
|
||||
entity: LinkEntity;
|
||||
}
|
||||
|
||||
export type AngularContentToken =
|
||||
| AngularTextToken
|
||||
| AngularMentionToken
|
||||
| AngularTagToken
|
||||
| AngularLinkToken;
|
||||
|
||||
export interface AngularContentSnapshot {
|
||||
text: string;
|
||||
entities: ContentEntity[];
|
||||
tokens: AngularContentToken[];
|
||||
}
|
||||
|
||||
export type AngularEllipsisSymbol = string | ((expanded: boolean) => string);
|
||||
|
||||
export type AngularCountEllipsisConfig = {
|
||||
count: number;
|
||||
rows?: never;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
symbol?: AngularEllipsisSymbol;
|
||||
onExpand?: (expanded: boolean) => void;
|
||||
};
|
||||
|
||||
export type AngularRowsEllipsisConfig = {
|
||||
rows: number;
|
||||
count?: never;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
symbol?: AngularEllipsisSymbol;
|
||||
onExpand?: (expanded: boolean) => void;
|
||||
};
|
||||
|
||||
export type AngularContentEllipsisConfig =
|
||||
| AngularCountEllipsisConfig
|
||||
| AngularRowsEllipsisConfig
|
||||
| false;
|
||||
|
||||
export type AngularRenderResult = Node | string | number | null | undefined;
|
||||
|
||||
export type AngularMentionRenderer = (
|
||||
entity: MentionEntity,
|
||||
index: number,
|
||||
) => AngularRenderResult;
|
||||
|
||||
export type AngularTagRenderer = (
|
||||
entity: TagEntity,
|
||||
index: number,
|
||||
) => AngularRenderResult;
|
||||
|
||||
export type AngularLinkRenderer = (
|
||||
entity: LinkEntity,
|
||||
index: number,
|
||||
) => AngularRenderResult;
|
||||
|
||||
export interface AngularContentTextProps {
|
||||
className?: string;
|
||||
weight?: "normal" | "bold";
|
||||
text?: string | null;
|
||||
ellipsis?: AngularContentEllipsisConfig;
|
||||
blur?: boolean;
|
||||
style?: Record<string, string | number> | null;
|
||||
onView?: () => void;
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
renderLink?: AngularLinkRenderer;
|
||||
}
|
||||
|
||||
export type AngularContentTextWithSuggestionsProps = Omit<
|
||||
AngularContentTextProps,
|
||||
"renderMention" | "renderTag"
|
||||
> & {
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
};
|
||||
|
||||
export type AngularContentTitleWithSuggestionsProps = Omit<
|
||||
AngularContentTextWithSuggestionsProps,
|
||||
"weight"
|
||||
>;
|
||||
|
||||
export interface AngularContentTextRendererState {
|
||||
props: AngularContentTextProps;
|
||||
snapshot: AngularContentSnapshot;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
const LINK_COLOR = "#1677ff";
|
||||
const READ_MORE_TEXT = "Читать полностью";
|
||||
|
||||
const toKebabCase = (value: string) => value.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);
|
||||
|
||||
const applyStyleObject = (
|
||||
element: HTMLElement,
|
||||
styles?: Record<string, string | number> | null,
|
||||
) => {
|
||||
if (!styles) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, rawValue] of Object.entries(styles)) {
|
||||
if (rawValue === null || rawValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const styleValue =
|
||||
typeof rawValue === "number" ? `${rawValue}px` : String(rawValue);
|
||||
|
||||
if (key.startsWith("--")) {
|
||||
element.style.setProperty(key, styleValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
const cssKey = key.includes("-") ? key : toKebabCase(key);
|
||||
element.style.setProperty(cssKey, styleValue);
|
||||
}
|
||||
};
|
||||
|
||||
const toNode = (value: AngularRenderResult): Node | null => {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Node) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return document.createTextNode(String(value));
|
||||
};
|
||||
|
||||
const resolveEllipsisSymbol = (
|
||||
symbol: AngularEllipsisSymbol | undefined,
|
||||
expanded: boolean,
|
||||
): string => {
|
||||
if (typeof symbol === "function") {
|
||||
return symbol(expanded);
|
||||
}
|
||||
|
||||
return symbol ?? READ_MORE_TEXT;
|
||||
};
|
||||
|
||||
const buildAngularTagHref = (entity: TagEntity): string => {
|
||||
return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;
|
||||
};
|
||||
|
||||
export { buildAngularTagHref };
|
||||
|
||||
export const createAngularContentTokens = (
|
||||
inputText: string | null | undefined,
|
||||
): AngularContentToken[] => {
|
||||
const text = inputText ?? "";
|
||||
const entities = findAllEntities(text);
|
||||
|
||||
let cursor = 0;
|
||||
const tokens: AngularContentToken[] = [];
|
||||
|
||||
for (const entity of entities) {
|
||||
if (entity.start > cursor) {
|
||||
tokens.push({
|
||||
kind: "text",
|
||||
text: text.slice(cursor, entity.start),
|
||||
start: cursor,
|
||||
end: entity.start,
|
||||
});
|
||||
}
|
||||
|
||||
if (entity.type === "mention") {
|
||||
tokens.push({
|
||||
kind: "mention",
|
||||
entity,
|
||||
});
|
||||
} else if (entity.type === "tag") {
|
||||
tokens.push({
|
||||
kind: "tag",
|
||||
entity,
|
||||
});
|
||||
} else {
|
||||
tokens.push({
|
||||
kind: "link",
|
||||
entity,
|
||||
});
|
||||
}
|
||||
|
||||
cursor = entity.end;
|
||||
}
|
||||
|
||||
if (cursor < text.length) {
|
||||
tokens.push({
|
||||
kind: "text",
|
||||
text: text.slice(cursor),
|
||||
start: cursor,
|
||||
end: text.length,
|
||||
});
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
export class AngularContentSuggestionsAdapter {
|
||||
snapshot(inputText: string | null | undefined): AngularContentSnapshot {
|
||||
const text = inputText ?? "";
|
||||
const entities = findAllEntities(text);
|
||||
const tokens = createAngularContentTokens(text);
|
||||
|
||||
return {
|
||||
text,
|
||||
entities,
|
||||
tokens,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class AngularContentTextRenderer {
|
||||
private host: HTMLElement | null = null;
|
||||
private props: AngularContentTextProps = {};
|
||||
private expanded = false;
|
||||
private viewed = false;
|
||||
private hostInsideView = false;
|
||||
private observer: IntersectionObserver | null = null;
|
||||
|
||||
attach(host: HTMLElement, props: AngularContentTextProps = {}): AngularContentTextRendererState {
|
||||
this.destroy();
|
||||
|
||||
this.host = host;
|
||||
this.props = { ...props };
|
||||
this.expanded = false;
|
||||
this.viewed = false;
|
||||
this.hostInsideView = false;
|
||||
|
||||
this.initObserver();
|
||||
this.render();
|
||||
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
update(nextProps: AngularContentTextProps): AngularContentTextRendererState {
|
||||
this.props = {
|
||||
...this.props,
|
||||
...nextProps,
|
||||
};
|
||||
|
||||
this.render();
|
||||
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.observer && this.host) {
|
||||
this.observer.unobserve(this.host);
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
this.observer = null;
|
||||
|
||||
if (this.host) {
|
||||
this.host.innerHTML = "";
|
||||
}
|
||||
|
||||
this.host = null;
|
||||
this.props = {};
|
||||
this.expanded = false;
|
||||
this.viewed = false;
|
||||
this.hostInsideView = false;
|
||||
}
|
||||
|
||||
getState(): AngularContentTextRendererState {
|
||||
return {
|
||||
props: { ...this.props },
|
||||
snapshot: this.createSnapshot(),
|
||||
expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText()),
|
||||
};
|
||||
}
|
||||
|
||||
private getText(): string {
|
||||
return this.props.text ?? "";
|
||||
}
|
||||
|
||||
private createSnapshot(): AngularContentSnapshot {
|
||||
const text = this.getText();
|
||||
const entities = findAllEntities(text);
|
||||
const tokens = createAngularContentTokens(text);
|
||||
|
||||
return {
|
||||
text,
|
||||
entities,
|
||||
tokens,
|
||||
};
|
||||
}
|
||||
|
||||
private resolveEllipsisConfig():
|
||||
| AngularCountEllipsisConfig
|
||||
| AngularRowsEllipsisConfig
|
||||
| null {
|
||||
const ellipsis = this.props.ellipsis;
|
||||
|
||||
if (!ellipsis || typeof ellipsis !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ellipsis;
|
||||
}
|
||||
|
||||
private getMergedExpanded(
|
||||
ellipsisConfig: AngularCountEllipsisConfig | AngularRowsEllipsisConfig | null,
|
||||
text: string,
|
||||
): boolean {
|
||||
const controlledExpanded =
|
||||
ellipsisConfig && typeof ellipsisConfig.expanded === "boolean"
|
||||
? ellipsisConfig.expanded
|
||||
: undefined;
|
||||
|
||||
if (controlledExpanded !== undefined) {
|
||||
return controlledExpanded;
|
||||
}
|
||||
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
if (count <= 0 || text.length <= count) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.expanded;
|
||||
}
|
||||
|
||||
private createDefaultMentionNode(entity: MentionEntity): Node {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = LINK_COLOR;
|
||||
span.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
span.textContent = entity.displayText;
|
||||
return span;
|
||||
}
|
||||
|
||||
private createDefaultTagNode(entity: TagEntity): Node {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = LINK_COLOR;
|
||||
span.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
span.textContent = entity.text;
|
||||
return span;
|
||||
}
|
||||
|
||||
private createDefaultLinkNode(entity: LinkEntity): Node {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = entity.url;
|
||||
anchor.target = "_blank";
|
||||
anchor.referrerPolicy = "no-referrer";
|
||||
anchor.style.color = LINK_COLOR;
|
||||
anchor.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
anchor.textContent = entity.text;
|
||||
return anchor;
|
||||
}
|
||||
|
||||
private renderEntity(entity: ContentEntity, index: number): Node | null {
|
||||
if (entity.type === "mention") {
|
||||
const customNode = this.props.renderMention?.(entity, index);
|
||||
return toNode(customNode) ?? this.createDefaultMentionNode(entity);
|
||||
}
|
||||
|
||||
if (entity.type === "tag") {
|
||||
const customNode = this.props.renderTag?.(entity, index);
|
||||
return toNode(customNode) ?? this.createDefaultTagNode(entity);
|
||||
}
|
||||
|
||||
const customNode = this.props.renderLink?.(entity, index);
|
||||
return toNode(customNode) ?? this.createDefaultLinkNode(entity);
|
||||
}
|
||||
|
||||
private buildTextNodes(
|
||||
text: string,
|
||||
entities: ContentEntity[],
|
||||
upto: number | null = null,
|
||||
): Node[] {
|
||||
let lastIndex = 0;
|
||||
const nodes: Node[] = [];
|
||||
|
||||
for (const [index, entity] of entities.entries()) {
|
||||
if (upto !== null && entity.start >= upto) {
|
||||
break;
|
||||
}
|
||||
|
||||
const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;
|
||||
if (entity.start > lastIndex && lastIndex < textEnd) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex, textEnd)));
|
||||
}
|
||||
|
||||
if (upto === null || entity.end <= upto) {
|
||||
const entityNode = this.renderEntity(entity, index);
|
||||
if (entityNode) {
|
||||
nodes.push(entityNode);
|
||||
}
|
||||
}
|
||||
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
|
||||
if (upto === null) {
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex)));
|
||||
}
|
||||
} else if (lastIndex < upto) {
|
||||
nodes.push(document.createTextNode(text.slice(lastIndex, upto)));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private applyBaseParagraphStyle(element: HTMLElement): void {
|
||||
element.style.whiteSpace = "pre-wrap";
|
||||
element.style.fontWeight = this.props.weight === "bold" ? "700" : "400";
|
||||
element.style.setProperty("-webkit-touch-callout", "default");
|
||||
element.style.setProperty("-webkit-user-select", "text");
|
||||
element.style.setProperty("-khtml-user-select", "text");
|
||||
element.style.setProperty("-moz-user-select", "text");
|
||||
element.style.setProperty("-ms-user-select", "text");
|
||||
element.style.setProperty("user-select", "text");
|
||||
|
||||
if (this.props.blur) {
|
||||
element.style.filter = "blur(3px)";
|
||||
element.style.setProperty("-webkit-user-select", "none");
|
||||
element.style.setProperty("-khtml-user-select", "none");
|
||||
element.style.setProperty("-moz-user-select", "none");
|
||||
element.style.setProperty("-ms-user-select", "none");
|
||||
element.style.setProperty("user-select", "none");
|
||||
element.style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
applyStyleObject(element, this.props.style);
|
||||
}
|
||||
|
||||
private buildReadMoreButton(symbol: AngularEllipsisSymbol | undefined): HTMLButtonElement {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = resolveEllipsisSymbol(symbol, this.expanded);
|
||||
button.style.border = "0";
|
||||
button.style.background = "none";
|
||||
button.style.padding = "0";
|
||||
button.style.marginLeft = "6px";
|
||||
button.style.cursor = "pointer";
|
||||
button.style.color = LINK_COLOR;
|
||||
button.style.fontWeight = "700";
|
||||
button.onclick = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.handleExpand();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
private handleExpand(): void {
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
|
||||
if (!ellipsisConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.expanded = true;
|
||||
ellipsisConfig.onExpand?.(true);
|
||||
this.render();
|
||||
}
|
||||
|
||||
private computeEntitySafeCutoff(
|
||||
count: number,
|
||||
entities: ContentEntity[],
|
||||
): number {
|
||||
let cutoff = count;
|
||||
let extended = true;
|
||||
|
||||
while (extended) {
|
||||
extended = false;
|
||||
|
||||
for (const entity of entities) {
|
||||
if (entity.start < cutoff && entity.end > cutoff) {
|
||||
cutoff = entity.end;
|
||||
extended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cutoff;
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = this.getText();
|
||||
const entities = findAllEntities(text);
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text);
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
const paragraph = document.createElement("div");
|
||||
|
||||
if (this.props.className) {
|
||||
paragraph.className = this.props.className;
|
||||
}
|
||||
|
||||
this.applyBaseParagraphStyle(paragraph);
|
||||
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
const shouldTruncate = !mergedExpanded && count > 0 && text.length > count;
|
||||
|
||||
if (shouldTruncate) {
|
||||
const cutoff = this.computeEntitySafeCutoff(count, entities);
|
||||
const nodes = this.buildTextNodes(text, entities, cutoff);
|
||||
|
||||
paragraph.append(...nodes);
|
||||
paragraph.append(document.createTextNode("…"));
|
||||
|
||||
if (ellipsisConfig.expandable) {
|
||||
paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol));
|
||||
}
|
||||
} else {
|
||||
paragraph.append(...this.buildTextNodes(text, entities));
|
||||
}
|
||||
} else {
|
||||
paragraph.append(...this.buildTextNodes(text, entities));
|
||||
|
||||
if (ellipsisConfig && "rows" in ellipsisConfig && !mergedExpanded) {
|
||||
paragraph.style.display = "-webkit-box";
|
||||
paragraph.style.setProperty("-webkit-box-orient", "vertical");
|
||||
paragraph.style.setProperty("-webkit-line-clamp", String(ellipsisConfig.rows));
|
||||
paragraph.style.overflow = "hidden";
|
||||
|
||||
if (ellipsisConfig.expandable) {
|
||||
wrapper.append(paragraph);
|
||||
wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol));
|
||||
this.host.replaceChildren(wrapper);
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.append(paragraph);
|
||||
this.host.replaceChildren(wrapper);
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
}
|
||||
|
||||
private initObserver(): void {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof IntersectionObserver === "undefined") {
|
||||
this.hostInsideView = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.observer = new IntersectionObserver(
|
||||
entries => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.isIntersecting && !this.hostInsideView) {
|
||||
this.hostInsideView = true;
|
||||
const ellipsisConfig = this.resolveEllipsisConfig();
|
||||
const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText());
|
||||
this.emitOnViewIfNeeded(mergedExpanded);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
this.observer.observe(this.host);
|
||||
}
|
||||
|
||||
private emitOnViewIfNeeded(mergedExpanded: boolean): void {
|
||||
if (!mergedExpanded || this.viewed || !this.hostInsideView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewed = true;
|
||||
this.props.onView?.();
|
||||
}
|
||||
}
|
||||
|
||||
export class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer {}
|
||||
|
||||
export class AngularContentTitleWithSuggestionsRenderer {
|
||||
private readonly renderer = new AngularContentTextWithSuggestionsRenderer();
|
||||
|
||||
attach(
|
||||
host: HTMLElement,
|
||||
props: AngularContentTitleWithSuggestionsProps = {},
|
||||
): AngularContentTextRendererState {
|
||||
return this.renderer.attach(host, this.normalizeProps(props));
|
||||
}
|
||||
|
||||
update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState {
|
||||
return this.renderer.update(this.normalizeProps(props));
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.renderer.destroy();
|
||||
}
|
||||
|
||||
getState(): AngularContentTextRendererState {
|
||||
return this.renderer.getState();
|
||||
}
|
||||
|
||||
private normalizeProps(
|
||||
props: AngularContentTitleWithSuggestionsProps,
|
||||
): AngularContentTextWithSuggestionsProps {
|
||||
return {
|
||||
...props,
|
||||
weight: "bold",
|
||||
blur: props.blur ?? false,
|
||||
ellipsis: props.ellipsis === undefined ? { rows: 2 } : props.ellipsis,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { toKebabCase };
|
||||
19
packages/content-suggestions/src/core/index.ts
Normal file
19
packages/content-suggestions/src/core/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export type {
|
||||
BaseEntity,
|
||||
ContentEntity,
|
||||
LinkEntity,
|
||||
MentionEntity,
|
||||
ParsedMention,
|
||||
ProcessedContent,
|
||||
TagEntity,
|
||||
} from "./types";
|
||||
|
||||
export {
|
||||
findAllEntities,
|
||||
findLinks,
|
||||
findMentions,
|
||||
findTags,
|
||||
mentionLinkRegexp,
|
||||
parseMention,
|
||||
processContent,
|
||||
} from "./parser";
|
||||
115
packages/content-suggestions/src/core/parser.ts
Normal file
115
packages/content-suggestions/src/core/parser.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type {
|
||||
ContentEntity,
|
||||
LinkEntity,
|
||||
MentionEntity,
|
||||
ParsedMention,
|
||||
ProcessedContent,
|
||||
TagEntity,
|
||||
} from "./types";
|
||||
|
||||
export const mentionLinkRegexp =
|
||||
/@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
|
||||
|
||||
export const parseMention = (mention: string): ParsedMention | null => {
|
||||
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
|
||||
const match = mention.match(regex);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mentionText = match[1];
|
||||
const mentionId = match[2];
|
||||
if (!mentionText || !mentionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
mention: `@${mentionText}`,
|
||||
id: mentionId,
|
||||
};
|
||||
};
|
||||
|
||||
export const findMentions = (text: string): MentionEntity[] => {
|
||||
let match: RegExpExecArray | null;
|
||||
const matches: MentionEntity[] = [];
|
||||
|
||||
while ((match = mentionLinkRegexp.exec(text)) !== null) {
|
||||
const parsed = parseMention(match[0]);
|
||||
const baseMention: Omit<MentionEntity, "userId"> = {
|
||||
start: match.index,
|
||||
end: mentionLinkRegexp.lastIndex,
|
||||
text: match[0],
|
||||
type: "mention",
|
||||
displayText: parsed?.mention ?? match[0],
|
||||
};
|
||||
|
||||
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
|
||||
}
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
export const findTags = (content: string): TagEntity[] => {
|
||||
const regex = /#[^\s]{1,201}/g;
|
||||
const results: TagEntity[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const value = match[0];
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + value.length,
|
||||
text: value,
|
||||
type: "tag",
|
||||
tag: value.replace("#", ""),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
export const findLinks = (content: string): LinkEntity[] => {
|
||||
const regex =
|
||||
/\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
|
||||
const results: LinkEntity[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const rawUrl = match[0];
|
||||
const hasProtocol = /^https?:\/\//i.test(rawUrl);
|
||||
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
|
||||
|
||||
results.push({
|
||||
start: match.index,
|
||||
end: match.index + rawUrl.length,
|
||||
text: rawUrl,
|
||||
url: fullUrl,
|
||||
type: "link",
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
export const findAllEntities = (content: string): ContentEntity[] => {
|
||||
const mentions = findMentions(content);
|
||||
const tags = findTags(content);
|
||||
const links = findLinks(content);
|
||||
|
||||
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
|
||||
};
|
||||
|
||||
export const processContent = (content: string): ProcessedContent => {
|
||||
const processedText = content.replace(mentionLinkRegexp, match => {
|
||||
const parsed = parseMention(match);
|
||||
return parsed ? parsed.mention : match;
|
||||
});
|
||||
|
||||
const tags = findTags(content).map(tag => tag.tag);
|
||||
|
||||
return {
|
||||
processedText,
|
||||
tags,
|
||||
};
|
||||
};
|
||||
33
packages/content-suggestions/src/core/types.ts
Normal file
33
packages/content-suggestions/src/core/types.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export interface BaseEntity {
|
||||
start: number;
|
||||
end: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface MentionEntity extends BaseEntity {
|
||||
type: "mention";
|
||||
displayText: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface TagEntity extends BaseEntity {
|
||||
type: "tag";
|
||||
tag: string;
|
||||
}
|
||||
|
||||
export interface LinkEntity extends BaseEntity {
|
||||
type: "link";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type ContentEntity = MentionEntity | TagEntity | LinkEntity;
|
||||
|
||||
export interface ParsedMention {
|
||||
mention: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ProcessedContent {
|
||||
processedText: string;
|
||||
tags: string[];
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import type { MentionEntity, TagEntity } from "../../core";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { TachTypography } from "@hublib-web/tach-typography/react";
|
||||
import { ContentText } from "./content-text";
|
||||
|
||||
type BaseContentTextProps = Omit<
|
||||
ComponentProps<typeof ContentText>,
|
||||
"renderMention" | "renderTag"
|
||||
> & {
|
||||
renderMention?: (entity: MentionEntity, index: number) => React.ReactNode;
|
||||
renderTag?: (entity: TagEntity, index: number) => React.ReactNode;
|
||||
};
|
||||
|
||||
const DefaultMention = ({ entity }: { entity: MentionEntity }) => (
|
||||
<TachTypography.Text.Body color="link">{entity.displayText}</TachTypography.Text.Body>
|
||||
);
|
||||
|
||||
const DefaultTag = ({ entity }: { entity: TagEntity }) => (
|
||||
<TachTypography.Text.Body color="link">
|
||||
{entity.text}
|
||||
</TachTypography.Text.Body>
|
||||
);
|
||||
|
||||
export const ContentTextWithSuggestions = ({
|
||||
renderMention,
|
||||
renderTag,
|
||||
...props
|
||||
}: BaseContentTextProps) => {
|
||||
return (
|
||||
<ContentText
|
||||
{...props}
|
||||
renderMention={(entity, index) =>
|
||||
renderMention ? renderMention(entity, index) : <DefaultMention entity={entity} />
|
||||
}
|
||||
renderTag={(entity, index) =>
|
||||
renderTag ? (
|
||||
renderTag(entity, index)
|
||||
) : (
|
||||
<DefaultTag entity={entity} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,353 @@
|
||||
import type { ComponentProps, CSSProperties, ReactNode } from "react";
|
||||
import type { EllipsisConfig } from "antd/lib/typography/Base";
|
||||
import type {
|
||||
ContentEntity,
|
||||
LinkEntity,
|
||||
MentionEntity,
|
||||
TagEntity,
|
||||
} from "../../core";
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { TachTypography } from "@hublib-web/tach-typography/react";
|
||||
import { findAllEntities } from "../../core";
|
||||
|
||||
type CustomEllipsisConfig =
|
||||
| ({
|
||||
count: number;
|
||||
rows?: never;
|
||||
expandable?: boolean;
|
||||
} & Partial<EllipsisConfig>)
|
||||
| ({
|
||||
rows: number;
|
||||
count?: never;
|
||||
expandable?: boolean;
|
||||
} & Partial<EllipsisConfig>)
|
||||
| false;
|
||||
|
||||
type ParagraphBodyProps = ComponentProps<typeof TachTypography.Paragraph.Body>;
|
||||
type TextBodyProps = ComponentProps<typeof TachTypography.Text.Body>;
|
||||
type TitleBodyProps = ComponentProps<typeof TachTypography.Title.Body>;
|
||||
|
||||
const joinClassName = (...values: Array<string | false | undefined>) =>
|
||||
values.filter(Boolean).join(" ");
|
||||
|
||||
const baseSelectableStyle: CSSProperties = {
|
||||
whiteSpace: "pre-wrap",
|
||||
WebkitTouchCallout: "default",
|
||||
WebkitUserSelect: "text",
|
||||
KhtmlUserSelect: "text",
|
||||
MozUserSelect: "text",
|
||||
msUserSelect: "text",
|
||||
userSelect: "text",
|
||||
};
|
||||
|
||||
const blurStyle: CSSProperties = {
|
||||
filter: "blur(3px)",
|
||||
WebkitUserSelect: "none",
|
||||
KhtmlUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
userSelect: "none",
|
||||
pointerEvents: "none",
|
||||
};
|
||||
|
||||
const ReadMoreButton: React.FC<{
|
||||
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
symbol?: EllipsisConfig["symbol"];
|
||||
expanded?: boolean;
|
||||
TitleComponent: React.ComponentType<TitleBodyProps>;
|
||||
}> = ({ onClick, symbol = "Читать полностью", expanded = false, TitleComponent }) => (
|
||||
<TitleComponent
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onClick(event);
|
||||
}}
|
||||
weight="bold"
|
||||
level={5}
|
||||
>
|
||||
{typeof symbol === "function" ? symbol(expanded) : symbol}
|
||||
</TitleComponent>
|
||||
);
|
||||
|
||||
export interface ContentTextProps {
|
||||
className?: string;
|
||||
weight?: TextBodyProps["weight"];
|
||||
text?: string | null;
|
||||
ellipsis?: CustomEllipsisConfig;
|
||||
blur?: boolean;
|
||||
style?: TextBodyProps["style"];
|
||||
onView?: () => void;
|
||||
renderMention?: (entity: MentionEntity, index: number) => ReactNode;
|
||||
renderTag?: (entity: TagEntity, index: number) => ReactNode;
|
||||
renderLink?: (entity: LinkEntity, index: number) => ReactNode;
|
||||
ParagraphComponent?: React.ComponentType<ParagraphBodyProps>;
|
||||
TextComponent?: React.ComponentType<TextBodyProps>;
|
||||
TitleComponent?: React.ComponentType<TitleBodyProps>;
|
||||
}
|
||||
|
||||
export const ContentText: React.NamedExoticComponent<ContentTextProps> = React.memo(
|
||||
({
|
||||
text,
|
||||
className,
|
||||
ellipsis = false,
|
||||
blur = false,
|
||||
weight = "normal",
|
||||
style,
|
||||
onView,
|
||||
renderMention,
|
||||
renderTag,
|
||||
renderLink,
|
||||
ParagraphComponent = TachTypography.Paragraph.Body,
|
||||
TextComponent = TachTypography.Text.Body,
|
||||
TitleComponent = TachTypography.Title.Body,
|
||||
...props
|
||||
}: ContentTextProps) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [containerInsideView, setContainerInsideView] = useState(false);
|
||||
const [viewed, setViewed] = useState(false);
|
||||
const content = text || "";
|
||||
|
||||
const entities = useMemo<ContentEntity[]>(() => findAllEntities(content), [content]);
|
||||
|
||||
const wrapWithKey = (node: ReactNode, key: React.Key) => {
|
||||
if (React.isValidElement(node)) {
|
||||
return React.cloneElement(node, { key });
|
||||
}
|
||||
|
||||
return <React.Fragment key={key}>{node}</React.Fragment>;
|
||||
};
|
||||
|
||||
const buildMentionNode = (entity: MentionEntity, index: number) => {
|
||||
const defaultNode = (
|
||||
<TextComponent color="link" weight={weight}>
|
||||
{entity.displayText}
|
||||
</TextComponent>
|
||||
);
|
||||
const customNode = renderMention?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `mention-${entity.start}-${index}`);
|
||||
};
|
||||
|
||||
const buildTagNode = (entity: TagEntity, index: number) => {
|
||||
const defaultNode = (
|
||||
<TextComponent color="link" weight={weight}>
|
||||
{entity.text}
|
||||
</TextComponent>
|
||||
);
|
||||
const customNode = renderTag?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `tag-${entity.start}-${index}`);
|
||||
};
|
||||
|
||||
const buildLinkNode = (entity: LinkEntity, index: number) => {
|
||||
const defaultNode = (
|
||||
<TachTypography.Link.Body
|
||||
target="_blank"
|
||||
referrerPolicy="no-referrer"
|
||||
color="link"
|
||||
weight={weight}
|
||||
href={entity.url}
|
||||
>
|
||||
{entity.text}
|
||||
</TachTypography.Link.Body>
|
||||
);
|
||||
const customNode = renderLink?.(entity, index) ?? defaultNode;
|
||||
return wrapWithKey(customNode, `link-${entity.start}-${index}`);
|
||||
};
|
||||
|
||||
const buildParts = (upto: number | null = null): ReactNode[] => {
|
||||
let lastIndex = 0;
|
||||
const nodes: ReactNode[] = [];
|
||||
|
||||
for (const [i, entity] of entities.entries()) {
|
||||
|
||||
if (upto !== null && entity.start >= upto) {
|
||||
break;
|
||||
}
|
||||
|
||||
const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;
|
||||
if (entity.start > lastIndex && lastIndex < textEnd) {
|
||||
nodes.push(content.slice(lastIndex, textEnd));
|
||||
}
|
||||
|
||||
if (upto === null || entity.end <= upto) {
|
||||
if (entity.type === "mention") {
|
||||
nodes.push(buildMentionNode(entity, i));
|
||||
} else if (entity.type === "tag") {
|
||||
nodes.push(buildTagNode(entity, i));
|
||||
} else if (entity.type === "link") {
|
||||
nodes.push(buildLinkNode(entity, i));
|
||||
}
|
||||
}
|
||||
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
|
||||
if (upto === null) {
|
||||
if (lastIndex < content.length) {
|
||||
nodes.push(content.slice(lastIndex));
|
||||
}
|
||||
} else if (lastIndex < upto) {
|
||||
nodes.push(content.slice(lastIndex, upto));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
const ellipsisConfig = ellipsis && typeof ellipsis === "object" ? ellipsis : null;
|
||||
const expandedFromProps =
|
||||
ellipsisConfig && typeof ellipsisConfig.expanded === "boolean"
|
||||
? ellipsisConfig.expanded
|
||||
: undefined;
|
||||
const isExpandedControlled = expandedFromProps !== undefined;
|
||||
const mergedExpanded = isExpandedControlled ? expandedFromProps : expanded;
|
||||
|
||||
const handleExpand = useCallback(
|
||||
(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
if (!isExpandedControlled) {
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
ellipsisConfig?.onExpand?.(event, { expanded: true });
|
||||
},
|
||||
[ellipsisConfig, isExpandedControlled],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpandedControlled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const count = ellipsisConfig.count ?? 0;
|
||||
if (content.length <= count && !expanded) {
|
||||
setExpanded(true);
|
||||
}
|
||||
}
|
||||
}, [content.length, ellipsisConfig, expanded, isExpandedControlled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mergedExpanded && !viewed && containerInsideView) {
|
||||
setViewed(true);
|
||||
onView?.();
|
||||
}
|
||||
}, [mergedExpanded, viewed, containerInsideView, onView]);
|
||||
|
||||
useEffect(() => {
|
||||
const ref = containerRef.current;
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.isIntersecting && !containerInsideView) {
|
||||
setContainerInsideView(true);
|
||||
}
|
||||
}, { threshold: 0.5 });
|
||||
|
||||
observer.observe(ref);
|
||||
|
||||
return () => {
|
||||
observer.unobserve(ref);
|
||||
};
|
||||
}, [containerInsideView]);
|
||||
|
||||
const mergedStyle: CSSProperties = {
|
||||
...baseSelectableStyle,
|
||||
...(blur ? blurStyle : undefined),
|
||||
...(style as CSSProperties | undefined),
|
||||
};
|
||||
|
||||
if (ellipsisConfig && "count" in ellipsisConfig) {
|
||||
const { count, expandable } = ellipsisConfig;
|
||||
|
||||
if (!mergedExpanded && count && content.length > count) {
|
||||
let cutoff = count;
|
||||
let extended = true;
|
||||
|
||||
while (extended) {
|
||||
extended = false;
|
||||
for (const entity of entities) {
|
||||
if (entity.start < cutoff && entity.end > cutoff) {
|
||||
cutoff = entity.end;
|
||||
extended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const truncatedNodes = buildParts(cutoff);
|
||||
|
||||
return (
|
||||
<ParagraphComponent
|
||||
ref={containerRef}
|
||||
weight={weight}
|
||||
className={joinClassName(className)}
|
||||
style={mergedStyle}
|
||||
{...(props as ParagraphBodyProps)}
|
||||
>
|
||||
{truncatedNodes}…
|
||||
{expandable && (
|
||||
<ReadMoreButton
|
||||
symbol={ellipsisConfig.symbol}
|
||||
onClick={handleExpand}
|
||||
expanded={mergedExpanded}
|
||||
TitleComponent={TitleComponent}
|
||||
/>
|
||||
)}
|
||||
</ParagraphComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (ellipsisConfig && "rows" in ellipsisConfig) {
|
||||
const paragraphEllipsis = mergedExpanded
|
||||
? false
|
||||
: {
|
||||
...ellipsisConfig,
|
||||
symbol: ellipsisConfig.expandable ? (
|
||||
<ReadMoreButton
|
||||
symbol={ellipsisConfig.symbol}
|
||||
onClick={handleExpand}
|
||||
expanded={mergedExpanded}
|
||||
TitleComponent={TitleComponent}
|
||||
/>
|
||||
) : (
|
||||
ellipsisConfig.symbol
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<ParagraphComponent
|
||||
ref={containerRef}
|
||||
weight={weight}
|
||||
className={joinClassName(className)}
|
||||
style={mergedStyle}
|
||||
ellipsis={paragraphEllipsis}
|
||||
{...(props as ParagraphBodyProps)}
|
||||
>
|
||||
{buildParts()}
|
||||
</ParagraphComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ParagraphComponent
|
||||
ref={containerRef}
|
||||
weight={weight}
|
||||
className={joinClassName(className)}
|
||||
style={mergedStyle}
|
||||
{...(props as ParagraphBodyProps)}
|
||||
>
|
||||
{buildParts()}
|
||||
</ParagraphComponent>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContentText.displayName = "ContentText";
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { ContentTextWithSuggestions } from "./content-text-with-suggestions";
|
||||
|
||||
interface ContentTitleWithSuggestionsProps
|
||||
extends Omit<ComponentProps<typeof ContentTextWithSuggestions>, "weight"> {}
|
||||
|
||||
export const ContentTitleWithSuggestions = ({
|
||||
text,
|
||||
ellipsis,
|
||||
blur = false,
|
||||
...rest
|
||||
}: ContentTitleWithSuggestionsProps) => {
|
||||
const normalizedEllipsis = ellipsis === undefined ? { rows: 2 } : ellipsis;
|
||||
const textProps = text === undefined ? {} : { text };
|
||||
|
||||
return (
|
||||
<ContentTextWithSuggestions
|
||||
weight="bold"
|
||||
blur={blur}
|
||||
ellipsis={normalizedEllipsis}
|
||||
{...textProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
27
packages/content-suggestions/src/react/index.tsx
Normal file
27
packages/content-suggestions/src/react/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
export { ContentText, type ContentTextProps } from "./components/content-text";
|
||||
export {
|
||||
ContentTextWithSuggestions,
|
||||
} from "./components/content-text-with-suggestions";
|
||||
export {
|
||||
ContentTitleWithSuggestions,
|
||||
} from "./components/content-title-with-suggestions";
|
||||
|
||||
export type {
|
||||
BaseEntity,
|
||||
ContentEntity,
|
||||
LinkEntity,
|
||||
MentionEntity,
|
||||
ParsedMention,
|
||||
ProcessedContent,
|
||||
TagEntity,
|
||||
} from "../core";
|
||||
|
||||
export {
|
||||
findAllEntities,
|
||||
findLinks,
|
||||
findMentions,
|
||||
findTags,
|
||||
mentionLinkRegexp,
|
||||
parseMention,
|
||||
processContent,
|
||||
} from "../core";
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import type { MentionEntity, TagEntity } from "../core";
|
||||
import React from "react";
|
||||
|
||||
import { TachTypography } from "@hublib-web/tach-typography/react";
|
||||
import { ContentTextWithSuggestions } from "../react";
|
||||
|
||||
const DEMO_TEXT =
|
||||
"Пример текста с упоминанием @[Иван Петров](123e4567-e89b-12d3-a456-426614174000), тегом #frontend и ссылкой docs.example.com";
|
||||
|
||||
const meta: Meta<typeof ContentTextWithSuggestions> = {
|
||||
title: "React/ContentTextWithSuggestions",
|
||||
component: ContentTextWithSuggestions,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
text: DEMO_TEXT,
|
||||
blur: false,
|
||||
},
|
||||
argTypes: {
|
||||
text: { control: "text" },
|
||||
blur: { control: "boolean" },
|
||||
ellipsis: { control: false },
|
||||
renderMention: { control: false },
|
||||
renderTag: { control: false },
|
||||
renderLink: { control: false },
|
||||
ParagraphComponent: { control: false },
|
||||
TextComponent: { control: false },
|
||||
TitleComponent: { control: false },
|
||||
},
|
||||
render: args => (
|
||||
<div style={{ maxWidth: 780, width: "100%" }}>
|
||||
<ContentTextWithSuggestions {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const WithExpandableEllipsis: Story = {
|
||||
args: {
|
||||
ellipsis: {
|
||||
rows: 2,
|
||||
expandable: true,
|
||||
symbol: "Читать полностью",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomMentionAndTagRender: Story = {
|
||||
render: args => {
|
||||
const renderMention = (entity: MentionEntity) => (
|
||||
<TachTypography.Link.Body href={`https://example.com/users/${entity.userId ?? "unknown"}`} color="link">
|
||||
{entity.displayText}
|
||||
</TachTypography.Link.Body>
|
||||
);
|
||||
|
||||
const renderTag = (entity: TagEntity) => (
|
||||
<TachTypography.Link.Body href={`https://example.com/tags/${entity.tag}`} color="link">
|
||||
{entity.text}
|
||||
</TachTypography.Link.Body>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 780, width: "100%" }}>
|
||||
<ContentTextWithSuggestions
|
||||
{...args}
|
||||
renderMention={renderMention}
|
||||
renderTag={renderTag}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { ContentTitleWithSuggestions } from "../react";
|
||||
|
||||
const DEMO_TITLE =
|
||||
"Заголовок с @[Мария](123e4567-e89b-12d3-a456-426614174001), тегом #release и ссылкой github.com";
|
||||
|
||||
const meta: Meta<typeof ContentTitleWithSuggestions> = {
|
||||
title: "React/ContentTitleWithSuggestions",
|
||||
component: ContentTitleWithSuggestions,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
text: DEMO_TITLE,
|
||||
blur: false,
|
||||
ellipsis: {
|
||||
rows: 2,
|
||||
expandable: true,
|
||||
symbol: "Читать полностью",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
text: { control: "text" },
|
||||
blur: { control: "boolean" },
|
||||
ellipsis: { control: false },
|
||||
renderMention: { control: false },
|
||||
renderTag: { control: false },
|
||||
renderLink: { control: false },
|
||||
ParagraphComponent: { control: false },
|
||||
TextComponent: { control: false },
|
||||
TitleComponent: { control: false },
|
||||
},
|
||||
render: args => (
|
||||
<div style={{ maxWidth: 780, width: "100%" }}>
|
||||
<ContentTitleWithSuggestions {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const Blurred: Story = {
|
||||
args: {
|
||||
blur: true,
|
||||
},
|
||||
};
|
||||
10
packages/content-suggestions/tsconfig.json
Normal file
10
packages/content-suggestions/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["node", "react"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
25
packages/content-suggestions/tsup.config.ts
Normal file
25
packages/content-suggestions/tsup.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
"core/index": "src/core/index.ts",
|
||||
"react/index": "src/react/index.tsx",
|
||||
"angular/index": "src/angular/index.ts",
|
||||
},
|
||||
format: ["esm", "cjs"],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
clean: false,
|
||||
target: "es2022",
|
||||
minify: false,
|
||||
treeshake: true,
|
||||
splitting: false,
|
||||
external: [
|
||||
"react",
|
||||
"react-dom",
|
||||
"antd",
|
||||
"@angular/core",
|
||||
"@angular/common",
|
||||
"@hublib-web/tach-typography/react",
|
||||
],
|
||||
});
|
||||
22
packages/tach-typography/.storybook/main.ts
Normal file
22
packages/tach-typography/.storybook/main.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(ts|tsx)"],
|
||||
addons: [
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@storybook/addon-a11y",
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
90
packages/tach-typography/.storybook/preview.css
Normal file
90
packages/tach-typography/.storybook/preview.css
Normal file
@@ -0,0 +1,90 @@
|
||||
:root {
|
||||
--Text-Primary: #0f172a;
|
||||
--Text-Secondary: #334155;
|
||||
--Text-Tertiary: #64748b;
|
||||
--Text-Quaternary: #94a3b8;
|
||||
--System-HashtagsInPost: #2563eb;
|
||||
--Default-White: #ffffff;
|
||||
--Default-Dark: #020617;
|
||||
--System-Alert: #dc2626;
|
||||
--Accent-Malahit: #16a34a;
|
||||
--System-Attantion: #d97706;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Inter, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
.sb-show-main {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.tach-story-surface {
|
||||
min-width: 320px;
|
||||
max-width: 920px;
|
||||
padding: 20px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #dbe1ea;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tach-story-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tach-story-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tach-story-grid--colors {
|
||||
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
|
||||
}
|
||||
|
||||
.tach-story-row {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 220px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tach-story-label {
|
||||
color: #475569;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tach-story-token-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.tach-story-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tach-story-table th,
|
||||
.tach-story-table td {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tach-story-table thead {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.tach-story-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
27
packages/tach-typography/.storybook/preview.ts
Normal file
27
packages/tach-typography/.storybook/preview.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
|
||||
import "antd/dist/reset.css";
|
||||
import "../src/styles/tach-typography.css";
|
||||
import "./preview.css";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
controls: {
|
||||
expanded: true,
|
||||
sort: "requiredFirst",
|
||||
},
|
||||
actions: {
|
||||
argTypesRegex: "^on[A-Z].*",
|
||||
},
|
||||
backgrounds: {
|
||||
default: "surface",
|
||||
values: [
|
||||
{ name: "surface", value: "#F5F7FB" },
|
||||
{ name: "dark", value: "#0F172A" },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
98
packages/tach-typography/README.md
Normal file
98
packages/tach-typography/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# @hublib-web/tach-typography
|
||||
|
||||
Typography package with shared tokens and framework adapters:
|
||||
|
||||
- `react` adapter based on `antd/Typography`
|
||||
- `angular` adapter based on `ng-zorro-antd/typography`
|
||||
|
||||
## Install from Git (SSH tag)
|
||||
|
||||
```bash
|
||||
yarn add "@hublib-web/tach-typography@git+ssh://git@github.com/ORG/REPO.git#workspace=@hublib-web/tach-typography&tag=tach-typography-v0.1.0"
|
||||
```
|
||||
|
||||
## Install inside this monorepo
|
||||
|
||||
```bash
|
||||
yarn add @hublib-web/tach-typography
|
||||
```
|
||||
|
||||
## Release this package
|
||||
|
||||
1. Bump `version` in `packages/tach-typography/package.json`.
|
||||
2. Build package artifacts:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/tach-typography build
|
||||
```
|
||||
|
||||
3. Commit release files:
|
||||
|
||||
```bash
|
||||
git add packages/tach-typography/package.json packages/tach-typography/dist
|
||||
git commit -m "release(tach-typography): v0.1.0"
|
||||
```
|
||||
|
||||
4. Create and push tag:
|
||||
|
||||
```bash
|
||||
git tag -a tach-typography-v0.1.0 -m "@hublib-web/tach-typography v0.1.0"
|
||||
git push origin main --follow-tags
|
||||
```
|
||||
|
||||
Detailed docs:
|
||||
|
||||
- [Release policy](../../docs/release-policy.md)
|
||||
- [Git installation](../../docs/git-installation.md)
|
||||
|
||||
## React usage (Ant Design)
|
||||
|
||||
```tsx
|
||||
import "@hublib-web/tach-typography/styles.css";
|
||||
import { TachTypography } from "@hublib-web/tach-typography/react";
|
||||
|
||||
export const Example = () => (
|
||||
<TachTypography.Text.Body color="link" weight="bold" ellipsis={{ rows: 1 }}>
|
||||
Hello from React + AntD
|
||||
</TachTypography.Text.Body>
|
||||
);
|
||||
```
|
||||
|
||||
## Angular usage (NG-ZORRO)
|
||||
|
||||
```ts
|
||||
import { Component } from "@angular/core";
|
||||
import { TachTypographyDirective, TachTypographyNzModule } from "@hublib-web/tach-typography/angular";
|
||||
|
||||
@Component({
|
||||
selector: "app-example",
|
||||
standalone: true,
|
||||
imports: [TachTypographyNzModule, TachTypographyDirective],
|
||||
template: `
|
||||
<span
|
||||
nz-typography
|
||||
tachTypography
|
||||
tachTypography="Body"
|
||||
tachTypographyColor="link"
|
||||
tachTypographyWeight="bold"
|
||||
>
|
||||
Hello from Angular + NG-ZORRO
|
||||
</span>
|
||||
`,
|
||||
})
|
||||
export class ExampleComponent {}
|
||||
```
|
||||
|
||||
## Storybook (dev/design system)
|
||||
|
||||
Run from repository root:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/tach-typography storybook
|
||||
```
|
||||
|
||||
Build static Storybook:
|
||||
|
||||
```bash
|
||||
yarn workspace @hublib-web/tach-typography storybook:build
|
||||
```
|
||||
204
packages/tach-typography/dist/angular/index.cjs
vendored
Normal file
204
packages/tach-typography/dist/angular/index.cjs
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
'use strict';
|
||||
|
||||
var core = require('@angular/core');
|
||||
var typography = require('ng-zorro-antd/typography');
|
||||
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
||||
var __typeError = (msg) => {
|
||||
throw TypeError(msg);
|
||||
};
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
||||
var __decoratorStart = (base) => [, , , __create(null)];
|
||||
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
||||
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
||||
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
||||
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
||||
var __runInitializers = (array, flags, self, value) => {
|
||||
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
||||
return value;
|
||||
};
|
||||
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
||||
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
||||
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
||||
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
||||
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
||||
return __privateGet(this, extra);
|
||||
}, set [name](x) {
|
||||
return __privateSet(this, extra, x);
|
||||
} }, name));
|
||||
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
||||
for (var i = decorators.length - 1; i >= 0; i--) {
|
||||
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
||||
if (k) {
|
||||
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
||||
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
||||
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
||||
}
|
||||
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
||||
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
||||
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
||||
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
||||
}
|
||||
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
||||
};
|
||||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
||||
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
||||
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
||||
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
||||
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
||||
|
||||
// src/core/classnames.ts
|
||||
var BASE_CLASS = "tach-typography";
|
||||
var join = (...parts) => parts.filter(Boolean).join(" ");
|
||||
var tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className
|
||||
} = {}) => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className
|
||||
);
|
||||
};
|
||||
var tachTypographyClassList = (options = {}) => {
|
||||
return tachTypographyClassName(options).split(" ").filter(Boolean);
|
||||
};
|
||||
|
||||
// src/core/ellipsis.ts
|
||||
var tachTypographyEllipsisStyle = (ellipsis) => {
|
||||
if (!ellipsis) {
|
||||
return void 0;
|
||||
}
|
||||
const rows = typeof ellipsis === "object" ? ellipsis.rows ?? 1 : 1;
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: rows
|
||||
};
|
||||
};
|
||||
|
||||
// src/angular/index.ts
|
||||
var camelToKebab = (value) => value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
||||
var toCssProperty = (styleKey) => {
|
||||
if (styleKey.startsWith("Webkit")) {
|
||||
return `-webkit-${camelToKebab(styleKey.slice(6))}`;
|
||||
}
|
||||
return camelToKebab(styleKey);
|
||||
};
|
||||
var tachAngularTypographyClassName = (options = {}) => {
|
||||
return tachTypographyClassName(options);
|
||||
};
|
||||
var tachAngularTypographyClassList = (options = {}) => {
|
||||
return tachTypographyClassList(options);
|
||||
};
|
||||
var tachAngularTypographyStyles = (ellipsis, preserveStyle = {}) => {
|
||||
const ellipsisStyle = tachTypographyEllipsisStyle(ellipsis);
|
||||
if (!ellipsisStyle) {
|
||||
return preserveStyle;
|
||||
}
|
||||
return {
|
||||
...ellipsisStyle,
|
||||
...preserveStyle
|
||||
};
|
||||
};
|
||||
var _tachTypographyEllipsis_dec, _tachTypographyClassName_dec, _tachTypographyClickable_dec, _tachTypographyWeight_dec, _tachTypographyColor_dec, _tachTypographyVariant_dec, _tachTypography_dec, _TachTypographyDirective_decorators, _init;
|
||||
_TachTypographyDirective_decorators = [core.Directive({
|
||||
selector: "[tachTypography]",
|
||||
standalone: true
|
||||
})], _tachTypography_dec = [core.Input()], _tachTypographyVariant_dec = [core.Input()], _tachTypographyColor_dec = [core.Input()], _tachTypographyWeight_dec = [core.Input()], _tachTypographyClickable_dec = [core.Input()], _tachTypographyClassName_dec = [core.Input()], _tachTypographyEllipsis_dec = [core.Input()];
|
||||
exports.TachTypographyDirective = class TachTypographyDirective {
|
||||
constructor(elementRef, renderer) {
|
||||
this.elementRef = elementRef;
|
||||
this.renderer = renderer;
|
||||
__publicField(this, "tachTypography", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
||||
__publicField(this, "tachTypographyVariant", __runInitializers(_init, 12, this, "Body")), __runInitializers(_init, 15, this);
|
||||
__publicField(this, "tachTypographyColor", __runInitializers(_init, 16, this, "primary")), __runInitializers(_init, 19, this);
|
||||
__publicField(this, "tachTypographyWeight", __runInitializers(_init, 20, this, "normal")), __runInitializers(_init, 23, this);
|
||||
__publicField(this, "tachTypographyClickable", __runInitializers(_init, 24, this, false)), __runInitializers(_init, 27, this);
|
||||
__publicField(this, "tachTypographyClassName", __runInitializers(_init, 28, this)), __runInitializers(_init, 31, this);
|
||||
__publicField(this, "tachTypographyEllipsis", __runInitializers(_init, 32, this)), __runInitializers(_init, 35, this);
|
||||
__publicField(this, "appliedClasses", /* @__PURE__ */ new Set());
|
||||
__publicField(this, "appliedStyleProperties", /* @__PURE__ */ new Set());
|
||||
}
|
||||
ngOnChanges(_changes) {
|
||||
this.syncClasses();
|
||||
this.syncEllipsisStyles();
|
||||
}
|
||||
syncClasses() {
|
||||
const nextClassList = tachTypographyClassList({
|
||||
variant: this.tachTypography || this.tachTypographyVariant,
|
||||
color: this.tachTypographyColor,
|
||||
weight: this.tachTypographyWeight,
|
||||
clickable: this.tachTypographyClickable,
|
||||
className: this.tachTypographyClassName
|
||||
});
|
||||
const nextSet = new Set(nextClassList);
|
||||
for (const className of this.appliedClasses) {
|
||||
if (!nextSet.has(className)) {
|
||||
this.renderer.removeClass(this.elementRef.nativeElement, className);
|
||||
}
|
||||
}
|
||||
for (const className of nextSet) {
|
||||
this.renderer.addClass(this.elementRef.nativeElement, className);
|
||||
}
|
||||
this.appliedClasses.clear();
|
||||
for (const className of nextSet) {
|
||||
this.appliedClasses.add(className);
|
||||
}
|
||||
}
|
||||
syncEllipsisStyles() {
|
||||
const nextStyles = tachTypographyEllipsisStyle(this.tachTypographyEllipsis) || {};
|
||||
const nextStyleKeys = new Set(Object.keys(nextStyles));
|
||||
for (const styleKey of this.appliedStyleProperties) {
|
||||
if (!nextStyleKeys.has(styleKey)) {
|
||||
this.renderer.removeStyle(this.elementRef.nativeElement, toCssProperty(styleKey));
|
||||
}
|
||||
}
|
||||
for (const [styleKey, styleValue] of Object.entries(nextStyles)) {
|
||||
this.renderer.setStyle(this.elementRef.nativeElement, toCssProperty(styleKey), styleValue);
|
||||
}
|
||||
this.appliedStyleProperties.clear();
|
||||
for (const styleKey of nextStyleKeys) {
|
||||
this.appliedStyleProperties.add(styleKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
_init = __decoratorStart();
|
||||
__decorateElement(_init, 5, "tachTypography", _tachTypography_dec, exports.TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyVariant", _tachTypographyVariant_dec, exports.TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyColor", _tachTypographyColor_dec, exports.TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyWeight", _tachTypographyWeight_dec, exports.TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyClickable", _tachTypographyClickable_dec, exports.TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyClassName", _tachTypographyClassName_dec, exports.TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyEllipsis", _tachTypographyEllipsis_dec, exports.TachTypographyDirective);
|
||||
exports.TachTypographyDirective = __decorateElement(_init, 0, "TachTypographyDirective", _TachTypographyDirective_decorators, exports.TachTypographyDirective);
|
||||
__runInitializers(_init, 1, exports.TachTypographyDirective);
|
||||
var _TachTypographyNzModule_decorators, _init2;
|
||||
_TachTypographyNzModule_decorators = [core.NgModule({
|
||||
imports: [typography.NzTypographyModule, exports.TachTypographyDirective],
|
||||
exports: [typography.NzTypographyModule, exports.TachTypographyDirective]
|
||||
})];
|
||||
exports.TachTypographyNzModule = class TachTypographyNzModule {
|
||||
};
|
||||
_init2 = __decoratorStart();
|
||||
exports.TachTypographyNzModule = __decorateElement(_init2, 0, "TachTypographyNzModule", _TachTypographyNzModule_decorators, exports.TachTypographyNzModule);
|
||||
__runInitializers(_init2, 1, exports.TachTypographyNzModule);
|
||||
|
||||
exports.tachAngularTypographyClassList = tachAngularTypographyClassList;
|
||||
exports.tachAngularTypographyClassName = tachAngularTypographyClassName;
|
||||
exports.tachAngularTypographyStyles = tachAngularTypographyStyles;
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
packages/tach-typography/dist/angular/index.cjs.map
vendored
Normal file
1
packages/tach-typography/dist/angular/index.cjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
31
packages/tach-typography/dist/angular/index.d.cts
vendored
Normal file
31
packages/tach-typography/dist/angular/index.d.cts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { OnChanges, ElementRef, Renderer2, SimpleChanges } from '@angular/core';
|
||||
import { T as TypographyClassOptions, d as TypographyRenderOptions, e as TypographyVariant, c as TypographyColor, f as TypographyWeight, E as EllipsisOptions } from '../types-CQyFuLqp.cjs';
|
||||
|
||||
type AngularTypographyClassInput = TypographyClassOptions;
|
||||
interface AngularTypographyRenderOptions extends TypographyRenderOptions {
|
||||
preserveStyle?: Record<string, string | number>;
|
||||
}
|
||||
declare const tachAngularTypographyClassName: (options?: AngularTypographyClassInput) => string;
|
||||
declare const tachAngularTypographyClassList: (options?: AngularTypographyClassInput) => string[];
|
||||
declare const tachAngularTypographyStyles: (ellipsis?: EllipsisOptions, preserveStyle?: Record<string, string | number>) => Record<string, string | number>;
|
||||
declare class TachTypographyDirective implements OnChanges {
|
||||
private readonly elementRef;
|
||||
private readonly renderer;
|
||||
tachTypography: TypographyVariant | "" | undefined;
|
||||
tachTypographyVariant: TypographyVariant;
|
||||
tachTypographyColor: TypographyColor;
|
||||
tachTypographyWeight: TypographyWeight;
|
||||
tachTypographyClickable: boolean;
|
||||
tachTypographyClassName: string | undefined;
|
||||
tachTypographyEllipsis: EllipsisOptions | undefined;
|
||||
private readonly appliedClasses;
|
||||
private readonly appliedStyleProperties;
|
||||
constructor(elementRef: ElementRef<HTMLElement>, renderer: Renderer2);
|
||||
ngOnChanges(_changes: SimpleChanges): void;
|
||||
private syncClasses;
|
||||
private syncEllipsisStyles;
|
||||
}
|
||||
declare class TachTypographyNzModule {
|
||||
}
|
||||
|
||||
export { type AngularTypographyClassInput, type AngularTypographyRenderOptions, TachTypographyDirective, TachTypographyNzModule, tachAngularTypographyClassList, tachAngularTypographyClassName, tachAngularTypographyStyles };
|
||||
31
packages/tach-typography/dist/angular/index.d.ts
vendored
Normal file
31
packages/tach-typography/dist/angular/index.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { OnChanges, ElementRef, Renderer2, SimpleChanges } from '@angular/core';
|
||||
import { T as TypographyClassOptions, d as TypographyRenderOptions, e as TypographyVariant, c as TypographyColor, f as TypographyWeight, E as EllipsisOptions } from '../types-CQyFuLqp.js';
|
||||
|
||||
type AngularTypographyClassInput = TypographyClassOptions;
|
||||
interface AngularTypographyRenderOptions extends TypographyRenderOptions {
|
||||
preserveStyle?: Record<string, string | number>;
|
||||
}
|
||||
declare const tachAngularTypographyClassName: (options?: AngularTypographyClassInput) => string;
|
||||
declare const tachAngularTypographyClassList: (options?: AngularTypographyClassInput) => string[];
|
||||
declare const tachAngularTypographyStyles: (ellipsis?: EllipsisOptions, preserveStyle?: Record<string, string | number>) => Record<string, string | number>;
|
||||
declare class TachTypographyDirective implements OnChanges {
|
||||
private readonly elementRef;
|
||||
private readonly renderer;
|
||||
tachTypography: TypographyVariant | "" | undefined;
|
||||
tachTypographyVariant: TypographyVariant;
|
||||
tachTypographyColor: TypographyColor;
|
||||
tachTypographyWeight: TypographyWeight;
|
||||
tachTypographyClickable: boolean;
|
||||
tachTypographyClassName: string | undefined;
|
||||
tachTypographyEllipsis: EllipsisOptions | undefined;
|
||||
private readonly appliedClasses;
|
||||
private readonly appliedStyleProperties;
|
||||
constructor(elementRef: ElementRef<HTMLElement>, renderer: Renderer2);
|
||||
ngOnChanges(_changes: SimpleChanges): void;
|
||||
private syncClasses;
|
||||
private syncEllipsisStyles;
|
||||
}
|
||||
declare class TachTypographyNzModule {
|
||||
}
|
||||
|
||||
export { type AngularTypographyClassInput, type AngularTypographyRenderOptions, TachTypographyDirective, TachTypographyNzModule, tachAngularTypographyClassList, tachAngularTypographyClassName, tachAngularTypographyStyles };
|
||||
200
packages/tach-typography/dist/angular/index.js
vendored
Normal file
200
packages/tach-typography/dist/angular/index.js
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Directive, Input, NgModule } from '@angular/core';
|
||||
import { NzTypographyModule } from 'ng-zorro-antd/typography';
|
||||
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
||||
var __typeError = (msg) => {
|
||||
throw TypeError(msg);
|
||||
};
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
||||
var __decoratorStart = (base) => [, , , __create(null)];
|
||||
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
||||
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
||||
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
||||
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
||||
var __runInitializers = (array, flags, self, value) => {
|
||||
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
||||
return value;
|
||||
};
|
||||
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
||||
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
||||
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
||||
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
||||
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
||||
return __privateGet(this, extra);
|
||||
}, set [name](x) {
|
||||
return __privateSet(this, extra, x);
|
||||
} }, name));
|
||||
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
||||
for (var i = decorators.length - 1; i >= 0; i--) {
|
||||
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
||||
if (k) {
|
||||
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
||||
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
||||
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
||||
}
|
||||
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
||||
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
||||
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
||||
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
||||
}
|
||||
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
||||
};
|
||||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
||||
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
||||
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
||||
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
||||
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
||||
|
||||
// src/core/classnames.ts
|
||||
var BASE_CLASS = "tach-typography";
|
||||
var join = (...parts) => parts.filter(Boolean).join(" ");
|
||||
var tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className
|
||||
} = {}) => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className
|
||||
);
|
||||
};
|
||||
var tachTypographyClassList = (options = {}) => {
|
||||
return tachTypographyClassName(options).split(" ").filter(Boolean);
|
||||
};
|
||||
|
||||
// src/core/ellipsis.ts
|
||||
var tachTypographyEllipsisStyle = (ellipsis) => {
|
||||
if (!ellipsis) {
|
||||
return void 0;
|
||||
}
|
||||
const rows = typeof ellipsis === "object" ? ellipsis.rows ?? 1 : 1;
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: rows
|
||||
};
|
||||
};
|
||||
|
||||
// src/angular/index.ts
|
||||
var camelToKebab = (value) => value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
||||
var toCssProperty = (styleKey) => {
|
||||
if (styleKey.startsWith("Webkit")) {
|
||||
return `-webkit-${camelToKebab(styleKey.slice(6))}`;
|
||||
}
|
||||
return camelToKebab(styleKey);
|
||||
};
|
||||
var tachAngularTypographyClassName = (options = {}) => {
|
||||
return tachTypographyClassName(options);
|
||||
};
|
||||
var tachAngularTypographyClassList = (options = {}) => {
|
||||
return tachTypographyClassList(options);
|
||||
};
|
||||
var tachAngularTypographyStyles = (ellipsis, preserveStyle = {}) => {
|
||||
const ellipsisStyle = tachTypographyEllipsisStyle(ellipsis);
|
||||
if (!ellipsisStyle) {
|
||||
return preserveStyle;
|
||||
}
|
||||
return {
|
||||
...ellipsisStyle,
|
||||
...preserveStyle
|
||||
};
|
||||
};
|
||||
var _tachTypographyEllipsis_dec, _tachTypographyClassName_dec, _tachTypographyClickable_dec, _tachTypographyWeight_dec, _tachTypographyColor_dec, _tachTypographyVariant_dec, _tachTypography_dec, _TachTypographyDirective_decorators, _init;
|
||||
_TachTypographyDirective_decorators = [Directive({
|
||||
selector: "[tachTypography]",
|
||||
standalone: true
|
||||
})], _tachTypography_dec = [Input()], _tachTypographyVariant_dec = [Input()], _tachTypographyColor_dec = [Input()], _tachTypographyWeight_dec = [Input()], _tachTypographyClickable_dec = [Input()], _tachTypographyClassName_dec = [Input()], _tachTypographyEllipsis_dec = [Input()];
|
||||
var TachTypographyDirective = class {
|
||||
constructor(elementRef, renderer) {
|
||||
this.elementRef = elementRef;
|
||||
this.renderer = renderer;
|
||||
__publicField(this, "tachTypography", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
||||
__publicField(this, "tachTypographyVariant", __runInitializers(_init, 12, this, "Body")), __runInitializers(_init, 15, this);
|
||||
__publicField(this, "tachTypographyColor", __runInitializers(_init, 16, this, "primary")), __runInitializers(_init, 19, this);
|
||||
__publicField(this, "tachTypographyWeight", __runInitializers(_init, 20, this, "normal")), __runInitializers(_init, 23, this);
|
||||
__publicField(this, "tachTypographyClickable", __runInitializers(_init, 24, this, false)), __runInitializers(_init, 27, this);
|
||||
__publicField(this, "tachTypographyClassName", __runInitializers(_init, 28, this)), __runInitializers(_init, 31, this);
|
||||
__publicField(this, "tachTypographyEllipsis", __runInitializers(_init, 32, this)), __runInitializers(_init, 35, this);
|
||||
__publicField(this, "appliedClasses", /* @__PURE__ */ new Set());
|
||||
__publicField(this, "appliedStyleProperties", /* @__PURE__ */ new Set());
|
||||
}
|
||||
ngOnChanges(_changes) {
|
||||
this.syncClasses();
|
||||
this.syncEllipsisStyles();
|
||||
}
|
||||
syncClasses() {
|
||||
const nextClassList = tachTypographyClassList({
|
||||
variant: this.tachTypography || this.tachTypographyVariant,
|
||||
color: this.tachTypographyColor,
|
||||
weight: this.tachTypographyWeight,
|
||||
clickable: this.tachTypographyClickable,
|
||||
className: this.tachTypographyClassName
|
||||
});
|
||||
const nextSet = new Set(nextClassList);
|
||||
for (const className of this.appliedClasses) {
|
||||
if (!nextSet.has(className)) {
|
||||
this.renderer.removeClass(this.elementRef.nativeElement, className);
|
||||
}
|
||||
}
|
||||
for (const className of nextSet) {
|
||||
this.renderer.addClass(this.elementRef.nativeElement, className);
|
||||
}
|
||||
this.appliedClasses.clear();
|
||||
for (const className of nextSet) {
|
||||
this.appliedClasses.add(className);
|
||||
}
|
||||
}
|
||||
syncEllipsisStyles() {
|
||||
const nextStyles = tachTypographyEllipsisStyle(this.tachTypographyEllipsis) || {};
|
||||
const nextStyleKeys = new Set(Object.keys(nextStyles));
|
||||
for (const styleKey of this.appliedStyleProperties) {
|
||||
if (!nextStyleKeys.has(styleKey)) {
|
||||
this.renderer.removeStyle(this.elementRef.nativeElement, toCssProperty(styleKey));
|
||||
}
|
||||
}
|
||||
for (const [styleKey, styleValue] of Object.entries(nextStyles)) {
|
||||
this.renderer.setStyle(this.elementRef.nativeElement, toCssProperty(styleKey), styleValue);
|
||||
}
|
||||
this.appliedStyleProperties.clear();
|
||||
for (const styleKey of nextStyleKeys) {
|
||||
this.appliedStyleProperties.add(styleKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
_init = __decoratorStart();
|
||||
__decorateElement(_init, 5, "tachTypography", _tachTypography_dec, TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyVariant", _tachTypographyVariant_dec, TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyColor", _tachTypographyColor_dec, TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyWeight", _tachTypographyWeight_dec, TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyClickable", _tachTypographyClickable_dec, TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyClassName", _tachTypographyClassName_dec, TachTypographyDirective);
|
||||
__decorateElement(_init, 5, "tachTypographyEllipsis", _tachTypographyEllipsis_dec, TachTypographyDirective);
|
||||
TachTypographyDirective = __decorateElement(_init, 0, "TachTypographyDirective", _TachTypographyDirective_decorators, TachTypographyDirective);
|
||||
__runInitializers(_init, 1, TachTypographyDirective);
|
||||
var _TachTypographyNzModule_decorators, _init2;
|
||||
_TachTypographyNzModule_decorators = [NgModule({
|
||||
imports: [NzTypographyModule, TachTypographyDirective],
|
||||
exports: [NzTypographyModule, TachTypographyDirective]
|
||||
})];
|
||||
var TachTypographyNzModule = class {
|
||||
};
|
||||
_init2 = __decoratorStart();
|
||||
TachTypographyNzModule = __decorateElement(_init2, 0, "TachTypographyNzModule", _TachTypographyNzModule_decorators, TachTypographyNzModule);
|
||||
__runInitializers(_init2, 1, TachTypographyNzModule);
|
||||
|
||||
export { TachTypographyDirective, TachTypographyNzModule, tachAngularTypographyClassList, tachAngularTypographyClassName, tachAngularTypographyStyles };
|
||||
//# sourceMappingURL=index.js.map
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/tach-typography/dist/angular/index.js.map
vendored
Normal file
1
packages/tach-typography/dist/angular/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
87
packages/tach-typography/dist/core/index.cjs
vendored
Normal file
87
packages/tach-typography/dist/core/index.cjs
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
// src/core/types.ts
|
||||
var TYPOGRAPHY_VARIANTS = [
|
||||
"LargeTitle",
|
||||
"Title1",
|
||||
"Title2",
|
||||
"Title3",
|
||||
"Headline",
|
||||
"Body",
|
||||
"Inputs",
|
||||
"Subheadline",
|
||||
"FootnoteUnderline",
|
||||
"Footnote",
|
||||
"Caption",
|
||||
"Caption2",
|
||||
"AccentH1",
|
||||
"AccentH2",
|
||||
"AccentSubttl",
|
||||
"AccentSubttl2",
|
||||
"AccentCaption",
|
||||
"AccentCaption2",
|
||||
"AccentRegularM",
|
||||
"AccentRegularS",
|
||||
"AccentLargeTtl",
|
||||
"AppMediumBody",
|
||||
"AppMediumSubtext",
|
||||
"AppMediumSubtextUnderline"
|
||||
];
|
||||
var TYPOGRAPHY_COLORS = [
|
||||
"primary",
|
||||
"secondary",
|
||||
"tertiary",
|
||||
"quaternary",
|
||||
"link",
|
||||
"white",
|
||||
"dark",
|
||||
"alert",
|
||||
"malahit",
|
||||
"attantion"
|
||||
];
|
||||
|
||||
// src/core/classnames.ts
|
||||
var BASE_CLASS = "tach-typography";
|
||||
var join = (...parts) => parts.filter(Boolean).join(" ");
|
||||
var tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className
|
||||
} = {}) => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className
|
||||
);
|
||||
};
|
||||
var tachTypographyClassList = (options = {}) => {
|
||||
return tachTypographyClassName(options).split(" ").filter(Boolean);
|
||||
};
|
||||
|
||||
// src/core/ellipsis.ts
|
||||
var tachTypographyEllipsisStyle = (ellipsis) => {
|
||||
if (!ellipsis) {
|
||||
return void 0;
|
||||
}
|
||||
const rows = typeof ellipsis === "object" ? ellipsis.rows ?? 1 : 1;
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: rows
|
||||
};
|
||||
};
|
||||
|
||||
exports.TYPOGRAPHY_COLORS = TYPOGRAPHY_COLORS;
|
||||
exports.TYPOGRAPHY_VARIANTS = TYPOGRAPHY_VARIANTS;
|
||||
exports.tachTypographyClassList = tachTypographyClassList;
|
||||
exports.tachTypographyClassName = tachTypographyClassName;
|
||||
exports.tachTypographyEllipsisStyle = tachTypographyEllipsisStyle;
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
packages/tach-typography/dist/core/index.cjs.map
vendored
Normal file
1
packages/tach-typography/dist/core/index.cjs.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../src/core/types.ts","../../src/core/classnames.ts","../../src/core/ellipsis.ts"],"names":[],"mappings":";;;AAAO,IAAM,mBAAA,GAAsB;AAAA,EACjC,YAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,mBAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,eAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF;AAEO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,SAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;;;ACpCA,IAAM,UAAA,GAAa,iBAAA;AAEnB,IAAM,IAAA,GAAO,IAAI,KAAA,KACf,KAAA,CAAM,OAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAEzB,IAAM,0BAA0B,CAAC;AAAA,EACtC,OAAA,GAAU,MAAA;AAAA,EACV,KAAA,GAAQ,SAAA;AAAA,EACR,MAAA,GAAS,QAAA;AAAA,EACT,SAAA,GAAY,KAAA;AAAA,EACZ;AACF,CAAA,GAA4B,EAAC,KAAc;AACzC,EAAA,OAAO,IAAA;AAAA,IACL,UAAA;AAAA,IACA,CAAA,EAAG,UAAU,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA;AAAA,IACzB,CAAA,EAAG,UAAU,CAAA,QAAA,EAAW,KAAK,CAAA,CAAA;AAAA,IAC7B,MAAA,KAAW,MAAA,IAAU,CAAA,EAAG,UAAU,CAAA,MAAA,CAAA;AAAA,IAClC,SAAA,IAAa,GAAG,UAAU,CAAA,SAAA,CAAA;AAAA,IAC1B;AAAA,GACF;AACF;AAEO,IAAM,uBAAA,GAA0B,CAAC,OAAA,GAAkC,EAAC,KAAgB;AACzF,EAAA,OAAO,wBAAwB,OAAO,CAAA,CACnC,MAAM,GAAG,CAAA,CACT,OAAO,OAAO,CAAA;AACnB;;;ACxBO,IAAM,2BAAA,GAA8B,CACzC,QAAA,KAC4B;AAC5B,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAO,OAAO,QAAA,KAAa,QAAA,GAAW,QAAA,CAAS,QAAQ,CAAA,GAAI,CAAA;AAEjE,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,QAAA;AAAA,IACV,YAAA,EAAc,UAAA;AAAA,IACd,OAAA,EAAS,aAAA;AAAA,IACT,eAAA,EAAiB,UAAA;AAAA,IACjB,eAAA,EAAiB;AAAA,GACnB;AACF","file":"index.cjs","sourcesContent":["export const TYPOGRAPHY_VARIANTS = [\n \"LargeTitle\",\n \"Title1\",\n \"Title2\",\n \"Title3\",\n \"Headline\",\n \"Body\",\n \"Inputs\",\n \"Subheadline\",\n \"FootnoteUnderline\",\n \"Footnote\",\n \"Caption\",\n \"Caption2\",\n \"AccentH1\",\n \"AccentH2\",\n \"AccentSubttl\",\n \"AccentSubttl2\",\n \"AccentCaption\",\n \"AccentCaption2\",\n \"AccentRegularM\",\n \"AccentRegularS\",\n \"AccentLargeTtl\",\n \"AppMediumBody\",\n \"AppMediumSubtext\",\n \"AppMediumSubtextUnderline\",\n] as const;\n\nexport const TYPOGRAPHY_COLORS = [\n \"primary\",\n \"secondary\",\n \"tertiary\",\n \"quaternary\",\n \"link\",\n \"white\",\n \"dark\",\n \"alert\",\n \"malahit\",\n \"attantion\",\n] as const;\n\nexport type TypographyVariant = (typeof TYPOGRAPHY_VARIANTS)[number];\nexport type TypographyColor = (typeof TYPOGRAPHY_COLORS)[number];\nexport type TypographyWeight = \"normal\" | \"bold\";\n\nexport interface TypographyClassOptions {\n variant?: TypographyVariant;\n color?: TypographyColor;\n weight?: TypographyWeight;\n clickable?: boolean;\n className?: string | undefined;\n}\n\nexport type EllipsisOptions =\n | boolean\n | {\n rows?: number;\n };\n\nexport interface TypographyRenderOptions extends TypographyClassOptions {\n ellipsis?: EllipsisOptions;\n}\n","import type { TypographyClassOptions } from \"./types\";\n\nconst BASE_CLASS = \"tach-typography\";\n\nconst join = (...parts: Array<string | undefined | null | false>): string =>\n parts.filter(Boolean).join(\" \");\n\nexport const tachTypographyClassName = ({\n variant = \"Body\",\n color = \"primary\",\n weight = \"normal\",\n clickable = false,\n className,\n}: TypographyClassOptions = {}): string => {\n return join(\n BASE_CLASS,\n `${BASE_CLASS}--${variant}`,\n `${BASE_CLASS}--color-${color}`,\n weight === \"bold\" && `${BASE_CLASS}--bold`,\n clickable && `${BASE_CLASS}--pointer`,\n className,\n );\n};\n\nexport const tachTypographyClassList = (options: TypographyClassOptions = {}): string[] => {\n return tachTypographyClassName(options)\n .split(\" \")\n .filter(Boolean);\n};\n","import type { EllipsisOptions } from \"./types\";\n\ntype StyleRecord = Record<string, string | number>;\n\nexport const tachTypographyEllipsisStyle = (\n ellipsis?: EllipsisOptions,\n): StyleRecord | undefined => {\n if (!ellipsis) {\n return undefined;\n }\n\n const rows = typeof ellipsis === \"object\" ? ellipsis.rows ?? 1 : 1;\n\n return {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: rows,\n };\n};\n"]}
|
||||
10
packages/tach-typography/dist/core/index.d.cts
vendored
Normal file
10
packages/tach-typography/dist/core/index.d.cts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { T as TypographyClassOptions, E as EllipsisOptions } from '../types-CQyFuLqp.cjs';
|
||||
export { a as TYPOGRAPHY_COLORS, b as TYPOGRAPHY_VARIANTS, c as TypographyColor, d as TypographyRenderOptions, e as TypographyVariant, f as TypographyWeight } from '../types-CQyFuLqp.cjs';
|
||||
|
||||
declare const tachTypographyClassName: ({ variant, color, weight, clickable, className, }?: TypographyClassOptions) => string;
|
||||
declare const tachTypographyClassList: (options?: TypographyClassOptions) => string[];
|
||||
|
||||
type StyleRecord = Record<string, string | number>;
|
||||
declare const tachTypographyEllipsisStyle: (ellipsis?: EllipsisOptions) => StyleRecord | undefined;
|
||||
|
||||
export { EllipsisOptions, TypographyClassOptions, tachTypographyClassList, tachTypographyClassName, tachTypographyEllipsisStyle };
|
||||
10
packages/tach-typography/dist/core/index.d.ts
vendored
Normal file
10
packages/tach-typography/dist/core/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { T as TypographyClassOptions, E as EllipsisOptions } from '../types-CQyFuLqp.js';
|
||||
export { a as TYPOGRAPHY_COLORS, b as TYPOGRAPHY_VARIANTS, c as TypographyColor, d as TypographyRenderOptions, e as TypographyVariant, f as TypographyWeight } from '../types-CQyFuLqp.js';
|
||||
|
||||
declare const tachTypographyClassName: ({ variant, color, weight, clickable, className, }?: TypographyClassOptions) => string;
|
||||
declare const tachTypographyClassList: (options?: TypographyClassOptions) => string[];
|
||||
|
||||
type StyleRecord = Record<string, string | number>;
|
||||
declare const tachTypographyEllipsisStyle: (ellipsis?: EllipsisOptions) => StyleRecord | undefined;
|
||||
|
||||
export { EllipsisOptions, TypographyClassOptions, tachTypographyClassList, tachTypographyClassName, tachTypographyEllipsisStyle };
|
||||
81
packages/tach-typography/dist/core/index.js
vendored
Normal file
81
packages/tach-typography/dist/core/index.js
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
// src/core/types.ts
|
||||
var TYPOGRAPHY_VARIANTS = [
|
||||
"LargeTitle",
|
||||
"Title1",
|
||||
"Title2",
|
||||
"Title3",
|
||||
"Headline",
|
||||
"Body",
|
||||
"Inputs",
|
||||
"Subheadline",
|
||||
"FootnoteUnderline",
|
||||
"Footnote",
|
||||
"Caption",
|
||||
"Caption2",
|
||||
"AccentH1",
|
||||
"AccentH2",
|
||||
"AccentSubttl",
|
||||
"AccentSubttl2",
|
||||
"AccentCaption",
|
||||
"AccentCaption2",
|
||||
"AccentRegularM",
|
||||
"AccentRegularS",
|
||||
"AccentLargeTtl",
|
||||
"AppMediumBody",
|
||||
"AppMediumSubtext",
|
||||
"AppMediumSubtextUnderline"
|
||||
];
|
||||
var TYPOGRAPHY_COLORS = [
|
||||
"primary",
|
||||
"secondary",
|
||||
"tertiary",
|
||||
"quaternary",
|
||||
"link",
|
||||
"white",
|
||||
"dark",
|
||||
"alert",
|
||||
"malahit",
|
||||
"attantion"
|
||||
];
|
||||
|
||||
// src/core/classnames.ts
|
||||
var BASE_CLASS = "tach-typography";
|
||||
var join = (...parts) => parts.filter(Boolean).join(" ");
|
||||
var tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className
|
||||
} = {}) => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className
|
||||
);
|
||||
};
|
||||
var tachTypographyClassList = (options = {}) => {
|
||||
return tachTypographyClassName(options).split(" ").filter(Boolean);
|
||||
};
|
||||
|
||||
// src/core/ellipsis.ts
|
||||
var tachTypographyEllipsisStyle = (ellipsis) => {
|
||||
if (!ellipsis) {
|
||||
return void 0;
|
||||
}
|
||||
const rows = typeof ellipsis === "object" ? ellipsis.rows ?? 1 : 1;
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: rows
|
||||
};
|
||||
};
|
||||
|
||||
export { TYPOGRAPHY_COLORS, TYPOGRAPHY_VARIANTS, tachTypographyClassList, tachTypographyClassName, tachTypographyEllipsisStyle };
|
||||
//# sourceMappingURL=index.js.map
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/tach-typography/dist/core/index.js.map
vendored
Normal file
1
packages/tach-typography/dist/core/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../src/core/types.ts","../../src/core/classnames.ts","../../src/core/ellipsis.ts"],"names":[],"mappings":";AAAO,IAAM,mBAAA,GAAsB;AAAA,EACjC,YAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,mBAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,eAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF;AAEO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,SAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;;;ACpCA,IAAM,UAAA,GAAa,iBAAA;AAEnB,IAAM,IAAA,GAAO,IAAI,KAAA,KACf,KAAA,CAAM,OAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAEzB,IAAM,0BAA0B,CAAC;AAAA,EACtC,OAAA,GAAU,MAAA;AAAA,EACV,KAAA,GAAQ,SAAA;AAAA,EACR,MAAA,GAAS,QAAA;AAAA,EACT,SAAA,GAAY,KAAA;AAAA,EACZ;AACF,CAAA,GAA4B,EAAC,KAAc;AACzC,EAAA,OAAO,IAAA;AAAA,IACL,UAAA;AAAA,IACA,CAAA,EAAG,UAAU,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA;AAAA,IACzB,CAAA,EAAG,UAAU,CAAA,QAAA,EAAW,KAAK,CAAA,CAAA;AAAA,IAC7B,MAAA,KAAW,MAAA,IAAU,CAAA,EAAG,UAAU,CAAA,MAAA,CAAA;AAAA,IAClC,SAAA,IAAa,GAAG,UAAU,CAAA,SAAA,CAAA;AAAA,IAC1B;AAAA,GACF;AACF;AAEO,IAAM,uBAAA,GAA0B,CAAC,OAAA,GAAkC,EAAC,KAAgB;AACzF,EAAA,OAAO,wBAAwB,OAAO,CAAA,CACnC,MAAM,GAAG,CAAA,CACT,OAAO,OAAO,CAAA;AACnB;;;ACxBO,IAAM,2BAAA,GAA8B,CACzC,QAAA,KAC4B;AAC5B,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAO,OAAO,QAAA,KAAa,QAAA,GAAW,QAAA,CAAS,QAAQ,CAAA,GAAI,CAAA;AAEjE,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,QAAA;AAAA,IACV,YAAA,EAAc,UAAA;AAAA,IACd,OAAA,EAAS,aAAA;AAAA,IACT,eAAA,EAAiB,UAAA;AAAA,IACjB,eAAA,EAAiB;AAAA,GACnB;AACF","file":"index.js","sourcesContent":["export const TYPOGRAPHY_VARIANTS = [\n \"LargeTitle\",\n \"Title1\",\n \"Title2\",\n \"Title3\",\n \"Headline\",\n \"Body\",\n \"Inputs\",\n \"Subheadline\",\n \"FootnoteUnderline\",\n \"Footnote\",\n \"Caption\",\n \"Caption2\",\n \"AccentH1\",\n \"AccentH2\",\n \"AccentSubttl\",\n \"AccentSubttl2\",\n \"AccentCaption\",\n \"AccentCaption2\",\n \"AccentRegularM\",\n \"AccentRegularS\",\n \"AccentLargeTtl\",\n \"AppMediumBody\",\n \"AppMediumSubtext\",\n \"AppMediumSubtextUnderline\",\n] as const;\n\nexport const TYPOGRAPHY_COLORS = [\n \"primary\",\n \"secondary\",\n \"tertiary\",\n \"quaternary\",\n \"link\",\n \"white\",\n \"dark\",\n \"alert\",\n \"malahit\",\n \"attantion\",\n] as const;\n\nexport type TypographyVariant = (typeof TYPOGRAPHY_VARIANTS)[number];\nexport type TypographyColor = (typeof TYPOGRAPHY_COLORS)[number];\nexport type TypographyWeight = \"normal\" | \"bold\";\n\nexport interface TypographyClassOptions {\n variant?: TypographyVariant;\n color?: TypographyColor;\n weight?: TypographyWeight;\n clickable?: boolean;\n className?: string | undefined;\n}\n\nexport type EllipsisOptions =\n | boolean\n | {\n rows?: number;\n };\n\nexport interface TypographyRenderOptions extends TypographyClassOptions {\n ellipsis?: EllipsisOptions;\n}\n","import type { TypographyClassOptions } from \"./types\";\n\nconst BASE_CLASS = \"tach-typography\";\n\nconst join = (...parts: Array<string | undefined | null | false>): string =>\n parts.filter(Boolean).join(\" \");\n\nexport const tachTypographyClassName = ({\n variant = \"Body\",\n color = \"primary\",\n weight = \"normal\",\n clickable = false,\n className,\n}: TypographyClassOptions = {}): string => {\n return join(\n BASE_CLASS,\n `${BASE_CLASS}--${variant}`,\n `${BASE_CLASS}--color-${color}`,\n weight === \"bold\" && `${BASE_CLASS}--bold`,\n clickable && `${BASE_CLASS}--pointer`,\n className,\n );\n};\n\nexport const tachTypographyClassList = (options: TypographyClassOptions = {}): string[] => {\n return tachTypographyClassName(options)\n .split(\" \")\n .filter(Boolean);\n};\n","import type { EllipsisOptions } from \"./types\";\n\ntype StyleRecord = Record<string, string | number>;\n\nexport const tachTypographyEllipsisStyle = (\n ellipsis?: EllipsisOptions,\n): StyleRecord | undefined => {\n if (!ellipsis) {\n return undefined;\n }\n\n const rows = typeof ellipsis === \"object\" ? ellipsis.rows ?? 1 : 1;\n\n return {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: rows,\n };\n};\n"]}
|
||||
88
packages/tach-typography/dist/react/index.cjs
vendored
Normal file
88
packages/tach-typography/dist/react/index.cjs
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var antd = require('antd');
|
||||
var jsxRuntime = require('react/jsx-runtime');
|
||||
|
||||
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
||||
|
||||
var React__default = /*#__PURE__*/_interopDefault(React);
|
||||
|
||||
// src/react/index.tsx
|
||||
|
||||
// src/core/classnames.ts
|
||||
var BASE_CLASS = "tach-typography";
|
||||
var join = (...parts) => parts.filter(Boolean).join(" ");
|
||||
var tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className
|
||||
} = {}) => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className
|
||||
);
|
||||
};
|
||||
var createTypographyVariant = (Component, variant) => {
|
||||
const Variant = React__default.default.forwardRef(
|
||||
({ color = "primary", weight = "normal", className, onClick, ...rest }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
||||
Component,
|
||||
{
|
||||
ref,
|
||||
className: tachTypographyClassName({
|
||||
variant,
|
||||
color,
|
||||
weight,
|
||||
className,
|
||||
clickable: Boolean(onClick)
|
||||
}),
|
||||
onClick,
|
||||
...rest
|
||||
}
|
||||
)
|
||||
);
|
||||
Variant.displayName = String(variant);
|
||||
return Variant;
|
||||
};
|
||||
var createTypographyComponent = (Component) => ({
|
||||
LargeTitle: createTypographyVariant(Component, "LargeTitle"),
|
||||
Title1: createTypographyVariant(Component, "Title1"),
|
||||
Title2: createTypographyVariant(Component, "Title2"),
|
||||
Title3: createTypographyVariant(Component, "Title3"),
|
||||
Headline: createTypographyVariant(Component, "Headline"),
|
||||
Body: createTypographyVariant(Component, "Body"),
|
||||
Inputs: createTypographyVariant(Component, "Inputs"),
|
||||
Subheadline: createTypographyVariant(Component, "Subheadline"),
|
||||
FootnoteUnderline: createTypographyVariant(Component, "FootnoteUnderline"),
|
||||
Footnote: createTypographyVariant(Component, "Footnote"),
|
||||
Caption: createTypographyVariant(Component, "Caption"),
|
||||
Caption2: createTypographyVariant(Component, "Caption2"),
|
||||
AccentH1: createTypographyVariant(Component, "AccentH1"),
|
||||
AccentH2: createTypographyVariant(Component, "AccentH2"),
|
||||
AccentSubttl: createTypographyVariant(Component, "AccentSubttl"),
|
||||
AccentSubttl2: createTypographyVariant(Component, "AccentSubttl2"),
|
||||
AccentCaption: createTypographyVariant(Component, "AccentCaption"),
|
||||
AccentCaption2: createTypographyVariant(Component, "AccentCaption2"),
|
||||
AccentRegularM: createTypographyVariant(Component, "AccentRegularM"),
|
||||
AccentRegularS: createTypographyVariant(Component, "AccentRegularS"),
|
||||
AccentLargeTtl: createTypographyVariant(Component, "AccentLargeTtl"),
|
||||
AppMediumBody: createTypographyVariant(Component, "AppMediumBody"),
|
||||
AppMediumSubtext: createTypographyVariant(Component, "AppMediumSubtext"),
|
||||
AppMediumSubtextUnderline: createTypographyVariant(Component, "AppMediumSubtextUnderline")
|
||||
});
|
||||
var TachTypography = {
|
||||
Text: createTypographyComponent(antd.Typography.Text),
|
||||
Paragraph: createTypographyComponent(antd.Typography.Paragraph),
|
||||
Link: createTypographyComponent(antd.Typography.Link),
|
||||
Title: createTypographyComponent(antd.Typography.Title)
|
||||
};
|
||||
|
||||
exports.TachTypography = TachTypography;
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
1
packages/tach-typography/dist/react/index.cjs.map
vendored
Normal file
1
packages/tach-typography/dist/react/index.cjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
121
packages/tach-typography/dist/react/index.d.cts
vendored
Normal file
121
packages/tach-typography/dist/react/index.d.cts
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
import React from 'react';
|
||||
import { LinkProps } from 'antd/lib/typography/Link';
|
||||
import { ParagraphProps } from 'antd/lib/typography/Paragraph';
|
||||
import { TextProps } from 'antd/lib/typography/Text';
|
||||
import { TitleProps } from 'antd/lib/typography/Title';
|
||||
import { c as TypographyColor, f as TypographyWeight } from '../types-CQyFuLqp.cjs';
|
||||
|
||||
interface AdditionalProps {
|
||||
color?: TypographyColor;
|
||||
weight?: TypographyWeight;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
className?: string | undefined;
|
||||
}
|
||||
declare const TachTypography: {
|
||||
Text: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
Paragraph: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
Link: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
Title: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
};
|
||||
|
||||
export { TachTypography };
|
||||
121
packages/tach-typography/dist/react/index.d.ts
vendored
Normal file
121
packages/tach-typography/dist/react/index.d.ts
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
import React from 'react';
|
||||
import { LinkProps } from 'antd/lib/typography/Link';
|
||||
import { ParagraphProps } from 'antd/lib/typography/Paragraph';
|
||||
import { TextProps } from 'antd/lib/typography/Text';
|
||||
import { TitleProps } from 'antd/lib/typography/Title';
|
||||
import { c as TypographyColor, f as TypographyWeight } from '../types-CQyFuLqp.js';
|
||||
|
||||
interface AdditionalProps {
|
||||
color?: TypographyColor;
|
||||
weight?: TypographyWeight;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
className?: string | undefined;
|
||||
}
|
||||
declare const TachTypography: {
|
||||
Text: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<TextProps & Pick<ParagraphProps, "ellipsis"> & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
Paragraph: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<ParagraphProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
Link: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<LinkProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
Title: {
|
||||
LargeTitle: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title1: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Title3: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Headline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Body: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Inputs: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Subheadline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
FootnoteUnderline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Footnote: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
Caption2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH1: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentH2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentSubttl2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentCaption2: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularM: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentRegularS: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AccentLargeTtl: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumBody: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtext: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
AppMediumSubtextUnderline: React.ForwardRefExoticComponent<TitleProps & AdditionalProps & React.RefAttributes<HTMLElement>>;
|
||||
};
|
||||
};
|
||||
|
||||
export { TachTypography };
|
||||
82
packages/tach-typography/dist/react/index.js
vendored
Normal file
82
packages/tach-typography/dist/react/index.js
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { Typography } from 'antd';
|
||||
import { jsx } from 'react/jsx-runtime';
|
||||
|
||||
// src/react/index.tsx
|
||||
|
||||
// src/core/classnames.ts
|
||||
var BASE_CLASS = "tach-typography";
|
||||
var join = (...parts) => parts.filter(Boolean).join(" ");
|
||||
var tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className
|
||||
} = {}) => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className
|
||||
);
|
||||
};
|
||||
var createTypographyVariant = (Component, variant) => {
|
||||
const Variant = React.forwardRef(
|
||||
({ color = "primary", weight = "normal", className, onClick, ...rest }, ref) => /* @__PURE__ */ jsx(
|
||||
Component,
|
||||
{
|
||||
ref,
|
||||
className: tachTypographyClassName({
|
||||
variant,
|
||||
color,
|
||||
weight,
|
||||
className,
|
||||
clickable: Boolean(onClick)
|
||||
}),
|
||||
onClick,
|
||||
...rest
|
||||
}
|
||||
)
|
||||
);
|
||||
Variant.displayName = String(variant);
|
||||
return Variant;
|
||||
};
|
||||
var createTypographyComponent = (Component) => ({
|
||||
LargeTitle: createTypographyVariant(Component, "LargeTitle"),
|
||||
Title1: createTypographyVariant(Component, "Title1"),
|
||||
Title2: createTypographyVariant(Component, "Title2"),
|
||||
Title3: createTypographyVariant(Component, "Title3"),
|
||||
Headline: createTypographyVariant(Component, "Headline"),
|
||||
Body: createTypographyVariant(Component, "Body"),
|
||||
Inputs: createTypographyVariant(Component, "Inputs"),
|
||||
Subheadline: createTypographyVariant(Component, "Subheadline"),
|
||||
FootnoteUnderline: createTypographyVariant(Component, "FootnoteUnderline"),
|
||||
Footnote: createTypographyVariant(Component, "Footnote"),
|
||||
Caption: createTypographyVariant(Component, "Caption"),
|
||||
Caption2: createTypographyVariant(Component, "Caption2"),
|
||||
AccentH1: createTypographyVariant(Component, "AccentH1"),
|
||||
AccentH2: createTypographyVariant(Component, "AccentH2"),
|
||||
AccentSubttl: createTypographyVariant(Component, "AccentSubttl"),
|
||||
AccentSubttl2: createTypographyVariant(Component, "AccentSubttl2"),
|
||||
AccentCaption: createTypographyVariant(Component, "AccentCaption"),
|
||||
AccentCaption2: createTypographyVariant(Component, "AccentCaption2"),
|
||||
AccentRegularM: createTypographyVariant(Component, "AccentRegularM"),
|
||||
AccentRegularS: createTypographyVariant(Component, "AccentRegularS"),
|
||||
AccentLargeTtl: createTypographyVariant(Component, "AccentLargeTtl"),
|
||||
AppMediumBody: createTypographyVariant(Component, "AppMediumBody"),
|
||||
AppMediumSubtext: createTypographyVariant(Component, "AppMediumSubtext"),
|
||||
AppMediumSubtextUnderline: createTypographyVariant(Component, "AppMediumSubtextUnderline")
|
||||
});
|
||||
var TachTypography = {
|
||||
Text: createTypographyComponent(Typography.Text),
|
||||
Paragraph: createTypographyComponent(Typography.Paragraph),
|
||||
Link: createTypographyComponent(Typography.Link),
|
||||
Title: createTypographyComponent(Typography.Title)
|
||||
};
|
||||
|
||||
export { TachTypography };
|
||||
//# sourceMappingURL=index.js.map
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/tach-typography/dist/react/index.js.map
vendored
Normal file
1
packages/tach-typography/dist/react/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
103
packages/tach-typography/dist/styles.css
vendored
Normal file
103
packages/tach-typography/dist/styles.css
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
.tach-typography {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-typography.tach-typography {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tach-typography--pointer,
|
||||
.ant-typography.tach-typography--pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tach-typography--bold,
|
||||
.ant-typography.tach-typography--bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.tach-typography--color-primary,
|
||||
.ant-typography.tach-typography--color-primary { color: var(--Text-Primary); }
|
||||
.tach-typography--color-secondary,
|
||||
.ant-typography.tach-typography--color-secondary { color: var(--Text-Secondary); }
|
||||
.tach-typography--color-tertiary,
|
||||
.ant-typography.tach-typography--color-tertiary { color: var(--Text-Tertiary); }
|
||||
.tach-typography--color-quaternary,
|
||||
.ant-typography.tach-typography--color-quaternary { color: var(--Text-Quaternary); }
|
||||
.tach-typography--color-link,
|
||||
.ant-typography.tach-typography--color-link { color: var(--System-HashtagsInPost); }
|
||||
.tach-typography--color-white,
|
||||
.ant-typography.tach-typography--color-white { color: var(--Default-White); }
|
||||
.tach-typography--color-dark,
|
||||
.ant-typography.tach-typography--color-dark { color: var(--Default-Dark); }
|
||||
.tach-typography--color-alert,
|
||||
.ant-typography.tach-typography--color-alert { color: var(--System-Alert); }
|
||||
.tach-typography--color-malahit,
|
||||
.ant-typography.tach-typography--color-malahit { color: var(--Accent-Malahit); }
|
||||
.tach-typography--color-attantion,
|
||||
.ant-typography.tach-typography--color-attantion { color: var(--System-Attantion); }
|
||||
|
||||
.tach-typography--LargeTitle,
|
||||
.ant-typography.tach-typography--LargeTitle { font-family: Inter, sans-serif; font-size: 38px; font-weight: 500; line-height: 46px; }
|
||||
.tach-typography--Title1,
|
||||
.ant-typography.tach-typography--Title1 { font-family: Inter, sans-serif; font-size: 28px; font-weight: 500; line-height: 34px; }
|
||||
.tach-typography--Title2,
|
||||
.ant-typography.tach-typography--Title2 { font-family: Inter, sans-serif; font-size: 22px; font-weight: 500; line-height: 28px; }
|
||||
.tach-typography--Title3,
|
||||
.ant-typography.tach-typography--Title3 { font-family: Inter, sans-serif; font-size: 20px; font-weight: 500; line-height: 26px; }
|
||||
.tach-typography--Headline,
|
||||
.ant-typography.tach-typography--Headline { font-family: Inter, sans-serif; font-size: 16px; font-weight: 500; line-height: 24px; }
|
||||
.tach-typography--Body,
|
||||
.tach-typography--AppMediumBody,
|
||||
.ant-typography.tach-typography--Body,
|
||||
.ant-typography.tach-typography--AppMediumBody { font-family: Inter, sans-serif; font-size: 14px; font-weight: 500; line-height: 20px; }
|
||||
.tach-typography--Inputs,
|
||||
.ant-typography.tach-typography--Inputs { font-family: Inter, sans-serif; font-size: 14px; font-weight: 500; line-height: 24px; }
|
||||
.tach-typography--Subheadline,
|
||||
.ant-typography.tach-typography--Subheadline { font-family: Inter, sans-serif; font-size: 14px; font-weight: 500; line-height: 18px; }
|
||||
.tach-typography--FootnoteUnderline,
|
||||
.ant-typography.tach-typography--FootnoteUnderline { font-family: Inter, sans-serif; font-size: 13px; font-weight: 500; line-height: 18px; text-decoration: underline; }
|
||||
.tach-typography--Footnote,
|
||||
.ant-typography.tach-typography--Footnote { font-family: Inter, sans-serif; font-size: 13px; font-weight: 500; line-height: 18px; }
|
||||
.tach-typography--Caption,
|
||||
.ant-typography.tach-typography--Caption { font-family: Inter, sans-serif; font-size: 10px; font-weight: 500; line-height: 12px; text-transform: uppercase; }
|
||||
.tach-typography--Caption2,
|
||||
.ant-typography.tach-typography--Caption2 { font-family: Inter, sans-serif; font-size: 8px; font-weight: 500; line-height: 10px; text-transform: uppercase; }
|
||||
|
||||
.tach-typography--AccentH1,
|
||||
.ant-typography.tach-typography--AccentH1 { font-family: Unbounded, sans-serif; font-size: 20px; font-weight: 700; line-height: 30px; }
|
||||
.tach-typography--AccentH2,
|
||||
.ant-typography.tach-typography--AccentH2 { font-family: Unbounded, sans-serif; font-size: 16px; font-weight: 700; line-height: 24px; }
|
||||
.tach-typography--AccentSubttl,
|
||||
.ant-typography.tach-typography--AccentSubttl { font-family: Unbounded, sans-serif; font-size: 14px; font-weight: 700; line-height: 22px; }
|
||||
.tach-typography--AccentSubttl2,
|
||||
.ant-typography.tach-typography--AccentSubttl2 { font-family: Unbounded, sans-serif; font-size: 12px; font-weight: 700; line-height: 20px; }
|
||||
.tach-typography--AccentCaption,
|
||||
.ant-typography.tach-typography--AccentCaption { font-family: Unbounded, sans-serif; font-size: 9px; font-weight: 700; line-height: 12px; text-transform: uppercase; }
|
||||
.tach-typography--AccentCaption2,
|
||||
.ant-typography.tach-typography--AccentCaption2 { font-family: Unbounded, sans-serif; font-size: 7px; font-weight: 700; line-height: 10px; text-transform: uppercase; }
|
||||
.tach-typography--AccentRegularM,
|
||||
.ant-typography.tach-typography--AccentRegularM { font-family: Unbounded, sans-serif; font-size: 14px; font-weight: 400; line-height: 22px; }
|
||||
.tach-typography--AccentRegularS,
|
||||
.ant-typography.tach-typography--AccentRegularS { font-family: Unbounded, sans-serif; font-size: 12px; font-weight: 400; line-height: 20px; }
|
||||
.tach-typography--AccentLargeTtl,
|
||||
.ant-typography.tach-typography--AccentLargeTtl { font-family: Unbounded, sans-serif; font-size: 38px; font-weight: 700; line-height: 52px; }
|
||||
.tach-typography--AppMediumSubtext,
|
||||
.ant-typography.tach-typography--AppMediumSubtext { text-align: center; font-family: Inter, sans-serif; font-size: 11px; font-weight: 400; line-height: 17px; }
|
||||
.tach-typography--AppMediumSubtextUnderline,
|
||||
.ant-typography.tach-typography--AppMediumSubtextUnderline { font-family: Inter, sans-serif; font-size: 11px; font-weight: 400; line-height: 17px; text-decoration: underline; }
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.tach-typography--AccentLargeTtl,
|
||||
.ant-typography.tach-typography--AccentLargeTtl {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.tach-typography--AccentRegularM,
|
||||
.ant-typography.tach-typography--AccentRegularM {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
20
packages/tach-typography/dist/types-CQyFuLqp.d.cts
vendored
Normal file
20
packages/tach-typography/dist/types-CQyFuLqp.d.cts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
declare const TYPOGRAPHY_VARIANTS: readonly ["LargeTitle", "Title1", "Title2", "Title3", "Headline", "Body", "Inputs", "Subheadline", "FootnoteUnderline", "Footnote", "Caption", "Caption2", "AccentH1", "AccentH2", "AccentSubttl", "AccentSubttl2", "AccentCaption", "AccentCaption2", "AccentRegularM", "AccentRegularS", "AccentLargeTtl", "AppMediumBody", "AppMediumSubtext", "AppMediumSubtextUnderline"];
|
||||
declare const TYPOGRAPHY_COLORS: readonly ["primary", "secondary", "tertiary", "quaternary", "link", "white", "dark", "alert", "malahit", "attantion"];
|
||||
type TypographyVariant = (typeof TYPOGRAPHY_VARIANTS)[number];
|
||||
type TypographyColor = (typeof TYPOGRAPHY_COLORS)[number];
|
||||
type TypographyWeight = "normal" | "bold";
|
||||
interface TypographyClassOptions {
|
||||
variant?: TypographyVariant;
|
||||
color?: TypographyColor;
|
||||
weight?: TypographyWeight;
|
||||
clickable?: boolean;
|
||||
className?: string | undefined;
|
||||
}
|
||||
type EllipsisOptions = boolean | {
|
||||
rows?: number;
|
||||
};
|
||||
interface TypographyRenderOptions extends TypographyClassOptions {
|
||||
ellipsis?: EllipsisOptions;
|
||||
}
|
||||
|
||||
export { type EllipsisOptions as E, type TypographyClassOptions as T, TYPOGRAPHY_COLORS as a, TYPOGRAPHY_VARIANTS as b, type TypographyColor as c, type TypographyRenderOptions as d, type TypographyVariant as e, type TypographyWeight as f };
|
||||
20
packages/tach-typography/dist/types-CQyFuLqp.d.ts
vendored
Normal file
20
packages/tach-typography/dist/types-CQyFuLqp.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
declare const TYPOGRAPHY_VARIANTS: readonly ["LargeTitle", "Title1", "Title2", "Title3", "Headline", "Body", "Inputs", "Subheadline", "FootnoteUnderline", "Footnote", "Caption", "Caption2", "AccentH1", "AccentH2", "AccentSubttl", "AccentSubttl2", "AccentCaption", "AccentCaption2", "AccentRegularM", "AccentRegularS", "AccentLargeTtl", "AppMediumBody", "AppMediumSubtext", "AppMediumSubtextUnderline"];
|
||||
declare const TYPOGRAPHY_COLORS: readonly ["primary", "secondary", "tertiary", "quaternary", "link", "white", "dark", "alert", "malahit", "attantion"];
|
||||
type TypographyVariant = (typeof TYPOGRAPHY_VARIANTS)[number];
|
||||
type TypographyColor = (typeof TYPOGRAPHY_COLORS)[number];
|
||||
type TypographyWeight = "normal" | "bold";
|
||||
interface TypographyClassOptions {
|
||||
variant?: TypographyVariant;
|
||||
color?: TypographyColor;
|
||||
weight?: TypographyWeight;
|
||||
clickable?: boolean;
|
||||
className?: string | undefined;
|
||||
}
|
||||
type EllipsisOptions = boolean | {
|
||||
rows?: number;
|
||||
};
|
||||
interface TypographyRenderOptions extends TypographyClassOptions {
|
||||
ellipsis?: EllipsisOptions;
|
||||
}
|
||||
|
||||
export { type EllipsisOptions as E, type TypographyClassOptions as T, TYPOGRAPHY_COLORS as a, TYPOGRAPHY_VARIANTS as b, type TypographyColor as c, type TypographyRenderOptions as d, type TypographyVariant as e, type TypographyWeight as f };
|
||||
136
packages/tach-typography/package.json
Normal file
136
packages/tach-typography/package.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"name": "@hublib-web/tach-typography",
|
||||
"version": "0.1.0",
|
||||
"description": "Cross-framework typography package for React and Angular",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./dist/core/index.cjs",
|
||||
"module": "./dist/core/index.js",
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"sideEffects": [
|
||||
"./dist/styles.css"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"styles"
|
||||
],
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"react": [
|
||||
"dist/react/index.d.ts"
|
||||
],
|
||||
"angular": [
|
||||
"dist/angular/index.d.ts"
|
||||
],
|
||||
"core": [
|
||||
"dist/core/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"import": "./dist/core/index.js",
|
||||
"require": "./dist/core/index.cjs"
|
||||
},
|
||||
"./core": {
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"import": "./dist/core/index.js",
|
||||
"require": "./dist/core/index.cjs"
|
||||
},
|
||||
"./react": {
|
||||
"types": "./dist/react/index.d.ts",
|
||||
"import": "./dist/react/index.js",
|
||||
"require": "./dist/react/index.cjs"
|
||||
},
|
||||
"./angular": {
|
||||
"types": "./dist/angular/index.d.ts",
|
||||
"import": "./dist/angular/index.js",
|
||||
"require": "./dist/angular/index.cjs"
|
||||
},
|
||||
"./styles.css": "./dist/styles.css",
|
||||
"./styles/typography-vars.scss": "./styles/typography-vars.scss"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn clean && tsup && node ./scripts/copy-styles.mjs",
|
||||
"clean": "rm -rf dist",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"test": "vitest run",
|
||||
"lint": "eslint src --ext .ts,.tsx",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "storybook build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": ">=17.0.0",
|
||||
"@angular/common": ">=17.0.0",
|
||||
"@angular/core": ">=17.0.0",
|
||||
"@angular/forms": ">=17.0.0",
|
||||
"@angular/platform-browser": ">=17.0.0",
|
||||
"@angular/router": ">=17.0.0",
|
||||
"antd": ">=5.0.0",
|
||||
"ng-zorro-antd": ">=17.0.0",
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/common": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/forms": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/platform-browser": {
|
||||
"optional": true
|
||||
},
|
||||
"@angular/router": {
|
||||
"optional": true
|
||||
},
|
||||
"antd": {
|
||||
"optional": true
|
||||
},
|
||||
"ng-zorro-antd": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"rxjs": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/animations": "^20.3.17",
|
||||
"@angular/common": "^20.3.17",
|
||||
"@angular/core": "^20.3.17",
|
||||
"@angular/forms": "^20.3.17",
|
||||
"@angular/platform-browser": "^20.3.17",
|
||||
"@angular/router": "^20.3.17",
|
||||
"@storybook/addon-a11y": "8.6.14",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/addon-interactions": "8.6.14",
|
||||
"@storybook/blocks": "8.6.14",
|
||||
"@storybook/react-vite": "8.6.14",
|
||||
"@storybook/test": "8.6.14",
|
||||
"@types/react": "^19.2.2",
|
||||
"antd": "^5.29.3",
|
||||
"ng-zorro-antd": "^20.4.4",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"storybook": "8.6.14",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "6.4.1"
|
||||
}
|
||||
}
|
||||
12
packages/tach-typography/scripts/copy-styles.mjs
Normal file
12
packages/tach-typography/scripts/copy-styles.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { cpSync, existsSync, mkdirSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
const source = resolve("src/styles/tach-typography.css");
|
||||
const destination = resolve("dist/styles.css");
|
||||
|
||||
if (!existsSync(source)) {
|
||||
throw new Error(`Styles file not found: ${source}`);
|
||||
}
|
||||
|
||||
mkdirSync(dirname(destination), { recursive: true });
|
||||
cpSync(source, destination);
|
||||
140
packages/tach-typography/src/angular/index.ts
Normal file
140
packages/tach-typography/src/angular/index.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { Directive, ElementRef, Input, OnChanges, Renderer2, SimpleChanges } from "@angular/core";
|
||||
import { NzTypographyModule } from "ng-zorro-antd/typography";
|
||||
|
||||
import {
|
||||
tachTypographyClassList,
|
||||
tachTypographyClassName,
|
||||
tachTypographyEllipsisStyle,
|
||||
type EllipsisOptions,
|
||||
type TypographyClassOptions,
|
||||
type TypographyColor,
|
||||
type TypographyRenderOptions,
|
||||
type TypographyVariant,
|
||||
type TypographyWeight,
|
||||
} from "../core";
|
||||
|
||||
export type AngularTypographyClassInput = TypographyClassOptions;
|
||||
|
||||
export interface AngularTypographyRenderOptions extends TypographyRenderOptions {
|
||||
preserveStyle?: Record<string, string | number>;
|
||||
}
|
||||
|
||||
const camelToKebab = (value: string): string =>
|
||||
value.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
|
||||
|
||||
const toCssProperty = (styleKey: string): string => {
|
||||
if (styleKey.startsWith("Webkit")) {
|
||||
return `-webkit-${camelToKebab(styleKey.slice(6))}`;
|
||||
}
|
||||
|
||||
return camelToKebab(styleKey);
|
||||
};
|
||||
|
||||
export const tachAngularTypographyClassName = (
|
||||
options: AngularTypographyClassInput = {},
|
||||
): string => {
|
||||
return tachTypographyClassName(options);
|
||||
};
|
||||
|
||||
export const tachAngularTypographyClassList = (
|
||||
options: AngularTypographyClassInput = {},
|
||||
): string[] => {
|
||||
return tachTypographyClassList(options);
|
||||
};
|
||||
|
||||
export const tachAngularTypographyStyles = (
|
||||
ellipsis?: EllipsisOptions,
|
||||
preserveStyle: Record<string, string | number> = {},
|
||||
): Record<string, string | number> => {
|
||||
const ellipsisStyle = tachTypographyEllipsisStyle(ellipsis);
|
||||
|
||||
if (!ellipsisStyle) {
|
||||
return preserveStyle;
|
||||
}
|
||||
|
||||
return {
|
||||
...ellipsisStyle,
|
||||
...preserveStyle,
|
||||
};
|
||||
};
|
||||
|
||||
@Directive({
|
||||
selector: "[tachTypography]",
|
||||
standalone: true,
|
||||
})
|
||||
export class TachTypographyDirective implements OnChanges {
|
||||
@Input() tachTypography: TypographyVariant | "" | undefined;
|
||||
@Input() tachTypographyVariant: TypographyVariant = "Body";
|
||||
@Input() tachTypographyColor: TypographyColor = "primary";
|
||||
@Input() tachTypographyWeight: TypographyWeight = "normal";
|
||||
@Input() tachTypographyClickable = false;
|
||||
@Input() tachTypographyClassName: string | undefined;
|
||||
@Input() tachTypographyEllipsis: EllipsisOptions | undefined;
|
||||
|
||||
private readonly appliedClasses = new Set<string>();
|
||||
private readonly appliedStyleProperties = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private readonly elementRef: ElementRef<HTMLElement>,
|
||||
private readonly renderer: Renderer2,
|
||||
) {}
|
||||
|
||||
ngOnChanges(_changes: SimpleChanges): void {
|
||||
this.syncClasses();
|
||||
this.syncEllipsisStyles();
|
||||
}
|
||||
|
||||
private syncClasses(): void {
|
||||
const nextClassList = tachTypographyClassList({
|
||||
variant: this.tachTypography || this.tachTypographyVariant,
|
||||
color: this.tachTypographyColor,
|
||||
weight: this.tachTypographyWeight,
|
||||
clickable: this.tachTypographyClickable,
|
||||
className: this.tachTypographyClassName,
|
||||
});
|
||||
|
||||
const nextSet = new Set(nextClassList);
|
||||
|
||||
for (const className of this.appliedClasses) {
|
||||
if (!nextSet.has(className)) {
|
||||
this.renderer.removeClass(this.elementRef.nativeElement, className);
|
||||
}
|
||||
}
|
||||
|
||||
for (const className of nextSet) {
|
||||
this.renderer.addClass(this.elementRef.nativeElement, className);
|
||||
}
|
||||
|
||||
this.appliedClasses.clear();
|
||||
for (const className of nextSet) {
|
||||
this.appliedClasses.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
private syncEllipsisStyles(): void {
|
||||
const nextStyles = tachTypographyEllipsisStyle(this.tachTypographyEllipsis) || {};
|
||||
const nextStyleKeys = new Set(Object.keys(nextStyles));
|
||||
|
||||
for (const styleKey of this.appliedStyleProperties) {
|
||||
if (!nextStyleKeys.has(styleKey)) {
|
||||
this.renderer.removeStyle(this.elementRef.nativeElement, toCssProperty(styleKey));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [styleKey, styleValue] of Object.entries(nextStyles)) {
|
||||
this.renderer.setStyle(this.elementRef.nativeElement, toCssProperty(styleKey), styleValue);
|
||||
}
|
||||
|
||||
this.appliedStyleProperties.clear();
|
||||
for (const styleKey of nextStyleKeys) {
|
||||
this.appliedStyleProperties.add(styleKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [NzTypographyModule, TachTypographyDirective],
|
||||
exports: [NzTypographyModule, TachTypographyDirective],
|
||||
})
|
||||
export class TachTypographyNzModule {}
|
||||
29
packages/tach-typography/src/core/classnames.ts
Normal file
29
packages/tach-typography/src/core/classnames.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { TypographyClassOptions } from "./types";
|
||||
|
||||
const BASE_CLASS = "tach-typography";
|
||||
|
||||
const join = (...parts: Array<string | undefined | null | false>): string =>
|
||||
parts.filter(Boolean).join(" ");
|
||||
|
||||
export const tachTypographyClassName = ({
|
||||
variant = "Body",
|
||||
color = "primary",
|
||||
weight = "normal",
|
||||
clickable = false,
|
||||
className,
|
||||
}: TypographyClassOptions = {}): string => {
|
||||
return join(
|
||||
BASE_CLASS,
|
||||
`${BASE_CLASS}--${variant}`,
|
||||
`${BASE_CLASS}--color-${color}`,
|
||||
weight === "bold" && `${BASE_CLASS}--bold`,
|
||||
clickable && `${BASE_CLASS}--pointer`,
|
||||
className,
|
||||
);
|
||||
};
|
||||
|
||||
export const tachTypographyClassList = (options: TypographyClassOptions = {}): string[] => {
|
||||
return tachTypographyClassName(options)
|
||||
.split(" ")
|
||||
.filter(Boolean);
|
||||
};
|
||||
21
packages/tach-typography/src/core/ellipsis.ts
Normal file
21
packages/tach-typography/src/core/ellipsis.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { EllipsisOptions } from "./types";
|
||||
|
||||
type StyleRecord = Record<string, string | number>;
|
||||
|
||||
export const tachTypographyEllipsisStyle = (
|
||||
ellipsis?: EllipsisOptions,
|
||||
): StyleRecord | undefined => {
|
||||
if (!ellipsis) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const rows = typeof ellipsis === "object" ? ellipsis.rows ?? 1 : 1;
|
||||
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: rows,
|
||||
};
|
||||
};
|
||||
56
packages/tach-typography/src/core/index.test.ts
Normal file
56
packages/tach-typography/src/core/index.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
tachTypographyClassList,
|
||||
tachTypographyClassName,
|
||||
tachTypographyEllipsisStyle,
|
||||
} from "./index";
|
||||
|
||||
describe("tachTypographyClassName", () => {
|
||||
it("builds className string with defaults", () => {
|
||||
expect(tachTypographyClassName()).toBe(
|
||||
"tach-typography tach-typography--Body tach-typography--color-primary",
|
||||
);
|
||||
});
|
||||
|
||||
it("adds optional states", () => {
|
||||
expect(
|
||||
tachTypographyClassName({
|
||||
variant: "AccentH1",
|
||||
color: "link",
|
||||
weight: "bold",
|
||||
clickable: true,
|
||||
className: "custom",
|
||||
}),
|
||||
).toBe(
|
||||
"tach-typography tach-typography--AccentH1 tach-typography--color-link tach-typography--bold tach-typography--pointer custom",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns list helper", () => {
|
||||
expect(tachTypographyClassList({ variant: "Title1" })).toEqual([
|
||||
"tach-typography",
|
||||
"tach-typography--Title1",
|
||||
"tach-typography--color-primary",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tachTypographyEllipsisStyle", () => {
|
||||
it("returns undefined without ellipsis", () => {
|
||||
expect(tachTypographyEllipsisStyle()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns one-line style for true", () => {
|
||||
expect(tachTypographyEllipsisStyle(true)).toMatchObject({
|
||||
WebkitLineClamp: 1,
|
||||
overflow: "hidden",
|
||||
});
|
||||
});
|
||||
|
||||
it("respects rows value", () => {
|
||||
expect(tachTypographyEllipsisStyle({ rows: 3 })).toMatchObject({
|
||||
WebkitLineClamp: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
3
packages/tach-typography/src/core/index.ts
Normal file
3
packages/tach-typography/src/core/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./types";
|
||||
export * from "./classnames";
|
||||
export * from "./ellipsis";
|
||||
61
packages/tach-typography/src/core/types.ts
Normal file
61
packages/tach-typography/src/core/types.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export const TYPOGRAPHY_VARIANTS = [
|
||||
"LargeTitle",
|
||||
"Title1",
|
||||
"Title2",
|
||||
"Title3",
|
||||
"Headline",
|
||||
"Body",
|
||||
"Inputs",
|
||||
"Subheadline",
|
||||
"FootnoteUnderline",
|
||||
"Footnote",
|
||||
"Caption",
|
||||
"Caption2",
|
||||
"AccentH1",
|
||||
"AccentH2",
|
||||
"AccentSubttl",
|
||||
"AccentSubttl2",
|
||||
"AccentCaption",
|
||||
"AccentCaption2",
|
||||
"AccentRegularM",
|
||||
"AccentRegularS",
|
||||
"AccentLargeTtl",
|
||||
"AppMediumBody",
|
||||
"AppMediumSubtext",
|
||||
"AppMediumSubtextUnderline",
|
||||
] as const;
|
||||
|
||||
export const TYPOGRAPHY_COLORS = [
|
||||
"primary",
|
||||
"secondary",
|
||||
"tertiary",
|
||||
"quaternary",
|
||||
"link",
|
||||
"white",
|
||||
"dark",
|
||||
"alert",
|
||||
"malahit",
|
||||
"attantion",
|
||||
] as const;
|
||||
|
||||
export type TypographyVariant = (typeof TYPOGRAPHY_VARIANTS)[number];
|
||||
export type TypographyColor = (typeof TYPOGRAPHY_COLORS)[number];
|
||||
export type TypographyWeight = "normal" | "bold";
|
||||
|
||||
export interface TypographyClassOptions {
|
||||
variant?: TypographyVariant;
|
||||
color?: TypographyColor;
|
||||
weight?: TypographyWeight;
|
||||
clickable?: boolean;
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
export type EllipsisOptions =
|
||||
| boolean
|
||||
| {
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
export interface TypographyRenderOptions extends TypographyClassOptions {
|
||||
ellipsis?: EllipsisOptions;
|
||||
}
|
||||
82
packages/tach-typography/src/react/index.tsx
Normal file
82
packages/tach-typography/src/react/index.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from "react";
|
||||
|
||||
import { Typography } from "antd";
|
||||
import type { LinkProps } from "antd/lib/typography/Link";
|
||||
import type { ParagraphProps } from "antd/lib/typography/Paragraph";
|
||||
import type { TextProps } from "antd/lib/typography/Text";
|
||||
import type { TitleProps } from "antd/lib/typography/Title";
|
||||
|
||||
import {
|
||||
tachTypographyClassName,
|
||||
type TypographyColor,
|
||||
type TypographyVariant,
|
||||
type TypographyWeight,
|
||||
} from "../core";
|
||||
|
||||
interface AdditionalProps {
|
||||
color?: TypographyColor;
|
||||
weight?: TypographyWeight;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
const createTypographyVariant = <P extends object>(
|
||||
Component: React.ComponentType<P>,
|
||||
variant: TypographyVariant,
|
||||
) => {
|
||||
const Variant = React.forwardRef<HTMLElement, P & AdditionalProps>(
|
||||
({ color = "primary", weight = "normal", className, onClick, ...rest }, ref) => (
|
||||
<Component
|
||||
ref={ref as never}
|
||||
className={tachTypographyClassName({
|
||||
variant,
|
||||
color,
|
||||
weight,
|
||||
className,
|
||||
clickable: Boolean(onClick),
|
||||
})}
|
||||
onClick={onClick}
|
||||
{...(rest as P)}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
Variant.displayName = String(variant);
|
||||
|
||||
return Variant;
|
||||
};
|
||||
|
||||
const createTypographyComponent = <P extends object>(Component: React.ComponentType<P>) => ({
|
||||
LargeTitle: createTypographyVariant(Component, "LargeTitle"),
|
||||
Title1: createTypographyVariant(Component, "Title1"),
|
||||
Title2: createTypographyVariant(Component, "Title2"),
|
||||
Title3: createTypographyVariant(Component, "Title3"),
|
||||
Headline: createTypographyVariant(Component, "Headline"),
|
||||
Body: createTypographyVariant(Component, "Body"),
|
||||
Inputs: createTypographyVariant(Component, "Inputs"),
|
||||
Subheadline: createTypographyVariant(Component, "Subheadline"),
|
||||
FootnoteUnderline: createTypographyVariant(Component, "FootnoteUnderline"),
|
||||
Footnote: createTypographyVariant(Component, "Footnote"),
|
||||
Caption: createTypographyVariant(Component, "Caption"),
|
||||
Caption2: createTypographyVariant(Component, "Caption2"),
|
||||
|
||||
AccentH1: createTypographyVariant(Component, "AccentH1"),
|
||||
AccentH2: createTypographyVariant(Component, "AccentH2"),
|
||||
AccentSubttl: createTypographyVariant(Component, "AccentSubttl"),
|
||||
AccentSubttl2: createTypographyVariant(Component, "AccentSubttl2"),
|
||||
AccentCaption: createTypographyVariant(Component, "AccentCaption"),
|
||||
AccentCaption2: createTypographyVariant(Component, "AccentCaption2"),
|
||||
AccentRegularM: createTypographyVariant(Component, "AccentRegularM"),
|
||||
AccentRegularS: createTypographyVariant(Component, "AccentRegularS"),
|
||||
AccentLargeTtl: createTypographyVariant(Component, "AccentLargeTtl"),
|
||||
AppMediumBody: createTypographyVariant(Component, "AppMediumBody"),
|
||||
AppMediumSubtext: createTypographyVariant(Component, "AppMediumSubtext"),
|
||||
AppMediumSubtextUnderline: createTypographyVariant(Component, "AppMediumSubtextUnderline"),
|
||||
});
|
||||
|
||||
export const TachTypography = {
|
||||
Text: createTypographyComponent<TextProps & Pick<ParagraphProps, "ellipsis">>(Typography.Text),
|
||||
Paragraph: createTypographyComponent<ParagraphProps>(Typography.Paragraph),
|
||||
Link: createTypographyComponent<LinkProps>(Typography.Link),
|
||||
Title: createTypographyComponent<TitleProps>(Typography.Title),
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
import React from "react";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { expect, fn, userEvent, within } from "@storybook/test";
|
||||
|
||||
import {
|
||||
TYPOGRAPHY_COLORS,
|
||||
TYPOGRAPHY_VARIANTS,
|
||||
type TypographyColor,
|
||||
type TypographyVariant,
|
||||
type TypographyWeight,
|
||||
} from "../core";
|
||||
import { TachTypography } from "../react";
|
||||
|
||||
type TypographyNamespace = keyof typeof TachTypography;
|
||||
|
||||
type VariantComponentProps = {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
color?: TypographyColor;
|
||||
ellipsis?: boolean | { rows?: number };
|
||||
href?: string;
|
||||
level?: 1 | 2 | 3 | 4 | 5;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
weight?: TypographyWeight;
|
||||
};
|
||||
|
||||
type VariantComponent = React.ComponentType<VariantComponentProps>;
|
||||
|
||||
const getVariantComponent = (
|
||||
namespace: TypographyNamespace,
|
||||
variant: TypographyVariant,
|
||||
): VariantComponent => {
|
||||
return (TachTypography[namespace] as unknown as Record<TypographyVariant, VariantComponent>)[variant];
|
||||
};
|
||||
|
||||
interface PlaygroundArgs {
|
||||
namespace: TypographyNamespace;
|
||||
variant: TypographyVariant;
|
||||
color: TypographyColor;
|
||||
weight: TypographyWeight;
|
||||
children: string;
|
||||
clickable: boolean;
|
||||
ellipsisRows: number;
|
||||
href: string;
|
||||
titleLevel: 1 | 2 | 3 | 4 | 5;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
}
|
||||
|
||||
const renderTypography = (args: PlaygroundArgs) => {
|
||||
const Component = getVariantComponent(args.namespace, args.variant);
|
||||
|
||||
const componentProps: VariantComponentProps = {
|
||||
color: args.color,
|
||||
weight: args.weight,
|
||||
};
|
||||
|
||||
if (args.clickable && args.onClick) {
|
||||
componentProps.onClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
args.onClick?.(event);
|
||||
};
|
||||
}
|
||||
|
||||
if (args.namespace === "Text" || args.namespace === "Paragraph") {
|
||||
componentProps.ellipsis = args.ellipsisRows > 0 ? { rows: args.ellipsisRows } : false;
|
||||
}
|
||||
|
||||
if (args.namespace === "Link") {
|
||||
componentProps.href = args.href;
|
||||
}
|
||||
|
||||
if (args.namespace === "Title") {
|
||||
componentProps.level = args.titleLevel;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tach-story-surface">
|
||||
<Component {...componentProps}>{args.children}</Component>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<PlaygroundArgs> = {
|
||||
title: "TachTypography/Playground",
|
||||
tags: ["autodocs"],
|
||||
render: renderTypography,
|
||||
args: {
|
||||
namespace: "Text",
|
||||
variant: "Body",
|
||||
color: "primary",
|
||||
weight: "normal",
|
||||
children: "TachTypography playground text",
|
||||
clickable: false,
|
||||
ellipsisRows: 0,
|
||||
href: "https://example.com",
|
||||
titleLevel: 3,
|
||||
},
|
||||
argTypes: {
|
||||
namespace: {
|
||||
control: "select",
|
||||
options: ["Text", "Paragraph", "Link", "Title"],
|
||||
},
|
||||
variant: {
|
||||
control: "select",
|
||||
options: TYPOGRAPHY_VARIANTS,
|
||||
},
|
||||
color: {
|
||||
control: "select",
|
||||
options: TYPOGRAPHY_COLORS,
|
||||
},
|
||||
weight: {
|
||||
control: "inline-radio",
|
||||
options: ["normal", "bold"],
|
||||
},
|
||||
children: {
|
||||
control: "text",
|
||||
},
|
||||
clickable: {
|
||||
control: "boolean",
|
||||
},
|
||||
ellipsisRows: {
|
||||
control: {
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 5,
|
||||
},
|
||||
description: "Works for Text and Paragraph namespaces",
|
||||
},
|
||||
href: {
|
||||
control: "text",
|
||||
description: "Works for Link namespace",
|
||||
},
|
||||
titleLevel: {
|
||||
control: "inline-radio",
|
||||
options: [1, 2, 3, 4, 5],
|
||||
description: "Works for Title namespace",
|
||||
},
|
||||
onClick: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Interactive playground for all TachTypography namespaces with full token and prop controls.",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Interactive: Story = {};
|
||||
|
||||
export const WithEllipsis: Story = {
|
||||
args: {
|
||||
namespace: "Paragraph",
|
||||
variant: "Body",
|
||||
ellipsisRows: 2,
|
||||
children:
|
||||
"This paragraph demonstrates multi-line truncation in Storybook. Increase or decrease ellipsisRows to validate visual behavior and clipping boundaries.",
|
||||
},
|
||||
};
|
||||
|
||||
export const ClickInteraction: Story = {
|
||||
args: {
|
||||
namespace: "Text",
|
||||
variant: "Subheadline",
|
||||
clickable: true,
|
||||
children: "Click this text to run interaction assertion",
|
||||
onClick: fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByText(args.children));
|
||||
await expect(args.onClick as ReturnType<typeof fn>).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import React from "react";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "TachTypography/Reference",
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const PropsMatrix: Story = {
|
||||
render: () => (
|
||||
<div className="tach-story-surface tach-story-stack">
|
||||
<table className="tach-story-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Prop</th>
|
||||
<th>Type</th>
|
||||
<th>React</th>
|
||||
<th>Angular</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>TypographyVariant</td>
|
||||
<td>TachTypography.[Text|Paragraph|Link|Title].Variant</td>
|
||||
<td>tachTypography / tachTypographyVariant</td>
|
||||
<td>Main typography token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>color</td>
|
||||
<td>TypographyColor</td>
|
||||
<td>color</td>
|
||||
<td>tachTypographyColor</td>
|
||||
<td>Maps to CSS variables</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>weight</td>
|
||||
<td>"normal" | "bold"</td>
|
||||
<td>weight</td>
|
||||
<td>tachTypographyWeight</td>
|
||||
<td>Bold class modifier</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ellipsis</td>
|
||||
<td><code>{"boolean | { rows?: number }"}</code></td>
|
||||
<td>ellipsis (Text/Paragraph)</td>
|
||||
<td>tachTypographyEllipsis</td>
|
||||
<td>Applies line clamp styles</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clickable</td>
|
||||
<td>boolean</td>
|
||||
<td>onClick adds pointer class</td>
|
||||
<td>tachTypographyClickable</td>
|
||||
<td>Visual affordance + cursor</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>className</td>
|
||||
<td>tachTypographyClassName</td>
|
||||
<td>Merges with token classes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const AngularAdapter: Story = {
|
||||
render: () => (
|
||||
<div className="tach-story-surface tach-story-stack">
|
||||
<h4>Angular adapter usage</h4>
|
||||
<pre>
|
||||
<code>
|
||||
{`import { TachTypographyDirective, TachTypographyNzModule } from "@hublib-web/tach-typography/angular";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [TachTypographyNzModule, TachTypographyDirective],
|
||||
template: \
|
||||
\`<span
|
||||
nz-typography
|
||||
tachTypography
|
||||
tachTypography="Body"
|
||||
tachTypographyColor="link"
|
||||
tachTypographyWeight="bold"
|
||||
[tachTypographyEllipsis]="{ rows: 2 }"
|
||||
>
|
||||
Typography for Angular + NG-ZORRO
|
||||
</span>\`,
|
||||
})
|
||||
export class ExampleComponent {}
|
||||
`}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,189 @@
|
||||
import React from "react";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import {
|
||||
TYPOGRAPHY_COLORS,
|
||||
TYPOGRAPHY_VARIANTS,
|
||||
type TypographyColor,
|
||||
type TypographyVariant,
|
||||
type TypographyWeight,
|
||||
} from "../core";
|
||||
import { TachTypography } from "../react";
|
||||
|
||||
type TypographyNamespace = keyof typeof TachTypography;
|
||||
|
||||
type VariantComponentProps = {
|
||||
children?: React.ReactNode;
|
||||
color?: TypographyColor;
|
||||
ellipsis?: boolean | { rows?: number };
|
||||
href?: string;
|
||||
level?: 1 | 2 | 3 | 4 | 5;
|
||||
weight?: TypographyWeight;
|
||||
};
|
||||
|
||||
type VariantComponent = React.ComponentType<VariantComponentProps>;
|
||||
|
||||
const getVariantComponent = (
|
||||
namespace: TypographyNamespace,
|
||||
variant: TypographyVariant,
|
||||
): VariantComponent => {
|
||||
return (TachTypography[namespace] as unknown as Record<TypographyVariant, VariantComponent>)[variant];
|
||||
};
|
||||
|
||||
interface VariantScaleArgs {
|
||||
namespace: TypographyNamespace;
|
||||
color: TypographyColor;
|
||||
weight: TypographyWeight;
|
||||
sampleText: string;
|
||||
}
|
||||
|
||||
interface ColorPaletteArgs {
|
||||
namespace: TypographyNamespace;
|
||||
variant: TypographyVariant;
|
||||
weight: TypographyWeight;
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "TachTypography/Tokens",
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Token showcase for typography variants and colors. Useful for validating visual consistency against the design system.",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const VariantScale: StoryObj<VariantScaleArgs> = {
|
||||
args: {
|
||||
namespace: "Text",
|
||||
color: "primary",
|
||||
weight: "normal",
|
||||
sampleText: "The quick brown fox jumps over the lazy dog",
|
||||
},
|
||||
argTypes: {
|
||||
namespace: {
|
||||
control: "select",
|
||||
options: ["Text", "Paragraph", "Link", "Title"],
|
||||
},
|
||||
color: {
|
||||
control: "select",
|
||||
options: TYPOGRAPHY_COLORS,
|
||||
},
|
||||
weight: {
|
||||
control: "inline-radio",
|
||||
options: ["normal", "bold"],
|
||||
},
|
||||
sampleText: {
|
||||
control: "text",
|
||||
},
|
||||
},
|
||||
render: args => (
|
||||
<div className="tach-story-surface tach-story-stack">
|
||||
{TYPOGRAPHY_VARIANTS.map(variant => {
|
||||
const Component = getVariantComponent(args.namespace, variant);
|
||||
const componentProps: VariantComponentProps = {
|
||||
color: args.color,
|
||||
weight: args.weight,
|
||||
};
|
||||
|
||||
if (args.namespace === "Link") {
|
||||
componentProps.href = "https://example.com";
|
||||
}
|
||||
|
||||
if (args.namespace === "Title") {
|
||||
componentProps.level = 4;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tach-story-row" key={variant}>
|
||||
<span className="tach-story-label">{variant}</span>
|
||||
<Component {...componentProps}>{args.sampleText}</Component>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const ColorPalette: StoryObj<ColorPaletteArgs> = {
|
||||
args: {
|
||||
namespace: "Text",
|
||||
variant: "Body",
|
||||
weight: "normal",
|
||||
},
|
||||
argTypes: {
|
||||
namespace: {
|
||||
control: "select",
|
||||
options: ["Text", "Paragraph", "Link", "Title"],
|
||||
},
|
||||
variant: {
|
||||
control: "select",
|
||||
options: TYPOGRAPHY_VARIANTS,
|
||||
},
|
||||
weight: {
|
||||
control: "inline-radio",
|
||||
options: ["normal", "bold"],
|
||||
},
|
||||
},
|
||||
render: args => {
|
||||
const Component = getVariantComponent(args.namespace, args.variant);
|
||||
|
||||
return (
|
||||
<div className="tach-story-surface tach-story-grid tach-story-grid--colors">
|
||||
{TYPOGRAPHY_COLORS.map(color => {
|
||||
const componentProps: VariantComponentProps = {
|
||||
color,
|
||||
weight: args.weight,
|
||||
};
|
||||
|
||||
if (args.namespace === "Link") {
|
||||
componentProps.href = "https://example.com";
|
||||
}
|
||||
|
||||
if (args.namespace === "Title") {
|
||||
componentProps.level = 4;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tach-story-row" key={color}>
|
||||
<span className="tach-story-label">{color}</span>
|
||||
<Component {...componentProps}>Color token preview</Component>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const TokenReference: Story = {
|
||||
render: () => (
|
||||
<div className="tach-story-surface tach-story-stack">
|
||||
<div>
|
||||
<h4>Variants ({TYPOGRAPHY_VARIANTS.length})</h4>
|
||||
<ul className="tach-story-token-list">
|
||||
{TYPOGRAPHY_VARIANTS.map(token => (
|
||||
<li key={token}>{token}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Colors ({TYPOGRAPHY_COLORS.length})</h4>
|
||||
<ul className="tach-story-token-list">
|
||||
{TYPOGRAPHY_COLORS.map(token => (
|
||||
<li key={token}>{token}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
103
packages/tach-typography/src/styles/tach-typography.css
Normal file
103
packages/tach-typography/src/styles/tach-typography.css
Normal file
@@ -0,0 +1,103 @@
|
||||
.tach-typography {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-typography.tach-typography {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tach-typography--pointer,
|
||||
.ant-typography.tach-typography--pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tach-typography--bold,
|
||||
.ant-typography.tach-typography--bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.tach-typography--color-primary,
|
||||
.ant-typography.tach-typography--color-primary { color: var(--Text-Primary); }
|
||||
.tach-typography--color-secondary,
|
||||
.ant-typography.tach-typography--color-secondary { color: var(--Text-Secondary); }
|
||||
.tach-typography--color-tertiary,
|
||||
.ant-typography.tach-typography--color-tertiary { color: var(--Text-Tertiary); }
|
||||
.tach-typography--color-quaternary,
|
||||
.ant-typography.tach-typography--color-quaternary { color: var(--Text-Quaternary); }
|
||||
.tach-typography--color-link,
|
||||
.ant-typography.tach-typography--color-link { color: var(--System-HashtagsInPost); }
|
||||
.tach-typography--color-white,
|
||||
.ant-typography.tach-typography--color-white { color: var(--Default-White); }
|
||||
.tach-typography--color-dark,
|
||||
.ant-typography.tach-typography--color-dark { color: var(--Default-Dark); }
|
||||
.tach-typography--color-alert,
|
||||
.ant-typography.tach-typography--color-alert { color: var(--System-Alert); }
|
||||
.tach-typography--color-malahit,
|
||||
.ant-typography.tach-typography--color-malahit { color: var(--Accent-Malahit); }
|
||||
.tach-typography--color-attantion,
|
||||
.ant-typography.tach-typography--color-attantion { color: var(--System-Attantion); }
|
||||
|
||||
.tach-typography--LargeTitle,
|
||||
.ant-typography.tach-typography--LargeTitle { font-family: Inter, sans-serif; font-size: 38px; font-weight: 500; line-height: 46px; }
|
||||
.tach-typography--Title1,
|
||||
.ant-typography.tach-typography--Title1 { font-family: Inter, sans-serif; font-size: 28px; font-weight: 500; line-height: 34px; }
|
||||
.tach-typography--Title2,
|
||||
.ant-typography.tach-typography--Title2 { font-family: Inter, sans-serif; font-size: 22px; font-weight: 500; line-height: 28px; }
|
||||
.tach-typography--Title3,
|
||||
.ant-typography.tach-typography--Title3 { font-family: Inter, sans-serif; font-size: 20px; font-weight: 500; line-height: 26px; }
|
||||
.tach-typography--Headline,
|
||||
.ant-typography.tach-typography--Headline { font-family: Inter, sans-serif; font-size: 16px; font-weight: 500; line-height: 24px; }
|
||||
.tach-typography--Body,
|
||||
.tach-typography--AppMediumBody,
|
||||
.ant-typography.tach-typography--Body,
|
||||
.ant-typography.tach-typography--AppMediumBody { font-family: Inter, sans-serif; font-size: 14px; font-weight: 500; line-height: 20px; }
|
||||
.tach-typography--Inputs,
|
||||
.ant-typography.tach-typography--Inputs { font-family: Inter, sans-serif; font-size: 14px; font-weight: 500; line-height: 24px; }
|
||||
.tach-typography--Subheadline,
|
||||
.ant-typography.tach-typography--Subheadline { font-family: Inter, sans-serif; font-size: 14px; font-weight: 500; line-height: 18px; }
|
||||
.tach-typography--FootnoteUnderline,
|
||||
.ant-typography.tach-typography--FootnoteUnderline { font-family: Inter, sans-serif; font-size: 13px; font-weight: 500; line-height: 18px; text-decoration: underline; }
|
||||
.tach-typography--Footnote,
|
||||
.ant-typography.tach-typography--Footnote { font-family: Inter, sans-serif; font-size: 13px; font-weight: 500; line-height: 18px; }
|
||||
.tach-typography--Caption,
|
||||
.ant-typography.tach-typography--Caption { font-family: Inter, sans-serif; font-size: 10px; font-weight: 500; line-height: 12px; text-transform: uppercase; }
|
||||
.tach-typography--Caption2,
|
||||
.ant-typography.tach-typography--Caption2 { font-family: Inter, sans-serif; font-size: 8px; font-weight: 500; line-height: 10px; text-transform: uppercase; }
|
||||
|
||||
.tach-typography--AccentH1,
|
||||
.ant-typography.tach-typography--AccentH1 { font-family: Unbounded, sans-serif; font-size: 20px; font-weight: 700; line-height: 30px; }
|
||||
.tach-typography--AccentH2,
|
||||
.ant-typography.tach-typography--AccentH2 { font-family: Unbounded, sans-serif; font-size: 16px; font-weight: 700; line-height: 24px; }
|
||||
.tach-typography--AccentSubttl,
|
||||
.ant-typography.tach-typography--AccentSubttl { font-family: Unbounded, sans-serif; font-size: 14px; font-weight: 700; line-height: 22px; }
|
||||
.tach-typography--AccentSubttl2,
|
||||
.ant-typography.tach-typography--AccentSubttl2 { font-family: Unbounded, sans-serif; font-size: 12px; font-weight: 700; line-height: 20px; }
|
||||
.tach-typography--AccentCaption,
|
||||
.ant-typography.tach-typography--AccentCaption { font-family: Unbounded, sans-serif; font-size: 9px; font-weight: 700; line-height: 12px; text-transform: uppercase; }
|
||||
.tach-typography--AccentCaption2,
|
||||
.ant-typography.tach-typography--AccentCaption2 { font-family: Unbounded, sans-serif; font-size: 7px; font-weight: 700; line-height: 10px; text-transform: uppercase; }
|
||||
.tach-typography--AccentRegularM,
|
||||
.ant-typography.tach-typography--AccentRegularM { font-family: Unbounded, sans-serif; font-size: 14px; font-weight: 400; line-height: 22px; }
|
||||
.tach-typography--AccentRegularS,
|
||||
.ant-typography.tach-typography--AccentRegularS { font-family: Unbounded, sans-serif; font-size: 12px; font-weight: 400; line-height: 20px; }
|
||||
.tach-typography--AccentLargeTtl,
|
||||
.ant-typography.tach-typography--AccentLargeTtl { font-family: Unbounded, sans-serif; font-size: 38px; font-weight: 700; line-height: 52px; }
|
||||
.tach-typography--AppMediumSubtext,
|
||||
.ant-typography.tach-typography--AppMediumSubtext { text-align: center; font-family: Inter, sans-serif; font-size: 11px; font-weight: 400; line-height: 17px; }
|
||||
.tach-typography--AppMediumSubtextUnderline,
|
||||
.ant-typography.tach-typography--AppMediumSubtextUnderline { font-family: Inter, sans-serif; font-size: 11px; font-weight: 400; line-height: 17px; text-decoration: underline; }
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.tach-typography--AccentLargeTtl,
|
||||
.ant-typography.tach-typography--AccentLargeTtl {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.tach-typography--AccentRegularM,
|
||||
.ant-typography.tach-typography--AccentRegularM {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
262
packages/tach-typography/styles/typography-vars.scss
Normal file
262
packages/tach-typography/styles/typography-vars.scss
Normal file
@@ -0,0 +1,262 @@
|
||||
// Medium styles
|
||||
@mixin AppMediumLargeTitle {
|
||||
/* app/Medium/LargeTitle */
|
||||
font-family: Inter;
|
||||
font-size: 38px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 46px; /* 121.053% */
|
||||
}
|
||||
@mixin AppMediumTitle1 {
|
||||
/* app/Medium/Title1 */
|
||||
font-family: Inter;
|
||||
font-size: 28px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 34px; /* 121.429% */
|
||||
}
|
||||
@mixin AppMediumTitle2 {
|
||||
/* app/Medium/Title2 */
|
||||
font-family: Inter;
|
||||
font-size: 22px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 28px; /* 127.273% */
|
||||
}
|
||||
|
||||
@mixin AppMediumTitle3 {
|
||||
/* app/Medium/Title3 */
|
||||
font-family: Inter;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 26px; /* 130% */
|
||||
}
|
||||
@mixin AppMediumHeadline {
|
||||
/* app/Medium/Headline */
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 150% */
|
||||
}
|
||||
@mixin AppMediumBody {
|
||||
/* app/Medium/Body */
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
@mixin AppMediumInputs {
|
||||
/* app/Medium/Inputs */
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 171.429% */
|
||||
}
|
||||
@mixin AppMediumSubheadline {
|
||||
/* app/Medium/Subheadline */
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px; /* 128.571% */
|
||||
}
|
||||
@mixin AppMediumFootnoteUnderline {
|
||||
/* app/Medium/Footnote Underline */
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-skip-ink: none;
|
||||
text-decoration-thickness: auto;
|
||||
text-underline-offset: auto;
|
||||
text-underline-position: from-font;
|
||||
}
|
||||
@mixin AppMediumFootnote {
|
||||
/* app/Medium/footnote */
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px; /* 138.462% */
|
||||
}
|
||||
@mixin AppMediumCaption {
|
||||
/* app/Medium/Caption */
|
||||
font-family: Inter;
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 12px; /* 120% */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@mixin AppMediumCaption2 {
|
||||
/* app/Medium/Caption2 */
|
||||
font-family: Inter;
|
||||
font-size: 8px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 10px; /* 125% */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@mixin AppMediumSubtext {
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 17px; /* 154.545% */
|
||||
}
|
||||
|
||||
@mixin AppMediumSubtextUnderline {
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 17px;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-skip-ink: none;
|
||||
text-decoration-thickness: auto;
|
||||
text-underline-offset: auto;
|
||||
text-underline-position: from-font;
|
||||
}
|
||||
|
||||
// Bold Styles
|
||||
@mixin AppBoldLargeTitle {
|
||||
@include AppMediumLargeTitle;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldTitle1 {
|
||||
@include AppMediumTitle1;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldTitle2 {
|
||||
@include AppMediumTitle2;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldTitle3 {
|
||||
@include AppMediumTitle3;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldHeadline {
|
||||
@include AppMediumHeadline;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldBody {
|
||||
@include AppMediumBody;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldInputs {
|
||||
@include AppBoldInputs;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldSubheadline {
|
||||
@include AppMediumSubheadline;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldFootnoteUnderline {
|
||||
@include AppMediumFootnoteUnderline;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldFootnote {
|
||||
@include AppMediumFootnote;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldCaption {
|
||||
@include AppMediumCaption;
|
||||
font-weight: 700;
|
||||
}
|
||||
@mixin AppBoldCaption2 {
|
||||
@include AppMediumCaption2;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// Accent Styles
|
||||
@mixin AccentLargeTtl {
|
||||
font-family: Unbounded;
|
||||
font-size: 38px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 52px; /* 136.842% */
|
||||
|
||||
@media (max-width: 575px) {
|
||||
@include AccentFontH1;
|
||||
}
|
||||
}
|
||||
@mixin AccentFontH1 {
|
||||
/* Accent font/h1 */
|
||||
font-family: Unbounded;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 30px; /* 150% */
|
||||
}
|
||||
@mixin AccentFontH2 {
|
||||
/* Accent font/h2 */
|
||||
font-family: Unbounded;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 24px; /* 150% */
|
||||
}
|
||||
@mixin AccentFontSubttl {
|
||||
/* Accent font/subttl */
|
||||
font-family: Unbounded;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 22px; /* 157.143% */
|
||||
}
|
||||
@mixin AccentFontSubttl2 {
|
||||
/* Accent font/subttl2 */
|
||||
font-family: Unbounded;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 20px; /* 166.667% */
|
||||
}
|
||||
@mixin AccentFontCaption {
|
||||
/* Accent font/caption */
|
||||
font-family: Unbounded;
|
||||
font-size: 9px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 12px; /* 133.333% */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@mixin AccentFontCaption2 {
|
||||
/* Accent font/caption2 */
|
||||
font-family: Unbounded;
|
||||
font-size: 7px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 10px; /* 142.857% */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@mixin AccentFontRegularM {
|
||||
/* accent font/regularM */
|
||||
font-family: Unbounded;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px; /* 157.143% */
|
||||
|
||||
@media (max-width: 575px) {
|
||||
@include AccentFontRegularS;
|
||||
}
|
||||
}
|
||||
@mixin AccentFontRegularS {
|
||||
/* accent font/regularS */
|
||||
font-family: Unbounded;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 166.667% */
|
||||
}
|
||||
10
packages/tach-typography/tsconfig.json
Normal file
10
packages/tach-typography/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["node", "react"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user