✨ Snippets
===
還有什麼 snippets 的想法都可以提出來
## 📑 配置 Snippets 教學

## 📑 配置 Tailwind 教學
在你的 settings.json 中插入以下設置
``` json
{
"tailwindCSS.classAttributes": [
"class",
"className",
"ngClass",
".*ClassName",
".*Styles",
".*Class"
],
"tailwindCSS.experimental.classRegex": [
"/*\\s?tw:\\s?\\*/\\s?[`|'|\"](.*?)[`|'|\"]",
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["className:[\\[|\\(]?([^)]*)[\\]|\\)]?", "(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"]
],
"editor.quickSuggestions": {
"strings": "on"
},
"editor.inlineSuggest.enabled": true,
}
```
<br>
## 📑 Typescriptreact.json
📚 [snippet with filename](https://stackoverflow.com/questions/70785401/how-to-get-file-name-when-overriding-vs-code-snippet)
📚 [vscode snippets variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables)
```json
{
"create React FC": {
"prefix": "rfc",
"body": [
"\"use client\";\n",
"export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}: React.FC = () => {",
" return (",
" <div>${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}</div>",
" )",
"}"
]
},
"create React FC Layout": {
"prefix": "rfclayout",
"body": [
"\"use client\";\n",
"export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}: React.FC<{ children: React.ReactNode }> = ({ children }) => {",
" return (",
" <div>${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}</div>",
" )",
"}"
]
},
"create React Context with Context Helper": {
"prefix": "rctxhelp",
"body": [
"\"use client\";\n",
"import { contextHelper } from '@nilswg/react-utils';",
"import { useState } from 'react';\n",
"const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = contextHelper({",
" contextName: '${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}',",
" contextValue(props) {",
" const [toggle, setToggle] = useState<boolean>(false);",
" return {",
" toggle,",
" setToggle,",
" };",
" },",
"});\n",
"export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Provider = ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}.Provider;",
"export const use${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}.useContext;"
]
},
// "create React Context (跪求你用 rctxhelp)": {
// "prefix": "rctx",
// "body": [
// "\"use client\";\n",
// "import type { FC, ReactNode } from 'react';",
// "import { createContext, useContext, useMemo } from 'react';\n",
// "type Context_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = {};\n",
// "const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context = createContext<Context_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} | null>(null);\n",
// "type Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}ContextProvider = {",
// " children: ReactNode;",
// "}\n",
// "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}ContextProvider: FC<Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}ContextProvider> = ({ children }) => {",
// " return (",
// " <${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context.Provider",
// " value={useMemo(() => {",
// " return {};",
// " }, [])}>",
// " {children}",
// " </${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context.Provider>",
// " );",
// "};\n",
// "export const use${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = () => {",
// " const context = useContext(${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context);",
// " if (!context) {",
// " throw new Error('use${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} must be used within a ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Provider');",
// " }",
// " return context;",
// "};"
// ]
// },
// "create React Composition Component (不好駕馭的 pattern)": {
// "prefix": "rcc",
// "body": [
// "\"use client\";\n",
// "import type { FC, ReactNode } from 'react';",
// "import {} from 'react';\n",
// "type Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root = {",
// " children: ReactNode;",
// "};\n",
// "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root: FC<Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root> = ({ children }) => {",
// " return <div>{children}</div>;",
// "};\n",
// "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = Object.freeze({",
// " Root: ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root,",
// "});"
// ]
// },
"create Next Page with default export": {
"prefix": "nfc",
"body": [
"",
"const ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}Page: React.FC = () => {",
" return (",
" <div>${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}Page</div>",
" )",
"}\n",
"export default ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}Page;"
]
},
"create Next Layout Page with default export": {
"prefix": "nfclayout",
"body": [
"",
"const ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}PagesLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {",
" return <div>{children}</div>",
"}\n",
"export default ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}PagesLayout;"
]
},
"create Next Slug Page with default export": {
"prefix": "nfcslug",
"body": [
"interface PageProps {",
" params: Promise<{ $1: string }>;",
"}\n",
"const ${TM_DIRECTORY/.*[\\\\/](.*)[\\\\/]\\[.*\\]/${1:/pascalcase}/}Page: React.FC<PageProps> = async ({ params }) => {",
" const { $1 } = await params;",
" return <div>${TM_DIRECTORY/.*[\\\\/](.*)[\\\\/]\\[.*\\]/${1:/pascalcase}/} {$1} Page</div>",
"}\n",
"export default ${TM_DIRECTORY/.*[\\\\/](.*)[\\\\/]\\[.*\\]/${1:/pascalcase}/}Page;"
]
},
"add className in props": {
"prefix": "$cl",
"body": "className?: string;"
},
"add children in props": {
"prefix": "$ch",
"body": "children: React.ReactNode;"
},
}
```
<br>
🛠️ Extensions
===

> Cline: 純個人使用之 AI 工具可不裝
> Git Graph: 就算你有喜歡的 git 管理工具,但便於討論建議裝
<br>
📝 Coding Pattern
===
目前我們開發上,求個開發方便,可以先直覺書寫就好。
我提這幾個主要是我會這樣寫,溝通一下希望你們看得懂。
### 📑 [React Compound-Pattern](https://www.patterns.dev/react/compound-pattern)
```typescript=
import type { FC, ReactNode } from 'react';
import { createContext, useContext, useMemo, useState } from 'react';
type Context_Menu = {
isOpen: boolean;
toggle: () => void;
};
const MenuContext = createContext<Context_Menu | null>(null);
type Props_MenuContextProvider = {
children: ReactNode;
};
export const MenuContextProvider: FC<Props_MenuContextProvider> = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return (
<MenuContext.Provider
value={useMemo(() => {
return {
isOpen,
toggle,
};
}, [isOpen])}>
{children}
</MenuContext.Provider>
);
};
export const useMenu = () => {
const context = useContext(MenuContext);
if (!context) {
throw new Error('useMenu must be used within a MenuProvider');
}
return context;
};
const MenuInstance = () => {
return (
<MenuWrapper>
<MenuButton />
</MenuWrapper>
);
};
const MenuWrapper: FC<{ children: ReactNode }> = ({ children }) => {
return (
<MenuContextProvider>
<div className="menu-wrapper">{children}</div>
</MenuContextProvider>
);
};
const MenuButton: FC = () => {
const { isOpen, toggle } = useMenu();
return <button onClick={toggle}>{isOpen ? 'Close' : 'Open'}</button>;
};
```
<br/>
> React Compound-Pattern 實現完全基於 React.Context
> 然而,React.Context 雖然強大但前置動作繁瑣,推薦使用自製的 **ContextHelper**
## ContextHelper
```typescript=
export const MenuContext = createProvider({
contextName: 'MenuContext',
contextValue: (props: { foo: string; d: number }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return { isOpen, toggle }; // <== Provider
},
});
export const useMenu = MenuContext.useContext;
export const MenuProvider = MenuContext.Provider;
```
更多說明 👉 https://hackmd.io/@nilswg/rJ99I1cqC
<!-- ### 📑 [React Composition-Pattern](https://dev.to/ricardolmsilva/composition-pattern-in-react-28mj)
```typescript=
type Props_PageRoot = {
children: ReactNode;
};
const PageRoot: FC<Props_PageRoot> = ({ children }) => {
return <div>{children}</div>;
};
type Props_PageChildRoot = {
children: ReactNode;
};
const PageChildRoot: FC<Props_PageChildRoot> = ({ children }) => {
return <div>{children}</div>;
};
type Props_PageChildItem = {
children: ReactNode;
};
const PageChildItem: FC<Props_PageChildItem> = ({ children }) => {
return <div>{children}</div>;
};
```
使用範例
```typescript=
export const Page = Object.freeze({ // Object.freeze 使物件不能竄改。
Root: PageRoot,
Child: Object.freeze({
Root: PageChildRoot,
Item: PageChildItem,
}),
});
const Page_Demo = () => {
return (
<Page.Root> // 對比版本二,需要 .Root
<Page.Child.Root>
<Page.Child.Item>Item 1</Page.Child.Item>
<Page.Child.Item>Item 2</Page.Child.Item>
</Page.Child.Root>
</Page.Root>
);
};
``` -->
<br>
# 完整的 setting.json 參考範例
> 大部分我本人都已經測試過有效,新專案可直接取代掉你目前的 setting.json
> 或是,擷取你需要的部份直接貼上使用。
> 除了! 字體你要自己去下載,否則該字體配置會不合用
```json
{
"editor.fontFamily": "Monoid Nerd Font Propo", // 字體
"editor.lineHeight": 1.75,
"editor.letterSpacing": -0.5,
"editor.fontLigatures": true,
"editor.fontSize": 12.5,
"editor.tabSize": 2,
"editor.linkedEditing": true,
"workbench.activityBar.location": "top", // 固定 vscode ActivityBar 位置
"workbench.colorTheme": "Default Dark Modern",
"workbench.iconTheme": "material-icon-theme",
"workbench.editor.customLabels.patterns": {
"**/index.{ts,tsx}": "${dirname}/",
"**/route.tsx": "${dirname} Route",
"**/page.tsx": "${dirname} Page",
"**/layout.tsx": "${dirname} Layout"
},
// extension: TrailingWhitespace
"files.trimTrailingWhitespace": true,
// formatter 配置
"editor.defaultFormatter": "biomejs.biome",
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
// tailwind 配置
"tailwindCSS.classAttributes": [
"class",
"className",
"ngClass",
".*ClassName",
".*Styles",
".*Class"
],
"tailwindCSS.experimental.classRegex": [
"/*\\s?tw:\\s?\\*/\\s?[`|'|\"](.*?)[`|'|\"]",
[
"cn\\(([^)]*)\\)",
"[\"'`]([^\"'`]*).*?[\"'`]"
],
[
"cva\\(([^)]*)\\)",
"[\"'`]([^\"'`]*).*?[\"'`]"
],
[
"className:[\\[|\\(]?([^)]*)[\\]|\\)]?",
"(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"
]
],
"editor.quickSuggestions": {
"strings": "on"
},
}
```
<br>
## VSCode 的字體下載
https://www.nerdfonts.com/font-downloads
### 字體安裝教學
> 1.下載 (這款蠻多人用的)

> => 2.解壓縮 => 3.安裝

> => 4. Vscode Setting => 5. 設置 "CaskaydiaCove Nerd Font"

<br>
# 其他筆記(可略過)
<br>
### TS 工具類型
```ts
// 顯示更完整的 TS 類型
export type Prettier<T> = {
[Key in keyof T]: T[Key] extends { [key: string]: {} } ? Prettier<T[Key]> : T[Key];
} & {};
```
<br>
### Auto Import File Exclude Patterns
因為 Shadcn 的底層是用到 radix-ui 有時候,你 import 會跳出兩個重複的名稱的套件,蠻煩的。
設置後,可以排除掉,不常用的 import file 路徑,也減少犯錯的可能性。
