Skip to content

Commit

Permalink
[Feat] LottieAnimation 컴포넌트 추가 (#63)
Browse files Browse the repository at this point in the history
* feat(packages/ui): 로티 파일, 생성 스크립트 추가

* feat(packages/ui): LottieAnimation 컴포넌트 추가

* fix(packages/ui): LottieAnimation 따로 export 하도록 수정, 에셋 상대경로로 수정

* fix(packages/ui): 스크립트 오타 수정

* feat(packages/ui): 코드리뷰 반영

* fix: 빌드 에러 수정
  • Loading branch information
kongnayeon authored Jan 17, 2025
1 parent 1b1f450 commit 2124a91
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 18 deletions.
14 changes: 13 additions & 1 deletion apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import Link from 'next/link';
import {
Icon,
Toast,
Expand All @@ -11,7 +10,15 @@ import {
Label,
Breadcrumb,
} from '@repo/ui';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { overlay } from 'overlay-kit';
const LottieAnimation = dynamic(
() => import('@repo/ui/LottieAnimation').then((mod) => mod.LottieAnimation),
{
ssr: false,
}
);

export default function Home() {
const notify1 = () =>
Expand Down Expand Up @@ -108,6 +115,11 @@ export default function Home() {
<Label variant="required">어떤 글을 생성할까요?</Label>
<Label variant="optional">어떤 글을 생성할까요?</Label>
</div>
<LottieAnimation
animationData="loadingBlack"
width="2.4rem"
height="2.4rem"
/>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<Breadcrumb>
<Breadcrumb.Item>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dependencies": {
"@vanilla-extract/css": "^1.17.0",
"@vanilla-extract/dynamic": "^2.1.2",
"@vanilla-extract/recipes": "^0.5.5"
"@vanilla-extract/recipes": "^0.5.5",
"lottie-react": "^2.4.0"
}
}
2 changes: 1 addition & 1 deletion packages/ui/esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const buildOptions = {
loader: { '.css': 'file' },
allowOverwrite: true,
outdir,
external: ['react'],
external: ['react', 'react-dom'],
};

esbuild
Expand Down
5 changes: 4 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@
".": {
"import": "./dist/index.js"
},
"./LottieAnimation": "./dist/components/LottieAnimation/LottieAnimation.js",
"./styles": "./dist/index.css"
},
"scripts": {
"build": "tsc && node esbuild.config.mjs",
"dev": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.mjs --watch",
"lint": "eslint . --max-warnings 0",
"build:icons": "esbuild src/scripts/generate-icon-map.ts --bundle --platform=node --outdir=dist/scripts",
"generate:icons": "pnpm run build:icons && node dist/scripts/generate-icon-map.js"
"generate:icons": "pnpm run build:icons && node dist/scripts/generate-icon-map.js",
"build:lotties": "esbuild src/scripts/generate-lottie-map.mts --bundle --platform=node --outdir=dist/scripts",
"generate:lotties": "pnpm run build:lotties && node dist/scripts/generate-lottie-map.js"
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.1",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/assets/lotties/check_black.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.0.0","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":90,"w":1080,"h":1080,"nm":"checklist","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Check","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-201.975,-19.236],[-54.501,128.238],[201.975,-128.238]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.121568627656,0.1254902035,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":70,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.26],"y":[0.934]},"o":{"x":[0.48],"y":[0.03]},"t":0.121,"s":[0]},{"t":45,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":44.82,"s":[0]},{"t":74.8203125,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":-120,"bm":0}],"markers":[]}
1 change: 1 addition & 0 deletions packages/ui/src/assets/lotties/check_white.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.0.0","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":90,"w":1080,"h":1080,"nm":"checklist","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Check","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-201.975,-19.236],[-54.501,128.238],[201.975,-128.238]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":70,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.26],"y":[0.934]},"o":{"x":[0.48],"y":[0.03]},"t":0.121,"s":[0]},{"t":45,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":44.82,"s":[0]},{"t":74.8203125,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":-120,"bm":0}],"markers":[]}
1 change: 1 addition & 0 deletions packages/ui/src/assets/lotties/loading_black.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"5.7.4","fr":60,"ip":613,"op":661,"w":600,"h":600,"nm":"asset","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"shape rotation","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[-26,-20,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[404,404],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":49,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-26,-20],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":613,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":636,"s":[20]},{"t":660,"s":[0]}],"ix":1},"e":{"a":0,"k":28,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":613,"s":[0]},{"t":660,"s":[360]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1500,"st":0,"bm":0}],"markers":[]}
1 change: 1 addition & 0 deletions packages/ui/src/assets/lotties/loading_white.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"nm":"asset","ddd":0,"h":600,"w":600,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"shape rotation","sr":1,"st":0,"op":1500,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-26,-20,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[300,300,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[404,404],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":49,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-26,-20],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":0,"k":28,"ix":2},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":613},{"s":[360],"t":660}],"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":613},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[20],"t":636},{"s":[0],"t":660}],"ix":1},"m":1},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 2","ix":3,"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1}],"ind":1}],"v":"5.7.4","fr":60,"op":661,"ip":613,"assets":[]}
11 changes: 11 additions & 0 deletions packages/ui/src/components/LottieAnimation/LottieAnimation.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createVar, style } from '@vanilla-extract/css';

export const widthVar = createVar();
export const heightVar = createVar();

export const lottieAnimationStyles = style({
width: widthVar,
height: heightVar,
overflow: 'hidden',
display: 'inline-block',
});
58 changes: 58 additions & 0 deletions packages/ui/src/components/LottieAnimation/LottieAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client';

import Lottie, { LottieComponentProps } from 'lottie-react';
import { lotties } from './assets';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import {
heightVar,
lottieAnimationStyles,
widthVar,
} from './LottieAnimation.css';

export type LottieAnimationProps = LottieComponentProps & {
animationData: keyof typeof lotties;
loop?: boolean;
autoplay?: boolean;
width?: string;
height?: string;
className?: string;
'aria-label'?: string;
};

/**
* @property {keyof typeof lotties} animationData - 사용할 로티 애니메이션
* @property {boolean} [loop=true] - 애니메이션을 반복할지 여부
* @property {boolean} [autoplay=true] - 자동으로 애니메이션 재생을 시작할지 여부
* @property {string} [width='auto'] - 너비
* @property {string} [height='auto'] - 높이
* @property {string} [className] - 추가적인 CSS 클래스
* @property {LottieComponentProps} [otherProps] - lottie-react의 prop
*/
export function LottieAnimation({
animationData,
loop = true,
autoplay = true,
width = '100%',
height = '100%',
className = '',
'aria-label': ariaLabel,
...rest
}: LottieAnimationProps) {
return (
<Lottie
animationData={lotties[animationData]}
loop={loop}
autoplay={autoplay}
className={`${lottieAnimationStyles} ${className}`}
style={{
...assignInlineVars({
[widthVar]: width,
[heightVar]: height,
}),
}}
role="img"
aria-label={ariaLabel}
{...rest}
/>
);
}
11 changes: 11 additions & 0 deletions packages/ui/src/components/LottieAnimation/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import check_black from '../../assets/lotties/check_black.json';
import check_white from '../../assets/lotties/check_white.json';
import loading_black from '../../assets/lotties/loading_black.json';
import loading_white from '../../assets/lotties/loading_white.json';

export const lotties = {
checkBlack: check_black,
checkWhite: check_white,
loadingBlack: loading_black,
loadingWhite: loading_white,
} as const;
52 changes: 52 additions & 0 deletions packages/ui/src/scripts/generate-lottie-map.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import fs from 'fs';
import path from 'path';

function toCamelCase(str: string): string {
return str
.replace(/[-_]+(.)?/g, (_, c: string) => (c ? c.toUpperCase() : ''))
.replace(/^\w/, (c: string) => c.toLowerCase());
}

const LOTTIES_DIR: string = path.resolve(__dirname, '../../src/assets/lotties');

const OUTPUT_PATH: string = path.resolve(
__dirname,
'../../src/components/LottieAnimation/assets.ts'
);

const jsonFiles: string[] = fs
.readdirSync(LOTTIES_DIR)
.filter((file) => file.endsWith('.json'));

if (jsonFiles.length === 0) {
console.log('No JSON files found in:', LOTTIES_DIR);
process.exit(0);
}

const importStatements: string[] = [];
const lottieMapping: string[] = [];

jsonFiles.forEach((file) => {
const baseName = path.basename(file, '.json');

const importName: string = baseName;

const lottieKey: string = toCamelCase(baseName);

importStatements.push(
`import ${importName} from '../../assets/lotties/${file}';`
);

lottieMapping.push(` ${lottieKey}: ${importName}`);
});

const fileContent = `${importStatements.join('\n')}
export const lotties = {
${lottieMapping.join(',\n')}
} as const;
`;

fs.writeFileSync(OUTPUT_PATH, fileContent, 'utf-8');

console.log(`로티 생성: ${OUTPUT_PATH}`);
Loading

0 comments on commit 2124a91

Please sign in to comment.