@matteodicristofalo/text-animations 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/dist/components/split-text-reveal/split-test-reveal.types.d.ts +11 -0
  3. package/dist/components/split-text-reveal/split-text-reveal.d.ts +3 -0
  4. package/dist/components/split-text-reveal/split-text-reveal.stories.d.ts +26 -0
  5. package/dist/components/split-text-rotate/split-text-rotate.d.ts +3 -0
  6. package/dist/components/split-text-rotate/split-text-rotate.stories.d.ts +22 -0
  7. package/dist/components/split-text-rotate/split-text-rotate.types.d.ts +9 -0
  8. package/dist/hooks/intersection-overser/intersection-observer.d.ts +6 -0
  9. package/dist/index.css +48 -0
  10. package/dist/index.js +146 -0
  11. package/dist/utils/numbers.d.ts +1 -0
  12. package/dist/utils/text.d.ts +4 -0
  13. package/package.json +4 -1
  14. package/.storybook/main.ts +0 -22
  15. package/.storybook/preview.ts +0 -14
  16. package/rollup.config.mjs +0 -21
  17. package/src/components/split-text-reveal/split-test-reveal.types.ts +0 -12
  18. package/src/components/split-text-reveal/split-text-reveal.module.scss +0 -26
  19. package/src/components/split-text-reveal/split-text-reveal.stories.ts +0 -34
  20. package/src/components/split-text-reveal/split-text-reveal.tsx +0 -61
  21. package/src/components/split-text-rotate/split-text-rotate.module.scss +0 -34
  22. package/src/components/split-text-rotate/split-text-rotate.stories.ts +0 -29
  23. package/src/components/split-text-rotate/split-text-rotate.tsx +0 -60
  24. package/src/components/split-text-rotate/split-text-rotate.types.ts +0 -9
  25. package/src/global.d.ts +0 -4
  26. package/src/hooks/intersection-overser/intersection-observer.ts +0 -41
  27. package/src/utils/numbers.ts +0 -3
  28. package/src/utils/text.ts +0 -19
  29. package/tsconfig.json +0 -13
  30. /package/{src/components/index.ts → dist/components/index.d.ts} +0 -0
  31. /package/{src/hooks/index.ts → dist/hooks/index.d.ts} +0 -0
  32. /package/{src/index.ts → dist/index.d.ts} +0 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @mdc/text-animations
1
+ # @matteodicristofalo/text-animations
2
2
 
3
3
  This library provides a set of React components to animate text
4
4
 
@@ -0,0 +1,11 @@
1
+ export type SplitTextRevealProps = {
2
+ text: string;
3
+ splitType?: SplitType;
4
+ revealOptions?: Partial<RevealOptions>;
5
+ };
6
+ type SplitType = "sentence" | "word" | "char";
7
+ type RevealOptions = {
8
+ duration: number;
9
+ stagger: number;
10
+ };
11
+ export {};
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import { SplitTextRevealProps } from "./split-test-reveal.types";
3
+ export declare function SplitTextReveal({ text, splitType, revealOptions, }: SplitTextRevealProps): React.JSX.Element;
@@ -0,0 +1,26 @@
1
+ import type { StoryObj } from "@storybook/react";
2
+ import { SplitTextReveal } from "./split-text-reveal";
3
+ declare const meta: {
4
+ title: string;
5
+ component: typeof SplitTextReveal;
6
+ parameters: {
7
+ layout: string;
8
+ };
9
+ argTypes: {
10
+ text: {
11
+ control: "text";
12
+ };
13
+ splitType: {
14
+ control: "radio";
15
+ options: string[];
16
+ };
17
+ revealOptions: {
18
+ control: {
19
+ type: "object";
20
+ };
21
+ };
22
+ };
23
+ };
24
+ export default meta;
25
+ type Story = StoryObj<typeof meta>;
26
+ export declare const Default: Story;
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import { SplitTextRotateProps } from "./split-text-rotate.types";
3
+ export declare function SplitTextRotate({ text, rotateOptions }: SplitTextRotateProps): React.JSX.Element;
@@ -0,0 +1,22 @@
1
+ import type { StoryObj } from "@storybook/react";
2
+ import { SplitTextRotate } from "./split-text-rotate";
3
+ declare const meta: {
4
+ title: string;
5
+ component: typeof SplitTextRotate;
6
+ parameters: {
7
+ layout: string;
8
+ };
9
+ argTypes: {
10
+ text: {
11
+ control: "text";
12
+ };
13
+ rotateOptions: {
14
+ control: {
15
+ type: "object";
16
+ };
17
+ };
18
+ };
19
+ };
20
+ export default meta;
21
+ type Story = StoryObj<typeof meta>;
22
+ export declare const Default: Story;
@@ -0,0 +1,9 @@
1
+ export type SplitTextRotateProps = {
2
+ text: string;
3
+ rotateOptions?: Partial<RotateOptions>;
4
+ };
5
+ type RotateOptions = {
6
+ duration: number;
7
+ stagger: number;
8
+ };
9
+ export {};
@@ -0,0 +1,6 @@
1
+ import { RefObject } from "react";
2
+ type Options = IntersectionObserverInit & {
3
+ once?: boolean;
4
+ };
5
+ export declare function useIntersectionObserver(ref: RefObject<HTMLElement>, options?: Options): boolean;
6
+ export {};
package/dist/index.css ADDED
@@ -0,0 +1,48 @@
1
+ .split-text-reveal-module_text__-9O5o {
2
+ position: relative;
3
+ }
4
+ .split-text-reveal-module_text__-9O5o .split-text-reveal-module_is-hidden__2J8-i {
5
+ opacity: 0;
6
+ }
7
+ .split-text-reveal-module_text__-9O5o .split-text-reveal-module_container__mkD-P {
8
+ display: inline-flex;
9
+ overflow: hidden;
10
+ }
11
+ .split-text-reveal-module_text__-9O5o .split-text-reveal-module_container__mkD-P span {
12
+ min-width: 0.25em;
13
+ transform: translateY(100%);
14
+ transition-delay: var(--var-delay);
15
+ transition-duration: var(--var-duration);
16
+ transition-property: transform;
17
+ transition-timing-function: ease-out;
18
+ will-change: transform;
19
+ }
20
+ .split-text-reveal-module_text__-9O5o.split-text-reveal-module_animate__y6o78 .split-text-reveal-module_container__mkD-P span {
21
+ transform: translateY(0);
22
+ }
23
+ .split-text-rotate-module_text__xzran {
24
+ display: block;
25
+ overflow: hidden;
26
+ position: relative;
27
+ }
28
+ .split-text-rotate-module_text__xzran .split-text-rotate-module_container__EAJkE {
29
+ display: flex;
30
+ }
31
+ .split-text-rotate-module_text__xzran .split-text-rotate-module_container__EAJkE:nth-of-type(2) {
32
+ position: absolute;
33
+ top: 0;
34
+ transform: translateY(100%);
35
+ }
36
+ .split-text-rotate-module_text__xzran .split-text-rotate-module_container__EAJkE .split-text-rotate-module_char__PYJ-k {
37
+ display: inline-block;
38
+ min-width: 0.25em;
39
+ transition-delay: var(--var-delay-out);
40
+ transition-duration: var(--var-duration);
41
+ transition-property: transform;
42
+ transition-timing-function: ease-out;
43
+ will-change: transform;
44
+ }
45
+ .split-text-rotate-module_text__xzran:hover .split-text-rotate-module_container__EAJkE .split-text-rotate-module_char__PYJ-k {
46
+ transform: translateY(-100%);
47
+ transition-delay: var(--var-delay-in);
48
+ }
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
1
+ import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
+
3
+ function round(num, digits) {
4
+ return parseFloat(num.toFixed(digits));
5
+ }
6
+
7
+ function sentences(text) {
8
+ return segment(text, "sentence");
9
+ }
10
+ function words(text) {
11
+ return segment(text, "word");
12
+ }
13
+ function characters(text) {
14
+ return segment(text, "grapheme");
15
+ }
16
+ function segment(text, granularity) {
17
+ var segmenter = new Intl.Segmenter(undefined, { granularity: granularity });
18
+ return Array.from(segmenter.segment(text), function (segment) { return segment.segment; });
19
+ }
20
+
21
+ /******************************************************************************
22
+ Copyright (c) Microsoft Corporation.
23
+
24
+ Permission to use, copy, modify, and/or distribute this software for any
25
+ purpose with or without fee is hereby granted.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
28
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
29
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
30
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
31
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
32
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
33
+ PERFORMANCE OF THIS SOFTWARE.
34
+ ***************************************************************************** */
35
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
36
+
37
+
38
+ var __assign = function() {
39
+ __assign = Object.assign || function __assign(t) {
40
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
41
+ s = arguments[i];
42
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
43
+ }
44
+ return t;
45
+ };
46
+ return __assign.apply(this, arguments);
47
+ };
48
+
49
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ };
53
+
54
+ function useIntersectionObserver(ref, options) {
55
+ var _a = useState(false), isIntersecting = _a[0], setIntersecting = _a[1];
56
+ useEffect(function () {
57
+ if (!ref.current)
58
+ return;
59
+ var observer = new IntersectionObserver(function (_a) {
60
+ var entry = _a[0];
61
+ if (options === null || options === void 0 ? void 0 : options.once) {
62
+ if (entry.isIntersecting) {
63
+ setIntersecting(true);
64
+ observer.disconnect();
65
+ }
66
+ }
67
+ else {
68
+ setIntersecting(entry.isIntersecting);
69
+ }
70
+ }, __assign({ threshold: 0.75 }, options));
71
+ observer.observe(ref.current);
72
+ return function () { return observer.disconnect(); };
73
+ }, [ref, options]);
74
+ return isIntersecting;
75
+ }
76
+
77
+ function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
78
+
79
+ var styles$1 = {"text":"split-text-reveal-module_text__-9O5o","is-hidden":"split-text-reveal-module_is-hidden__2J8-i","container":"split-text-reveal-module_container__mkD-P","animate":"split-text-reveal-module_animate__y6o78"};
80
+
81
+ var DEFAULT_DURATION$1 = 0.5;
82
+ var DEFAULT_STAGGER$1 = 0.01;
83
+ function SplitTextReveal(_a) {
84
+ var _b;
85
+ var text = _a.text, _c = _a.splitType, splitType = _c === void 0 ? "char" : _c, revealOptions = _a.revealOptions;
86
+ var _d = useState(false), hasBeenMounted = _d[0], setHasBeenMounted = _d[1];
87
+ var ref = useRef(null);
88
+ var memoizedOptions = useMemo(function () { return ({ once: true, threshold: 0.1 }); }, []);
89
+ var isInView = useIntersectionObserver(ref, memoizedOptions);
90
+ var transitionDuration = (revealOptions === null || revealOptions === void 0 ? void 0 : revealOptions.duration) || DEFAULT_DURATION$1;
91
+ var transitionStagger = (revealOptions === null || revealOptions === void 0 ? void 0 : revealOptions.stagger) || DEFAULT_STAGGER$1;
92
+ var splittedText = useMemo(function () {
93
+ switch (splitType) {
94
+ case "sentence":
95
+ return sentences(text);
96
+ case "word":
97
+ return words(text);
98
+ case "char":
99
+ return characters(text);
100
+ }
101
+ }, [splitType, text]);
102
+ useEffect(function () {
103
+ setHasBeenMounted(true);
104
+ }, []);
105
+ return (React.createElement("span", { ref: ref, className: clsx(styles$1["text"], (_b = {}, _b[styles$1["animate"]] = isInView, _b)), style: {
106
+ "--var-duration": "".concat(transitionDuration, "s"),
107
+ } }, hasBeenMounted ? (splittedText.map(function (el, i) { return (React.createElement("span", { key: i, className: styles$1["container"] },
108
+ React.createElement("span", { style: {
109
+ "--var-delay": "".concat(round(transitionStagger * i, 3), "s"),
110
+ } }, el))); })) : (React.createElement("span", { className: styles$1["is-hidden"] }, text))));
111
+ }
112
+
113
+ var styles = {"text":"split-text-rotate-module_text__xzran","container":"split-text-rotate-module_container__EAJkE","char":"split-text-rotate-module_char__PYJ-k"};
114
+
115
+ var DEFAULT_DURATION = 0.25;
116
+ var DEFAULT_STAGGER = 0.025;
117
+ function SplitTextRotate(_a) {
118
+ var text = _a.text, rotateOptions = _a.rotateOptions;
119
+ var _b = useState(false), hasBeenMounted = _b[0], setHasBeenMounted = _b[1];
120
+ var splittedText = useMemo(function () { return characters(text); }, [text]);
121
+ var _c = useMemo(function () { return ({
122
+ transitionDuration: (rotateOptions === null || rotateOptions === void 0 ? void 0 : rotateOptions.duration) || DEFAULT_DURATION,
123
+ transitionStagger: (rotateOptions === null || rotateOptions === void 0 ? void 0 : rotateOptions.stagger) || DEFAULT_STAGGER,
124
+ }); }, [rotateOptions]), transitionDuration = _c.transitionDuration, transitionStagger = _c.transitionStagger;
125
+ var delays = useCallback(function (index) {
126
+ var noElementsZeroBased = splittedText.length - 1;
127
+ return {
128
+ in: round(transitionStagger * index, 3),
129
+ out: round(transitionStagger * (noElementsZeroBased - index), 3),
130
+ };
131
+ }, [splittedText, transitionStagger]);
132
+ useEffect(function () {
133
+ setHasBeenMounted(true);
134
+ }, []);
135
+ return (React.createElement("span", { className: styles["text"], style: {
136
+ "--var-duration": "".concat(transitionDuration, "s"),
137
+ } }, hasBeenMounted ? (Array.from({ length: 2 }).map(function (_, i) { return (React.createElement("span", { key: i, className: styles["container"] }, splittedText.map(function (char, i) {
138
+ var charDelays = delays(i);
139
+ return (React.createElement("span", { key: i, className: styles["char"], style: {
140
+ "--var-delay-in": "".concat(charDelays.in, "s"),
141
+ "--var-delay-out": "".concat(charDelays.out, "s"),
142
+ } }, char));
143
+ }))); })) : (React.createElement("span", null, text))));
144
+ }
145
+
146
+ export { SplitTextReveal, SplitTextRotate };
@@ -0,0 +1 @@
1
+ export declare function round(num: number, digits: number): number;
@@ -0,0 +1,4 @@
1
+ export declare function sentences(text: string): string[];
2
+ export declare function words(text: string): string[];
3
+ export declare function characters(text: string): string[];
4
+ export declare function segment(text: string, granularity: "sentence" | "word" | "grapheme"): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matteodicristofalo/text-animations",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "React library to animate text",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && rollup -c",
@@ -43,6 +43,9 @@
43
43
  ".": "./dist/index.js",
44
44
  "./index.css": "./dist/index.css"
45
45
  },
46
+ "files": [
47
+ "dist"
48
+ ],
46
49
  "author": "Matteo Di Cristofalo",
47
50
  "license": "ISC",
48
51
  "homepage": "https://github.com/matteodicristofalo/text-animations",
@@ -1,22 +0,0 @@
1
- import type { StorybookConfig } from "@storybook/react-vite";
2
- import tsconfigPaths from "vite-tsconfig-paths";
3
-
4
- const config: StorybookConfig = {
5
- stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
6
- addons: [
7
- "@storybook/addon-onboarding",
8
- "@storybook/addon-essentials",
9
- "@chromatic-com/storybook",
10
- "@storybook/addon-interactions",
11
- ],
12
- framework: {
13
- name: "@storybook/react-vite",
14
- options: {},
15
- },
16
- viteFinal: async (config) => {
17
- config.plugins?.push(tsconfigPaths());
18
- return config;
19
- },
20
- };
21
-
22
- export default config;
@@ -1,14 +0,0 @@
1
- import type { Preview } from "@storybook/react";
2
-
3
- const preview: Preview = {
4
- parameters: {
5
- controls: {
6
- matchers: {
7
- color: /(background|color)$/i,
8
- date: /Date$/i,
9
- },
10
- },
11
- },
12
- };
13
-
14
- export default preview;
package/rollup.config.mjs DELETED
@@ -1,21 +0,0 @@
1
- import resolve from '@rollup/plugin-node-resolve';
2
- import typescript from '@rollup/plugin-typescript';
3
- import peerDepsExternal from 'rollup-plugin-peer-deps-external';
4
- import postCss from 'rollup-plugin-postcss';
5
-
6
- export default {
7
- input: 'src/index.ts',
8
- output: {
9
- dir: 'dist',
10
- format: 'es',
11
- },
12
- plugins: [
13
- postCss({
14
- extract: true,
15
- modules: true,
16
- }),
17
- typescript(),
18
- peerDepsExternal(),
19
- resolve(),
20
- ],
21
- }
@@ -1,12 +0,0 @@
1
- export type SplitTextRevealProps = {
2
- text: string;
3
- splitType?: SplitType;
4
- revealOptions?: Partial<RevealOptions>;
5
- };
6
-
7
- type SplitType = "sentence" | "word" | "char";
8
-
9
- type RevealOptions = {
10
- duration: number;
11
- stagger: number;
12
- };
@@ -1,26 +0,0 @@
1
- .text {
2
- position: relative;
3
-
4
- .container {
5
- display: inline-flex;
6
- overflow: hidden;
7
-
8
- span {
9
- min-width: 0.25em;
10
- transform: translateY(100%);
11
- transition-delay: var(--var-delay);
12
- transition-duration: var(--var-duration);
13
- transition-property: transform;
14
- transition-timing-function: ease-out;
15
- will-change: transform;
16
- }
17
- }
18
-
19
- &.animate {
20
- .container {
21
- span {
22
- transform: translateY(0);
23
- }
24
- }
25
- }
26
- }
@@ -1,34 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import { SplitTextReveal } from "./split-text-reveal";
3
-
4
- const meta = {
5
- title: "SplitTextReveal",
6
- component: SplitTextReveal,
7
- parameters: {
8
- layout: "centered",
9
- },
10
- argTypes: {
11
- text: {
12
- control: "text",
13
- },
14
- splitType: {
15
- control: "radio",
16
- options: ["char", "word", "sentence"],
17
- },
18
- revealOptions: {
19
- control: {
20
- type: "object",
21
- },
22
- },
23
- },
24
- } satisfies Meta<typeof SplitTextReveal>;
25
-
26
- export default meta;
27
- type Story = StoryObj<typeof meta>;
28
-
29
- export const Default: Story = {
30
- args: {
31
- text: "Here is the revealed text",
32
- splitType: "char",
33
- },
34
- };
@@ -1,61 +0,0 @@
1
- "use client";
2
-
3
- import React, { useMemo, useRef } from "react";
4
- import { round } from "@utils/numbers";
5
- import { characters, sentences, words } from "@utils/text";
6
- import { useIntersectionObserver } from "@hooks";
7
- import { SplitTextRevealProps } from "./split-test-reveal.types";
8
- import clsx from "clsx";
9
- import styles from "./split-text-reveal.module.scss";
10
-
11
- const DEFAULT_DURATION = 0.5;
12
- const DEFAULT_STAGGER = 0.01;
13
-
14
- export function SplitTextReveal({
15
- text,
16
- splitType = "char",
17
- revealOptions,
18
- }: SplitTextRevealProps) {
19
- const ref = useRef(null);
20
- const memoizedOptions = useMemo(() => ({ once: true, threshold: 0.1 }), []);
21
- const isInView = useIntersectionObserver(ref, memoizedOptions);
22
- const transitionDuration = revealOptions?.duration || DEFAULT_DURATION;
23
- const transitionStagger = revealOptions?.stagger || DEFAULT_STAGGER;
24
-
25
- const splitFn = useMemo(() => {
26
- switch (splitType) {
27
- case "sentence":
28
- return sentences;
29
- case "word":
30
- return words;
31
- case "char":
32
- return characters;
33
- }
34
- }, [splitType]);
35
-
36
- return (
37
- <span
38
- ref={ref}
39
- className={clsx(styles["text"], { [styles["animate"]]: isInView })}
40
- style={
41
- {
42
- "--var-duration": `${transitionDuration}s`,
43
- } as React.CSSProperties
44
- }
45
- >
46
- {splitFn(text).map((el, i) => (
47
- <span key={i} className={styles["container"]}>
48
- <span
49
- style={
50
- {
51
- "--var-delay": `${round(transitionStagger * i, 3)}s`,
52
- } as React.CSSProperties
53
- }
54
- >
55
- {el}
56
- </span>
57
- </span>
58
- ))}
59
- </span>
60
- );
61
- }
@@ -1,34 +0,0 @@
1
- .text {
2
- display: block;
3
- overflow: hidden;
4
- position: relative;
5
-
6
- .container {
7
- display: flex;
8
-
9
- &:nth-of-type(2) {
10
- position: absolute;
11
- top: 0;
12
- transform: translateY(100%);
13
- }
14
-
15
- .char {
16
- display: inline-block;
17
- min-width: 0.25em;
18
- transition-delay: var(--var-delay-out);
19
- transition-duration: var(--var-duration);
20
- transition-property: transform;
21
- transition-timing-function: ease-out;
22
- will-change: transform;
23
- }
24
- }
25
-
26
- &:hover {
27
- .container {
28
- .char {
29
- transform: translateY(-100%);
30
- transition-delay: var(--var-delay-in);
31
- }
32
- }
33
- }
34
- }
@@ -1,29 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import { SplitTextRotate } from "./split-text-rotate";
3
-
4
- const meta = {
5
- title: "SplitTextRotate",
6
- component: SplitTextRotate,
7
- parameters: {
8
- layout: "centered",
9
- },
10
- argTypes: {
11
- text: {
12
- control: "text",
13
- },
14
- rotateOptions: {
15
- control: {
16
- type: "object",
17
- },
18
- },
19
- },
20
- } satisfies Meta<typeof SplitTextRotate>;
21
-
22
- export default meta;
23
- type Story = StoryObj<typeof meta>;
24
-
25
- export const Default: Story = {
26
- args: {
27
- text: "Rotating text",
28
- },
29
- };
@@ -1,60 +0,0 @@
1
- "use client";
2
-
3
- import React, { useCallback } from "react";
4
- import { round } from "@utils/numbers";
5
- import { characters } from "@utils/text";
6
- import { SplitTextRotateProps } from "./split-text-rotate.types";
7
- import styles from "./split-text-rotate.module.scss";
8
-
9
- const DEFAULT_DURATION = 0.25;
10
- const DEFAULT_STAGGER = 0.025;
11
-
12
- export function SplitTextRotate({ text, rotateOptions }: SplitTextRotateProps) {
13
- const transitionDuration = rotateOptions?.duration || DEFAULT_DURATION;
14
- const transitionStagger = rotateOptions?.stagger || DEFAULT_STAGGER;
15
- const splittedText = characters(text);
16
-
17
- const delays = useCallback(
18
- (index: number) => {
19
- const noElementsZeroBased = splittedText.length - 1;
20
- return {
21
- in: round(transitionStagger * index, 3),
22
- out: round(transitionStagger * (noElementsZeroBased - index), 3),
23
- };
24
- },
25
- [splittedText, transitionStagger]
26
- );
27
-
28
- return (
29
- <span
30
- className={styles["text"]}
31
- style={
32
- {
33
- "--var-duration": `${transitionDuration}s`,
34
- } as React.CSSProperties
35
- }
36
- >
37
- {Array.from({ length: 2 }).map((_, i) => (
38
- <span key={i} className={styles["container"]}>
39
- {splittedText.map((char, i) => {
40
- const charDelays = delays(i);
41
- return (
42
- <span
43
- key={i}
44
- className={styles["char"]}
45
- style={
46
- {
47
- "--var-delay-in": `${charDelays.in}s`,
48
- "--var-delay-out": `${charDelays.out}s`,
49
- } as React.CSSProperties
50
- }
51
- >
52
- {char}
53
- </span>
54
- );
55
- })}
56
- </span>
57
- ))}
58
- </span>
59
- );
60
- }
@@ -1,9 +0,0 @@
1
- export type SplitTextRotateProps = {
2
- text: string;
3
- rotateOptions?: Partial<RotateOptions>;
4
- };
5
-
6
- type RotateOptions = {
7
- duration: number;
8
- stagger: number;
9
- };
package/src/global.d.ts DELETED
@@ -1,4 +0,0 @@
1
- declare module "*.module.scss" {
2
- const classes: { [key: string]: string };
3
- export default classes;
4
- }
@@ -1,41 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState, RefObject } from "react";
4
-
5
- type Options = IntersectionObserverInit & {
6
- once?: boolean;
7
- };
8
-
9
- export function useIntersectionObserver(
10
- ref: RefObject<HTMLElement>,
11
- options?: Options
12
- ): boolean {
13
- const [isIntersecting, setIntersecting] = useState(false);
14
-
15
- useEffect(() => {
16
- if (!ref.current) return;
17
-
18
- const observer = new IntersectionObserver(
19
- ([entry]) => {
20
- if (options?.once) {
21
- if (entry.isIntersecting) {
22
- setIntersecting(true);
23
- observer.disconnect();
24
- }
25
- } else {
26
- setIntersecting(entry.isIntersecting);
27
- }
28
- },
29
- {
30
- threshold: 0.75,
31
- ...options,
32
- }
33
- );
34
-
35
- observer.observe(ref.current);
36
-
37
- return () => observer.disconnect();
38
- }, [ref, options]);
39
-
40
- return isIntersecting;
41
- }
@@ -1,3 +0,0 @@
1
- export function round(num: number, digits: number): number {
2
- return parseFloat(num.toFixed(digits));
3
- }
package/src/utils/text.ts DELETED
@@ -1,19 +0,0 @@
1
- export function sentences(text: string) {
2
- return segment(text, "sentence");
3
- }
4
-
5
- export function words(text: string) {
6
- return segment(text, "word");
7
- }
8
-
9
- export function characters(text: string) {
10
- return segment(text, "grapheme");
11
- }
12
-
13
- export function segment(
14
- text: string,
15
- granularity: "sentence" | "word" | "grapheme"
16
- ) {
17
- const segmenter = new Intl.Segmenter(undefined, { granularity });
18
- return Array.from(segmenter.segment(text), (segment) => segment.segment);
19
- }
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "declaration": true,
4
- "esModuleInterop": true,
5
- "jsx": "react",
6
- "lib": ["esnext", "dom"],
7
- "outDir": "dist",
8
- "paths": {
9
- "@hooks": ["./src/hooks/index"],
10
- "@utils/*": ["./src/utils/*"]
11
- }
12
- }
13
- }
File without changes
File without changes