uikit/README.md
Mohamad Zade d839eb458d
All checks were successful
Build and Deploy Next.js + Nginx Docker Image / build-and-deploy (push) Successful in 49s
Build and Deploy Next.js + Nginx Docker Image / deploy (push) Successful in 7s
Update README.md
2026-10-04 12:39:19 +00:00

1007 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# uikit
Shared library for BI next.js projects.
---
## Install
```bash
yarn add git+https://git.d.aiengines.ir/bi/uikit.git
```
---
## Update
```bash
yarn upgrade uikit
```
اگر نیاز به پاک‌سازی cache و نصب مجدد `uikit` بود:
**Bash**
```bash
rm -rf .next
rm -rf node_modules/.vite
rm -rf node_modules/.cache
rm -rf node_modules/uikit
yarn upgrade uikit
```
**PowerShell**
```powershell
Remove-Item -Recurse -Force .next -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force node_modules/.vite -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force node_modules/.cache -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force node_modules/uikit -ErrorAction SilentlyContinue
yarn upgrade uikit
```
---
# imports
| مسیر | کاربرد | dependency اضافی |
| ------------------ | ------------------------------------- | --------------------------------------- |
| `uikit` | utility functions | - |
| `uikit/utils` | utility functions | - |
| `uikit/pagination` | pagination | Chakra + React |
| `uikit/layout` | Header, Sidebar | Chakra + React |
| `uikit/core` | API، BiProvider، Keycloak، Permission | Chakra + React Query + Axios + Keycloak + Query Params |
| `uikit/table` | DataTable | Chakra + TanStack Table + Pagination |
| `uikit/remote/ui` | Remote UI components مثل `GBadge` | Chakra + React + remote static file |
---
# utils
```js
import { toFaDigits, toEnDigits, toFaNumber } from "uikit/utils";
toFaDigits(123456);
toEnDigits("۱۲۳۴۵۶");
toFaNumber(123456);
```
or
```js
import { toFaDigits } from "uikit";
```
---
# pagination
required dependencies
```bash
yarn add @chakra-ui/react@2
yarn add @emotion/react@11
yarn add @emotion/styled@11
yarn add chakra-react-select@5
yarn add react-select@5
yarn add framer-motion@10
```
using
```jsx
import { Pagination, LightPagination, SimplePagination } from "uikit/pagination";
function Page() {
return (
<Pagination
currentPage={1}
totalCount={100}
totalPages={5}
onPageChange={(page) => console.log(page)}
/>
);
}
```
---
# Header
required dependencies:
```bash
yarn add @chakra-ui/react@2
yarn add @emotion/react@11
yarn add @emotion/styled@11
yarn add framer-motion@10
```
using:
```jsx
import { Header } from "uikit/layout";
function AppHeader() {
return (
<Header>
<Header.BrandSection>
<Header.Logo src="/logo.png" />
<Header.Brand>
<Header.Title>عنوان سامانه</Header.Title>
<Header.Description>توضیح کوتاه سامانه</Header.Description>
</Header.Brand>
</Header.BrandSection>
<Header.Bi />
</Header>
);
}
```
---
# Sidebar
required dependencies:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
yarn add @tanstack/react-query axios keycloak-js
yarn add next-query-params@^5.1.0 use-query-params@^2.2.2
```
`Sidebar` همان UI ناوبری سامانه را برای دسکتاپ و موبایل ارائه می‌دهد. در دسکتاپ به صورت نوار آیکون کناری و در موبایل به صورت bottom navigation + drawer «بیشتر» نمایش داده می‌شود.
آیتم‌ها به صورت config به کامپوننت داده می‌شوند:
```jsx
import { SlUserFollowing } from "react-icons/sl";
const navigationItems = [
{
title: "آنالیز شبکه متعارف - فالوور/فالووینگ",
mobileTitle: "ارتباط کاربران",
icon: SlUserFollowing,
path: "/user-connections/analyze",
perm: ["analyze"],
},
];
```
نمونه استفاده در Next.js Pages Router:
```jsx
import NextLink from "next/link";
import { useRouter } from "next/router";
import { Sidebar } from "uikit/layout";
function AppSidebar() {
const router = useRouter();
return (
<Sidebar
items={navigationItems}
currentPath={router.asPath}
linkComponent={NextLink}
/>
);
}
```
`Sidebar` باید داخل `BiProvider` استفاده شود. Permission هر آیتم از `perm` توسط خود UI Kit بررسی می‌شود و خروج با Keycloak توسط خود UI Kit انجام می‌شود. سامانه مقصد نیازی به `usePermission` یا `logoutKeycloak` برای Sidebar ندارد.
کنترل دسترسی کلی `panel_access` عمداً داخل Sidebar انجام نمی‌شود تا navigation هنگام render باعث redirect و لغو route در Next.js نشود. اگر سامانه به این guard نیاز دارد، از `PanelAccessGuard` موجود در `uikit/core` در سطح Layout/App استفاده کنید.
`aliases` نیز پشتیبانی می‌شود و برای تشخیص آیتم active استفاده می‌شود:
```jsx
{
title: "آنالیز شبکه های اجتماعی خارجی",
mobileTitle: "شبکه خارجی",
icon: TbAnalyze,
path: "/posts/final_analyze",
aliases: ["/posts/analyze"],
perm: ["analyze"],
}
```
اگر `perm` آرایه باشد، همه permissionهای داخل آرایه برای فعال بودن آیتم لازم هستند. آیتم بدون `perm` محدودیت permission ندارد.
---
# Core
required dependencies:
```bash
yarn add @chakra-ui/react@^2
yarn add @emotion/react@^11
yarn add @emotion/styled@^11
yarn add framer-motion@^12
yarn add @tanstack/react-query@^5
yarn add axios@^1
yarn add keycloak-js
yarn add next-query-params@^5.1.0
yarn add use-query-params@^2.2.2
```
---
## BiProvider
```jsx
import { BiProvider } from "uikit/core";
export default function App() {
return (
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="front-client"
permissionClientId="permission-client"
>
<YourApp />
</BiProvider>
);
}
```
---
## BiProvider props
| Prop |
| ----------------------------- |
| `apiBaseUrl` |
| `keycloakClientId` |
| `permissionClientId` |
| `loading` |
| `updateChecker` |
| `updateCheckerProps` |
| `keycloakUrl` |
| `keycloakRealm` |
| `permissionUrl` |
| `keycloakEnabled` |
| `permissionEnabled` |
| `remoteUiEnabled` |
| `remoteUiUrl` |
| `remoteUiChakraProviderProps` |
---
## BiProvider defaults
```js
keycloakUrl = "https://auth.ibagher.ir";
keycloakRealm = "bi";
permissionUrl = "https://api.d.aiengines.ir/user_api/v1/permissions/list";
updateChecker = false;
remoteUiEnabled = true;
remoteUiUrl = "https://uikit.d.aiengines.ir/GBadge.js";
```
---
## BiProvider with Remote UI
`BiProvider` به صورت خودکار dependencyهای لازم برای کامپوننت‌های `remote/ui` را setup می‌کند.
پس اگر سامانه مقصد از `BiProvider` استفاده می‌کند، برای استفاده از `GBadge` کار اضافه‌ای لازم نیست.
```jsx
import { BiProvider } from "uikit/core";
import { GBadge } from "uikit/remote/ui";
export default function App() {
return (
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="front-client"
permissionClientId="permission-client"
>
<GBadge colorScheme="blue" py={1} px={2} borderRadius="full">
فعال
</GBadge>
</BiProvider>
);
}
```
در این حالت `GBadge` همچنان از Chakra استفاده می‌کند، ولی فایل remote آن، یعنی `GBadge.js`، خودش Chakra و React و font را داخل bundle نمی‌آورد.
dependencyهای لازم از خود سامانه مقصد گرفته می‌شوند.
---
## Change Remote UI URL
اگر فایل remote روی آدرس دیگری deploy شده باشد:
```jsx
import { BiProvider } from "uikit/core";
export default function App() {
return (
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="front-client"
permissionClientId="permission-client"
remoteUiUrl="https://static.example.com/uikit/GBadge.js"
>
<YourApp />
</BiProvider>
);
}
```
---
## Disable Remote UI setup in BiProvider
اگر نمی‌خواهید `BiProvider` به صورت خودکار remote ui را setup کند:
```jsx
import { BiProvider } from "uikit/core";
export default function App() {
return (
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="front-client"
permissionClientId="permission-client"
remoteUiEnabled={false}
>
<YourApp />
</BiProvider>
);
}
```
---
To enable `updateChecker` you should have a script `write-version.js` in the root of the project and in the `package.json` of the target project, run before the build:
```json
{
"scripts": {
"prebuild": "node write-version.js",
"build": "next build"
}
}
```
---
# API
```js
import { api, fetcher, configureApi, getApi } from "uikit/core";
```
```js
import { api } from "uikit/core";
const response = await api.get("/users");
```
or
```js
import { fetcher } from "uikit/core";
const data = await fetcher("/users");
```
---
# createApi
```js
import { createApi } from "uikit/core";
export const sampleApi = createApi({
key: "sample",
url: "/...",
actions: {
create: {
method: "post",
url: "/...",
},
update: {
method: "patch",
url: (id) => `/.../${id}`,
},
delete: {
method: "delete",
url: (id) => `/.../${id}`,
},
confirm: {
method: "post",
url: "/...",
},
updateImage: {
method: "post",
url: (id) => `/.../${id}/image`,
body: ({ formData }) => formData,
},
},
});
```
---
# Keycloak
```js
import {
getKeycloakToken,
loginKeycloak,
logoutKeycloak,
} from "uikit/core";
const token = getKeycloakToken();
loginKeycloak();
logoutKeycloak({
redirectUri: window.location.origin,
});
```
---
# Permission
```jsx
import { Can, usePermission } from "uikit/core";
function Page() {
const { can, cannot, permissions } = usePermission();
return (
<>
{can("users:list") && <div>لیست کاربران</div>}
<Can perm="users:create" fallback={null}>
<button>ایجاد کاربر</button>
</Can>
</>
);
}
```
---
# Remote UI
`remote/ui` برای کامپوننت‌هایی است که باید بدون build مجدد سامانه مقصد، از طریق فایل remote به‌روزرسانی شوند.
مثلاً `GBadge` از این مسیر استفاده می‌شود:
```jsx
import { GBadge } from "uikit/remote/ui";
function Page() {
return (
<GBadge colorScheme="blue" py={1} px={2} borderRadius="full">
فعال
</GBadge>
);
}
```
---
## GBadge
required dependencies:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
```
using:
```jsx
import { GBadge } from "uikit/remote/ui";
function Page({ item }) {
return (
<GBadge colorScheme="blue" py={1} px={2} borderRadius="full">
{item}
</GBadge>
);
}
```
`GBadge` از Chakra `Badge` استفاده می‌کند.
اما برای کم شدن حجم فایل remote، این موارد داخل `GBadge.js` bundle نمی‌شوند:
```txt
React
ReactDOM
Chakra
Emotion
theme
Fonts
font files
```
این موارد باید در سامانه مقصد وجود داشته باشند.
---
## GBadge with BiProvider
اگر سامانه مقصد از `BiProvider` استفاده می‌کند، نیازی به setup دستی نیست:
```jsx
import { BiProvider } from "uikit/core";
import { GBadge } from "uikit/remote/ui";
function App() {
return (
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="front-client"
permissionClientId="permission-client"
>
<GBadge colorScheme="blue" py={1} px={2} borderRadius="full">
فعال
</GBadge>
</BiProvider>
);
}
```
---
## GBadge without BiProvider
اگر سامانه مقصد از `BiProvider` استفاده نمی‌کند، دو حالت وجود دارد.
### حالت ساده
اگر theme خاصی ندارید، فقط استفاده از `GBadge` کافی است:
```jsx
import { GBadge } from "uikit/remote/ui";
function Page() {
return (
<GBadge colorScheme="blue" py={1} px={2} borderRadius="full">
فعال
</GBadge>
);
}
```
در این حالت wrapper مربوط به `GBadge` خودش dependencyهای لازم را setup می‌کند.
---
### حالت با theme اختصاصی
اگر سامانه مقصد theme اختصاصی Chakra دارد، بهتر است در entry اصلی پروژه یک بار setup انجام شود.
مثلاً در `main.jsx`، `App.jsx` یا `_app.jsx`:
```jsx
import React from "react";
import { createRoot } from "react-dom/client";
import { ChakraProvider } from "@chakra-ui/react";
import { setupUikitRemoteDeps } from "uikit/remote/ui";
import { theme } from "./theme";
import App from "./App";
setupUikitRemoteDeps({
theme,
});
createRoot(document.getElementById("root")).render(
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>,
);
```
برای Next.js:
```jsx
import { ChakraProvider } from "@chakra-ui/react";
import { setupUikitRemoteDeps } from "uikit/remote/ui";
import { theme } from "@/theme";
setupUikitRemoteDeps({
theme,
});
export default function App({ Component, pageProps }) {
return (
<ChakraProvider theme={theme}>
<Component {...pageProps} />
</ChakraProvider>
);
}
```
---
## Remote URL without BiProvider
اگر سامانه مقصد از `BiProvider` استفاده نمی‌کند و آدرس فایل remote متفاوت است:
```jsx
import { setupUikitRemoteDeps } from "uikit/remote/ui";
import { theme } from "./theme";
setupUikitRemoteDeps({
theme,
remoteUrl: "https://static.example.com/uikit/GBadge.js",
});
```
یا مستقیم روی خود کامپوننت:
```jsx
import { GBadge } from "uikit/remote/ui";
function Page() {
return (
<GBadge
remoteUrl="https://static.example.com/uikit/GBadge.js"
colorScheme="blue"
py={1}
px={2}
borderRadius="full"
>
فعال
</GBadge>
);
}
```
---
## Font
فونت داخل فایل remote باندل نمی‌شود.
اگر سامانه مقصد از `BiProvider` استفاده می‌کند، فونت و theme اصلی از همان `BiProvider` می‌آید.
اگر سامانه مقصد `BiProvider` ندارد، فونت را در خود سامانه مقصد ست کنید:
```css
@font-face {
font-family: Estedad;
src: url("/fonts/estedad.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
:root {
--uikit-font-family: Estedad, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
body {
font-family: var(--uikit-font-family);
}
```
`GBadge` از این مقدار استفاده می‌کند:
```css
var(--uikit-font-family, inherit)
```
---
## Build Remote UI
برای ساخت فایل remote:
```bash
yarn build:remote
```
خروجی remote:
```txt
dist/remote/GBadge.js
```
این فایل باید روی static server یا CDN داخلی deploy شود.
مثلاً:
```txt
https://uikit.d.aiengines.ir/GBadge.js
```
---
## Deploy Remote UI
بعد از تغییر در `GBadge.remote.jsx` یا کامپوننت‌های remote:
```bash
yarn build:remote
```
سپس فایل زیر را deploy کنید:
```txt
dist/remote/GBadge.js
```
سامانه‌های مقصد بعد از reload صفحه، نسخه جدید remote را دریافت می‌کنند.
اگر cache سمت browser یا CDN فعال است، باید cache فایل remote کنترل شود.
---
## Important Notes
اگر از مسیر زیر استفاده شود:
```js
import { GBadge } from "uikit/remote/ui";
```
خود کامپوننت از فایل remote استفاده می‌کند.
اما اگر کامپوننتی از مسیر معمولی package import شود، مثل:
```js
import { SomeComponent } from "uikit";
```
یا:
```js
import { SomeComponent } from "uikit/table";
```
آن کامپوننت داخل build سامانه مقصد bundle می‌شود و برای آپدیت شدن نیاز به rebuild یا `yarn upgrade` دارد.
پس:
```txt
uikit/remote/ui => update by remote file
uikit/table => update by package upgrade
uikit/core => update by package upgrade
uikit/layout => update by package upgrade
```
---
# DataTable
required dependencies:
```bash
yarn add @tanstack/react-table@^8
```
using:
```jsx
import { DataTable } from "uikit/table";
function UsersTable({ data, columns }) {
return (
<DataTable
data={data}
columns={columns}
page={1}
size={20}
pagination={{
totalCount: 100,
totalPages: 5,
}}
onPageChange={(page) => console.log(page)}
getRowId={(row) => String(row.id)}
enableSelection
showIndex
>
<DataTable.Topbar>
<DataTable.Title>لیست کاربران</DataTable.Title>
<DataTable.Spacer />
<DataTable.Actions>
<DataTable.SelectAll />
</DataTable.Actions>
</DataTable.Topbar>
<DataTable.Content>
<DataTable.Table />
<DataTable.Pagination />
</DataTable.Content>
</DataTable>
);
}
```
## Sidebar.Exit
`Sidebar` به‌صورت پیش‌فرض گزینه خروج را نمایش نمی‌دهد. برای فعال‌کردن خروج Keycloak داخلی UI Kit، آن را به‌صورت compound component اضافه کنید:
```jsx
<Sidebar
items={items}
currentPath={router.asPath}
linkComponent={NextLink}
>
<Sidebar.Exit />
</Sidebar>
```
عنوان و آیکون خروج قابل تغییر هستند:
```jsx
<Sidebar.Exit title="خروج از سامانه" icon={MyExitIcon} />
```
`Sidebar.Exit` از `logoutKeycloak` داخلی UI Kit استفاده می‌کند و callback خروج از سامانه مقصد نیاز ندارد.
### Sidebar active route notes
`Sidebar` compares `currentPath` with each item's `path` and `aliases`, ignoring query strings, hashes, and trailing slashes. Use the actual application route as `path`; use `aliases` only for alternate routes that should keep the same item active.
Active sidebar items are rendered as non-links to avoid redundant same-route navigations. This is especially useful on pages that synchronize URL query params.
---
# Remote AppHeader (self-contained)
`AppHeader` یک remote مستقل است و برای فایل remote خودش از `setupUikitRemoteDeps` یا
`window.__UIKIT_REMOTE_DEPS__` استفاده نمی‌کند.
```jsx
import { AppHeader } from "uikit/remote/ui";
function ExampleAppHeader() {
return (
<AppHeader bg="#314E89">
<AppHeader.BrandSection>
<AppHeader.Logo src="/logo.png" />
<AppHeader.Brand>
<AppHeader.Title>عنوان سامانه</AppHeader.Title>
<AppHeader.Description>توضیحات سامانه</AppHeader.Description>
</AppHeader.Brand>
</AppHeader.BrandSection>
<AppHeader.Extra
src="/secondary-logo.png"
text="متن دلخواه"
/>
<AppHeader.Bi />
</AppHeader>
);
}
```
`AppHeader` در flow عادی صفحه باقی می‌ماند و عرض کامل می‌گیرد؛ بنابراین اگر داخل `VStack`/`Stack` قرار بگیرد، محتوای بعدی زیر Header قرار نمی‌گیرد.
`bg` اختیاری است و رنگ پس‌زمینه پیش‌فرض Header را override می‌کند. `AppHeader.Extra` نیز اختیاری است و لوگو + متن دلخواه می‌گیرد. `AppHeader.Extra` و `AppHeader.Bi` مستقل از هم هستند و می‌توان هرکدام را حذف کرد. اگر هر دو وجود داشته باشند، در انتهای Header ترتیب نمایشی `Extra` سپس `Bi` است.
فایل remote از این آدرس بارگذاری می‌شود:
```text
https://uikit.d.aiengines.ir/AppHeader.js
```
برای `AppHeader.js` هیچ dependency از host تزریق نمی‌شود. خود remote به صورت Web Component
ایزوله با Shadow DOM اجرا می‌شود. تنها `font-family` از سامانه مقصد inherit می‌شود تا AppHeader
از همان فونت برنامه استفاده کند؛ سایر استایل‌ها همچنان ایزوله هستند. مسیر React wrapper فقط
`react` خود برنامه را برای mount کردن custom element استفاده می‌کند و هیچ وابستگی به Chakra ندارد.
## AppHeader local preview
برای دیدن `remote/ui/AppHeader` قبل از انتشار:
```bash
yarn install
yarn dev
```
سپس آدرس Vite که در ترمینال نمایش داده می‌شود (معمولاً `http://localhost:5173`) را باز کنید. در حالت dev، همان سورس `src/remote/ui/AppHeader/AppHeader.remote.jsx` به‌صورت remote لوکال لود می‌شود.
## Remote AppSidebar
`AppSidebar` is an isolated desktop sidebar. Its visual layer lives inside Shadow DOM and does not depend on Chakra, Tailwind, router, permission or authentication packages from the host. The host supplies the menu data and application logic.
Prefer the dedicated entry so projects that only need `AppSidebar` do not load the Chakra-oriented remote setup used by older components:
```jsx
import { AppSidebar } from "uikit/remote/ui/AppSidebar";
<AppSidebar
items={[
{
id: "home",
title: "عنوان آیتم",
mobileTitle: "عنوان کوتاه",
icon: <YourSvgIcon />,
active: true,
onClick: () => navigate("/home"),
},
]}
>
<AppSidebar.Exit onClick={logout} />
</AppSidebar>
```
Permission and routing decisions belong to the host. Filter inaccessible items before passing them, or pass `disabled: true` when the disabled state should remain visible.
متن `AppSidebar.Exit` داخل خود UI Kit ثابت و همیشه «خروج» است؛ Host فقط callback و در صورت نیاز `disabled` را پاس می‌دهد.
### AppSidebar responsive behavior
On widths below the desktop breakpoint (`62em`), `AppSidebar` renders a fixed bottom navigation bar:
- Up to 4 visible items: all items are shown directly; when `AppSidebar.Exit` is enabled, exit is the final direct item and there is no **More** button.
- More than 4 visible items: the first 4 items stay in the bottom bar; **More** is added as the fifth slot; item 5 onward plus `AppSidebar.Exit` are rendered in the bottom drawer.
- `hidden` items are excluded before this count is calculated.
The host remains responsible for route, permission and logout logic.
## Pagination
`remote/ui/Pagination` چهار حالت نمایشی دارد و قرارداد props در همه آن‌ها یکسان است:
```txt
full
compact
simple
minimal
```
حالت `minimal` فقط نحوه نمایش را ساده‌تر می‌کند. همه variantها API یکسان دارند و `loading` نیز یک prop اختیاری است:
```jsx
import { Pagination } from "uikit/remote/ui";
<Pagination
level="minimal"
currentPage={page}
totalPages={totalPages}
totalCount={totalCount}
pageSize={10}
loading={isLoading}
onPageChange={setPage}
/>
```
اگر `loading` پاس داده نشود، رفتار Pagination مانند قبل است.
- `loading=true` و `totalPages`/`totalCount` هنوز در دسترس نیستند: صفحه جاری `1` است و جای مقادیر نامشخص loader نمایش داده می‌شود.
- `loading=true` و totals قبلاً موجودند: totals ثابت می‌مانند و فقط صفحه جاری تا رسیدن پاسخ جدید loader نمایش می‌دهد.
- در جابه‌جایی بین صفحات، اگر loader داخل دکمه صفحه جاری نمایش داده شود، spinner سفید است تا روی پس‌زمینه active خوانا بماند.
## Deployed remote demo
The production build now also creates a static preview at `dist/remote/index.html`.
Because the Docker server already serves `dist/remote` as its document root, opening
`https://uikit.d.aiengines.ir/` shows the UI Kit preview instead of a directory listing.
The remote module URLs stay unchanged, for example `/AppHeader.js`, `/AppSidebar.js`,
`/Pagination.js`, and `/GBadge.js`.
### AppHeader.Extra بدون متن
اگر `text` پاس داده نشود، فقط لوگو نمایش داده می‌شود و تصویر تمام ارتفاع قابل‌استفاده‌ی `AppHeader.Extra` را می‌گیرد.