Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#238] 홈페이지 초기 로딩 시 스켈레톤 UI 적용 및 code-highlighter 컴포넌트 스토리북 제작 #239

Merged
merged 5 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
* 코드의 줄 수를 표시하는 컴포넌트
*/
Comment on lines +8 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc 잊지 않고 신경써주셔서 감사합니다!

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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

꼼꼼하시네요👍🏻

({ 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
Loading