Skip to content

Commit

Permalink
[#238] 홈페이지 초기 로딩 시 스켈레톤 UI 적용 및 code-highlighter 컴포넌트 스토리북 제작 (#239)
Browse files Browse the repository at this point in the history
* 🙀 chore: 로딩 시 스켈레톤 UI 추가 및 무한스크롤 커스텀 훅으로 분리

* ✨ feat: 코드 high-lighter storybook 추가

* 🙀 chore: let -> const로 변경
  • Loading branch information
lee0jae330 authored Dec 5, 2024
1 parent 8c3233b commit 2c9e07b
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 44 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,14 @@
<h1>hello world!</h1>
```

웹 개발 초보자들에게 코드를 작성하는 것은 어렵고 부담스럽게 느껴질 수 있습니다.<br />
하지만 BooLock에서는 블록을 조립하며 쉽고 재미있게 자신만의 웹사이트를 만들 수 있습니다.<br />
웹 개발 초보자들에게 코드를 작성하는 것은 어렵고 부담스럽게 느껴질 수 있습니다.<br />
하지만 BooLock에서는 블록을 조립하며 쉽고 재미있게 자신만의 웹사이트를 만들 수 있습니다.<br />

BooLock은 HTML과 CSS를 블록코딩 방식으로 학습할 수 있는 플랫폼입니다.<br />
코드를 몰라도 블록을 조립하는 간단한 방식으로 원하는 웹사이트를 제작하고, 이를 통해 웹 개발의 기초를 재미있게 배울 수 있습니다.<br />
BooLock은 HTML과 CSS를 블록코딩 방식으로 학습할 수 있는 플랫폼입니다.<br />
코드를 몰라도 블록을 조립하는 간단한 방식으로 원하는 웹사이트를 제작하고, 이를 통해 웹 개발의 기초를 재미있게 배울 수 있습니다.<br />

블록들이 모여 만들어진 나만의 웹페이지를 주변 사람들과 공유해보세요. ❤️


<br/>

# 🐥 주요기능 <a id="major_feature"></a>
Expand Down Expand Up @@ -184,7 +183,7 @@ BooLock은 HTML과 CSS를 블록코딩 방식으로 학습할 수 있는 플랫
<img src="https://github.com/user-attachments/assets/0a337295-256e-4a59-92a7-9c83ce8a1618" alt="storybook description" width="600"/>
</div>

Blockly의 Flyout 내부에 DOM 요소(input, button)를 동적으로 생성하여 사용자 인터페이스를 구성했습니다. 입력값은 중복 확인과 유효성 검사를 거쳐 CSS_ 접두사를 붙여 블록으로 등록되며, 즉시 Flyout에 반영됩니다. 생성된 블록은 우클릭 시 "블록 삭제" 옵션으로 관련된 모든 인스턴스를 삭제할 수 있습니다. Reset CSS 체크박스와 툴팁을 추가해 기본 스타일 초기화와 사용자 경험을 개선했습니다.
Blockly의 Flyout 내부에 DOM 요소(input, button)를 동적으로 생성하여 사용자 인터페이스를 구성했습니다. 입력값은 중복 확인과 유효성 검사를 거쳐 CSS\_ 접두사를 붙여 블록으로 등록되며, 즉시 Flyout에 반영됩니다. 생성된 블록은 우클릭 시 "블록 삭제" 옵션으로 관련된 모든 인스턴스를 삭제할 수 있습니다. Reset CSS 체크박스와 툴팁을 추가해 기본 스타일 초기화와 사용자 경험을 개선했습니다.

> **👇 더 자세한 기록 및 과정 확인하기 👇** <br/> > [Blockly 커스텀](https://www.notion.so/Blockly-14077ba9a1b680ccafcbd5b3781bf390?pvs=21)
Expand Down Expand Up @@ -268,6 +267,7 @@ React의 SPA 구조로 인해 검색 엔진이 페이지 정보를 인식하지
> **👇 더 자세한 기록 및 과정 확인하기 👇** <br/> > [사용자 가이드](https://www.notion.so/51679d3e288b485f9a0ea9068eb621f5?pvs=21) <br/>
### 스토리북 테스트<a id="fe-storybook"></a>

[**스토리북 배포 링크**](https://boostcampwm-2024.github.io/web31-BooLock/)

<div align="center">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Meta, StoryObj } from '@storybook/react';

import { CodeContent } from './CodeContent';

const meta: Meta<typeof CodeContent> = {
title: 'shared/code-highlighter/CodeContent',
component: CodeContent,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof CodeContent>;

export const Default: Story = {
args: {
code: `<html>
<head></head>
<body>
<div>
<h1>title</h1>
<p>content</p>
</div>
</body>
</html>`,
codeLineList: [
'<html>',
' <head></head>',
' <body>',
' <div>',
' <h1>title</h1>',
' <p>content</p>',
' </div>',
' </body>',
'</html>',
],
selectedBlockStartLine: 5,
selectedBlockLength: 7,
selectedBlockType: 'BOOLOCK_SYSTEM_html',
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import styles from '../styles/CodeViewer.module.css';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';

import styles from '../../styles/CodeViewer.module.css';

type CodeViewerProps = {
code: string;
Expand All @@ -9,6 +10,11 @@ type CodeViewerProps = {
selectedBlockType?: string | null;
};

/**
*
* @description
* 변환된 HTML, CSS 코드를 보여주는 컴포넌트
*/
export const CodeContent = ({
code,
codeLineList,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Meta, StoryObj } from '@storybook/react';

import { CodeViewer } from './CodeViewer';

const meta: Meta<typeof CodeViewer> = {
title: 'shared/code-highlighter/CodeViewer',
component: CodeViewer,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof CodeViewer>;

export const Default: Story = {
args: {
code: `<html>
<head></head>
<body>
<div>
<h1>title</h1>
<p>content</p>
</div>
</body>
</html>`,
type: 'html',
theme: 'light',
},
};

export const DarkThemeHTML: Story = {
args: {
code: `<html>
<head></head>
<body>
<div>
<h1>title</h1>
<p>content</p>
</div>
</body>
</html>`,
type: 'html',
theme: 'dark',
},
};

export const LightThemeCss: Story = {
args: {
code: `.title {
background-color: red;
}
.content {
font-size: 16px;
font-weight: bold;
font-family: 'Arial';
color: #000;
}
`,
type: 'css',
theme: 'light',
},
};

export const DarkThemeCss: Story = {
args: {
code: `.title {
background-color: red;
}
.content {
font-size: 16px;
font-weight: bold;
font-family: 'Arial';
color: #000;
}
`,
type: 'css',
theme: 'dark',
},
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CodeContent } from './CodeContent';
import { LineNumbers } from './LineNumbers';
import { parseHighlightCss } from '../utils/parseHighlightCss';
import { parseHighlightHtml } from '../utils/parseHighlightHtml';
import styles from '../styles/CodeViewer.module.css';
import { CodeContent } from '../CodeContent/CodeContent';
import { LineNumbers } from '../LineNumbers/LineNumbers';
import { parseHighlightCss } from '../../utils/parseHighlightCss';
import { parseHighlightHtml } from '../../utils/parseHighlightHtml';
import styles from '../../styles/CodeViewer.module.css';
import { useCoachMarkStore } from '@/shared/store/useCoachMarkStore';

type CodeViewerProps = {
Expand All @@ -14,6 +14,11 @@ type CodeViewerProps = {
selectedBlockType?: string | null;
};

/**
*
* @description
* 변환된 HTML, CSS 코드를 줄 수와 함께 보여주는 컴포넌트
*/
export const CodeViewer = ({
code,
type,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, StoryObj } from '@storybook/react';

import { LineNumbers } from './LineNumbers';

const meta: Meta<typeof LineNumbers> = {
title: 'shared/code-highlighter/LineNumbers',
component: LineNumbers,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof LineNumbers>;

export const Default: Story = {
args: {
codeLineList: ['line1', 'line2', 'line3'],
},
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import styles from '../../styles/CodeViewer.module.css';
import { useState } from 'react';
import styles from '../styles/CodeViewer.module.css';

type LineNumbersProps = {
codeLineList: string[];
};

/**
*
* @description
* 코드의 줄 수를 표시하는 컴포넌트
*/
export const LineNumbers = ({ codeLineList }: LineNumbersProps) => {
const [hoveredLineNumber, setHoveredLineNumber] = useState<number | null>(null);

Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/shared/code-highlighter/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { CodeViewer } from './components/CodeViewer';
export { CodeViewer } from './components/CodeViewer/CodeViewer';
1 change: 1 addition & 0 deletions apps/client/src/shared/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export { useCssOptionItem } from './css/useCssOptionItem';
export { workspaceKeys } from './query-key/workspaceKeys';

export { usePreventLeaveWorkspacePage } from './usePreventLeaveWorkspacePage';
export { useInfiniteScroll } from './useInfiniteScroll';
29 changes: 29 additions & 0 deletions apps/client/src/shared/hooks/useInfiniteScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect, useRef } from 'react';

type useInfiniteScrollProps = {
intersectionCallback: IntersectionObserverCallback;
};

export const useInfiniteScroll = ({ intersectionCallback }: useInfiniteScrollProps) => {
const targetRef = useRef<HTMLDivElement | null>(null);

const option = {
root: null,
rootMargin: '0px',
threshold: 0.5,
};

useEffect(() => {
if (!targetRef.current) {
return;
}
const observer = new IntersectionObserver(intersectionCallback, option);
observer.observe(targetRef.current);
return () => {
if (targetRef.current) {
observer.unobserve(targetRef.current);
}
};
}, [intersectionCallback]);
return targetRef;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useWorkspaceChangeStatusStore } from '@/shared/store';

export const usePreventLeaveWorkspacePage = () => {
const { isBlockChanged, isCssChanged } = useWorkspaceChangeStatusStore();
let blocker = useBlocker(
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
currentLocation.pathname !== nextLocation.pathname && (isBlockChanged || isCssChanged)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { EmptyWorkspace, WorkspaceGrid, WorkspaceHeader, WorkspaceList } from '@/widgets';
import { useEffect, useRef } from 'react';
import { useGetWorkspaceList, useInfiniteScroll } from '@/shared/hooks';

import { SkeletonWorkspaceList } from '@/shared/ui';
import { WorkspaceLoadError } from '@/entities';
import { useGetWorkspaceList } from '@/shared/hooks';

/**
*
Expand All @@ -14,36 +13,25 @@ export const WorkspaceContainer = () => {
const { hasNextPage, fetchNextPage, isPending, isFetchingNextPage, isError, workspaceList } =
useGetWorkspaceList();

const nextFetchTargetRef = useRef<HTMLDivElement | null>(null);

useEffect(() => {
const options = {
root: null,
rootMargin: '0px',
threshold: 0.5,
};
const fetchCallback: IntersectionObserverCallback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasNextPage) {
fetchNextPage();
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(fetchCallback, options);
if (nextFetchTargetRef.current) {
observer.observe(nextFetchTargetRef.current);
}
return () => {
if (nextFetchTargetRef.current) {
observer.unobserve(nextFetchTargetRef.current);
const fetchCallback: IntersectionObserverCallback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasNextPage) {
fetchNextPage();
observer.unobserve(entry.target);
}
};
}, [workspaceList]);
});
};

const nextFetchTargetRef = useInfiniteScroll({ intersectionCallback: fetchCallback });

return (
<section className="w-full max-w-[1152px] px-3 pb-48">
<WorkspaceHeader />
{isPending && (
<WorkspaceGrid>
<SkeletonWorkspaceList skeletonNum={8} />
</WorkspaceGrid>
)}
{isError ? (
<WorkspaceLoadError />
) : (
Expand All @@ -53,7 +41,7 @@ export const WorkspaceContainer = () => {
) : (
<WorkspaceGrid>
<WorkspaceList workspaceList={workspaceList} />
{(isPending || isFetchingNextPage) && <SkeletonWorkspaceList skeletonNum={8} />}
{isFetchingNextPage && <SkeletonWorkspaceList skeletonNum={8} />}
</WorkspaceGrid>
))
)}
Expand Down

0 comments on commit 2c9e07b

Please sign in to comment.