BLOG-5_apply_the_first_view (#15)
squid 756112cb21 BLOG-5 docs: add comment for next config squid c16e3f29a0 BLOG-5 refactor: apply clean architecture directory structure squid 7f1a202cc7 BLOG-5 feat: self tags animation squid 738008b983 BLOG-5 refactor: remove unused props definition squid 90c8a2f627 BLOG-5 feat: home page fisrt view desktop layout zoe d76f54b2b1 BLOG-5 feat: mobile home page first view Reviewed-on: #15 Co-authored-by: SquidSpirit <squid@squidspirit.com> Co-committed-by: SquidSpirit <squid@squidspirit.com>
This commit is contained in:
parent
0ed7511119
commit
e12d815440
@ -1,7 +1,8 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
// Avoid from rendering twice in development mode
|
||||
reactStrictMode: false,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
@ -15,9 +15,11 @@
|
||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"next": "15.1.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"react-redux": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
|
86
frontend/pnpm-lock.yaml
generated
86
frontend/pnpm-lock.yaml
generated
@ -23,6 +23,9 @@ importers:
|
||||
'@fortawesome/react-fontawesome':
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.0.0)
|
||||
'@reduxjs/toolkit':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0(react-redux@9.2.0(@types/react@19.0.7)(react@19.0.0)(redux@5.0.1))(react@19.0.0)
|
||||
next:
|
||||
specifier: 15.1.4
|
||||
version: 15.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -32,6 +35,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-redux:
|
||||
specifier: ^9.2.0
|
||||
version: 9.2.0(@types/react@19.0.7)(react@19.0.0)(redux@5.0.1)
|
||||
devDependencies:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.2.0
|
||||
@ -357,6 +363,17 @@ packages:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@reduxjs/toolkit@2.5.0':
|
||||
resolution: {integrity: sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==}
|
||||
peerDependencies:
|
||||
react: ^16.9.0 || ^17.0.0 || ^18 || ^19
|
||||
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-redux:
|
||||
optional: true
|
||||
|
||||
'@rtsao/scc@1.1.0':
|
||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||
|
||||
@ -389,6 +406,9 @@ packages:
|
||||
'@types/react@19.0.7':
|
||||
resolution: {integrity: sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==}
|
||||
|
||||
'@types/use-sync-external-store@0.0.6':
|
||||
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.20.0':
|
||||
resolution: {integrity: sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@ -979,6 +999,9 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
immer@10.1.1:
|
||||
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
|
||||
engines: {node: '>=6'}
|
||||
@ -1462,6 +1485,18 @@ packages:
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-redux@9.2.0:
|
||||
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.2.25 || ^19
|
||||
react: ^18.0 || ^19
|
||||
redux: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
redux:
|
||||
optional: true
|
||||
|
||||
react@19.0.0:
|
||||
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -1473,6 +1508,14 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
redux-thunk@3.1.0:
|
||||
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
|
||||
peerDependencies:
|
||||
redux: ^5.0.0
|
||||
|
||||
redux@5.0.1:
|
||||
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
|
||||
|
||||
reflect.getprototypeof@1.0.10:
|
||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -1481,6 +1524,9 @@ packages:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@ -1729,6 +1775,11 @@ packages:
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
use-sync-external-store@1.4.0:
|
||||
resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
@ -2010,6 +2061,16 @@ snapshots:
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@reduxjs/toolkit@2.5.0(react-redux@9.2.0(@types/react@19.0.7)(react@19.0.0)(redux@5.0.1))(react@19.0.0)':
|
||||
dependencies:
|
||||
immer: 10.1.1
|
||||
redux: 5.0.1
|
||||
redux-thunk: 3.1.0(redux@5.0.1)
|
||||
reselect: 5.1.1
|
||||
optionalDependencies:
|
||||
react: 19.0.0
|
||||
react-redux: 9.2.0(@types/react@19.0.7)(react@19.0.0)(redux@5.0.1)
|
||||
|
||||
'@rtsao/scc@1.1.0': {}
|
||||
|
||||
'@rushstack/eslint-patch@1.10.5': {}
|
||||
@ -2038,6 +2099,8 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/use-sync-external-store@0.0.6': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@ -2840,6 +2903,8 @@ snapshots:
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
immer@10.1.1: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
@ -3266,6 +3331,15 @@ snapshots:
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-redux@9.2.0(@types/react@19.0.7)(react@19.0.0)(redux@5.0.1):
|
||||
dependencies:
|
||||
'@types/use-sync-external-store': 0.0.6
|
||||
react: 19.0.0
|
||||
use-sync-external-store: 1.4.0(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.7
|
||||
redux: 5.0.1
|
||||
|
||||
react@19.0.0: {}
|
||||
|
||||
read-cache@1.0.0:
|
||||
@ -3276,6 +3350,12 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
redux-thunk@3.1.0(redux@5.0.1):
|
||||
dependencies:
|
||||
redux: 5.0.1
|
||||
|
||||
redux@5.0.1: {}
|
||||
|
||||
reflect.getprototypeof@1.0.10:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@ -3296,6 +3376,8 @@ snapshots:
|
||||
gopd: 1.2.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
reselect@5.1.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
@ -3642,6 +3724,10 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
use-sync-external-store@1.4.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
|
@ -3,5 +3,8 @@
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@apply bg-white text-gray-600;
|
||||
@apply bg-white text-base font-normal text-gray-600;
|
||||
|
||||
--tool-bar-height: 4rem;
|
||||
--content-height: calc(100vh - var(--tool-bar-height));
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Navbar from "@/ui/layout/navbar";
|
||||
import Footer from "@/ui/layout/footer";
|
||||
import Navbar from "@/lib/common/presenter/ui/Navbar";
|
||||
import Footer from "@/lib/common/presenter/ui/Footer";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
@ -21,11 +21,11 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="zh-Hant">
|
||||
<body className={`${inter.className} antialiased`}>
|
||||
<div className="min-h-screen">
|
||||
<Navbar />
|
||||
{children}
|
||||
</div>
|
||||
<Footer />
|
||||
<div className="min-h-screen">
|
||||
<Navbar />
|
||||
{children}
|
||||
</div>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
@ -1,5 +1,19 @@
|
||||
import SelfTags from "@/lib/home/framework/ui/SelfTags";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div></div>
|
||||
<div className="mx-auto flex min-h-[--content-height] max-w-screen-xl flex-col justify-center gap-y-2.5 px-4 md:gap-y-8 md:px-6">
|
||||
<h2 className="text-3xl font-bold text-gray-800 md:text-6xl">
|
||||
Hello 大家好!
|
||||
</h2>
|
||||
<h1 className="flex flex-row items-center gap-x-2 text-4xl font-bold text-gray-800 md:text-7xl">
|
||||
<span>我是</span>
|
||||
<div className="rounded-lg bg-blue-600 px-1.5 py-1">
|
||||
<span className="text-white">Squid</span>
|
||||
</div>
|
||||
<span>魷魚</span>
|
||||
</h1>
|
||||
<SelfTags />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { faGithub, faYoutube } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faEnvelope } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
|
||||
type Props = {};
|
||||
|
||||
export default function Footer({}: Props) {
|
||||
export default function Footer() {
|
||||
return (
|
||||
<div className="border-t border-gray-300">
|
||||
<div className="mx-auto flex max-w-screen-xl flex-col items-center justify-center gap-4 px-4 py-12 md:flex-row md:px-6">
|
@ -1,11 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = {};
|
||||
|
||||
export default function Navbar({}: Props) {
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<div className="border-b border-gray-300">
|
||||
<div className="mx-auto flex h-16 max-w-screen-xl flex-row items-center justify-between px-4 md:px-6">
|
||||
<div className="mx-auto flex h-[--tool-bar-height] max-w-screen-xl flex-row items-center justify-between px-4 md:px-6">
|
||||
<a className="text-2xl font-black text-gray-800" href="/">
|
||||
魚之魷魂
|
||||
</a>
|
55
frontend/src/lib/home/framework/ui/SelfTags.tsx
Normal file
55
frontend/src/lib/home/framework/ui/SelfTags.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import tagStore from "../../presenter/tagStore";
|
||||
import { useTagDispatch, useTagSelector } from "../../presenter/tagHooks";
|
||||
import { useEffect, useState } from "react";
|
||||
import { tagStartedAction, tagStoppedAction } from "../../presenter/tagSlice";
|
||||
|
||||
export default function SelfTags() {
|
||||
return (
|
||||
<Provider store={tagStore}>
|
||||
<_SelfTags />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function _SelfTags() {
|
||||
const tags = useTagSelector((state) => state.tag.tags);
|
||||
const dispatch = useTagDispatch();
|
||||
|
||||
const [isTagsVisible, setIsTagsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(tagStartedAction({ interval: 4000 }));
|
||||
return () => {
|
||||
dispatch(tagStoppedAction());
|
||||
setIsTagsVisible(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (tags.length === 0) return;
|
||||
setIsTagsVisible(true);
|
||||
setTimeout(() => {
|
||||
setIsTagsVisible(false);
|
||||
}, 3500);
|
||||
}, [tags]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative w-full max-w-screen-md transition-opacity duration-500 ${isTagsVisible ? "opacity-100" : "opacity-0"}`}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-transparent via-60% to-white" />
|
||||
<div className="flex flex-row items-center gap-x-2 overflow-hidden">
|
||||
{tags.map((tag) => (
|
||||
<Hashtag key={tag} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Hashtag(props: { tag: string }) {
|
||||
return <span className="text-nowrap text-gray-400"># {props.tag}</span>;
|
||||
}
|
6
frontend/src/lib/home/presenter/tagHooks.ts
Normal file
6
frontend/src/lib/home/presenter/tagHooks.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import tagStore from "./tagStore";
|
||||
|
||||
export const useTagDispatch = useDispatch.withTypes<typeof tagStore.dispatch>();
|
||||
export const useTagSelector =
|
||||
useSelector.withTypes<ReturnType<typeof tagStore.getState>>();
|
72
frontend/src/lib/home/presenter/tagSlice.ts
Normal file
72
frontend/src/lib/home/presenter/tagSlice.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import tagStore from "./tagStore";
|
||||
import shuffleArray from "@/lib/util/shuffleArray";
|
||||
|
||||
export interface TagState {
|
||||
tags: string[];
|
||||
timer?: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
export interface TagStartedActionPayload {
|
||||
interval: number;
|
||||
}
|
||||
|
||||
export const tagSlice = createSlice({
|
||||
name: "tag",
|
||||
initialState: {
|
||||
tags: [],
|
||||
timer: undefined,
|
||||
} as TagState,
|
||||
reducers: {
|
||||
started: (state, action: PayloadAction<TagStartedActionPayload>) => {
|
||||
state.tags = shuffleArray(tagsCollection);
|
||||
state.timer = setInterval(() => {
|
||||
tagStore.dispatch(tagSlice.actions.shuffled());
|
||||
}, action.payload.interval);
|
||||
},
|
||||
shuffled: (state) => {
|
||||
state.tags = shuffleArray(tagsCollection);
|
||||
},
|
||||
stopped: (state) => {
|
||||
clearInterval(state.timer);
|
||||
state.timer = undefined;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const tagStartedAction = tagSlice.actions.started;
|
||||
export const tagStoppedAction = tagSlice.actions.stopped;
|
||||
|
||||
const tagReducer = tagSlice.reducer;
|
||||
export default tagReducer;
|
||||
|
||||
const tagsCollection = [
|
||||
"APP",
|
||||
"C++",
|
||||
"Design Pattern",
|
||||
"Docker",
|
||||
"Flutter",
|
||||
"Go",
|
||||
"Java",
|
||||
"LINER",
|
||||
"Linux",
|
||||
"Python",
|
||||
"Squid",
|
||||
"TypeScript",
|
||||
"中央大學",
|
||||
"全端",
|
||||
"分享",
|
||||
"前端",
|
||||
"後端",
|
||||
"教學",
|
||||
"暴肝",
|
||||
"知識",
|
||||
"碼農",
|
||||
"科技",
|
||||
"科普",
|
||||
"程式設計",
|
||||
"資工系",
|
||||
"軟體工程",
|
||||
"遊戲",
|
||||
"魷魚",
|
||||
];
|
10
frontend/src/lib/home/presenter/tagStore.ts
Normal file
10
frontend/src/lib/home/presenter/tagStore.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import tagReducer from "./tagSlice";
|
||||
|
||||
const tagStore = configureStore({
|
||||
reducer: {
|
||||
tag: tagReducer,
|
||||
},
|
||||
});
|
||||
|
||||
export default tagStore;
|
8
frontend/src/lib/util/shuffleArray.ts
Normal file
8
frontend/src/lib/util/shuffleArray.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export default function shuffleArray<T>(array: T[]): T[] {
|
||||
const newArray = [...array];
|
||||
for (let i = newArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
|
||||
}
|
||||
return newArray;
|
||||
}
|
@ -2,7 +2,7 @@ import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./src/ui/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/lib/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user