uikit
Shared library for BI next.js projects.
Install
yarn add git+https://git.d.aiengines.ir/bi/uikit.git
Update
yarn upgrade uikit
or
rm -rf .next
rm -rf node_modules/.vite
rm -rf node_modules/.cache
rm -rf node_modules/uikit
yarn upgrade uikit
imports
| مسیر | کاربرد | dependency اضافی |
|---|---|---|
uikit |
utility functions | - |
uikit/utils |
utility functions | - |
uikit/pagination |
pagination | Chakra + React + react-icons |
uikit/layout |
Header, Sidebar | Chakra + React + React Icons |
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
import { toFaDigits, toEnDigits, toFaNumber } from "uikit/utils";
toFaDigits(123456);
toEnDigits("۱۲۳۴۵۶");
toFaNumber(123456);
or
import { toFaDigits } from "uikit";
pagination
required dependencies
yarn add @chakra-ui/react@2
yarn add @chakra-ui/icons@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
yarn add react-icons
using
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:
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
using:
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:
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons
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 به کامپوننت داده میشوند:
import { SlUserFollowing } from "react-icons/sl";
const navigationItems = [
{
title: "آنالیز شبکه متعارف - فالوور/فالووینگ",
mobileTitle: "ارتباط کاربران",
icon: SlUserFollowing,
path: "/user-connections/analyze",
perm: ["analyze"],
},
];
نمونه استفاده در Next.js Pages Router:
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 استفاده میشود:
{
title: "آنالیز شبکه های اجتماعی خارجی",
mobileTitle: "شبکه خارجی",
icon: TbAnalyze,
path: "/posts/final_analyze",
aliases: ["/posts/analyze"],
perm: ["analyze"],
}
اگر perm آرایه باشد، همه permissionهای داخل آرایه برای فعال بودن آیتم لازم هستند. آیتم بدون perm محدودیت permission ندارد.
Core
required dependencies:
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
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
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 کار اضافهای لازم نیست.
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 شده باشد:
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 کند:
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:
{
"scripts": {
"prebuild": "node write-version.js",
"build": "next build"
}
}
API
import { api, fetcher, configureApi, getApi } from "uikit/core";
import { api } from "uikit/core";
const response = await api.get("/users");
or
import { fetcher } from "uikit/core";
const data = await fetcher("/users");
createApi
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
import {
getKeycloakToken,
loginKeycloak,
logoutKeycloak,
} from "uikit/core";
const token = getKeycloakToken();
loginKeycloak();
logoutKeycloak({
redirectUri: window.location.origin,
});
Permission
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 از این مسیر استفاده میشود:
import { GBadge } from "uikit/remote/ui";
function Page() {
return (
<GBadge colorScheme="blue" py={1} px={2} borderRadius="full">
فعال
</GBadge>
);
}
GBadge
required dependencies:
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
using:
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 نمیشوند:
React
ReactDOM
Chakra
Emotion
theme
Fonts
font files
این موارد باید در سامانه مقصد وجود داشته باشند.
GBadge with BiProvider
اگر سامانه مقصد از BiProvider استفاده میکند، نیازی به setup دستی نیست:
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 کافی است:
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:
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:
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 متفاوت است:
import { setupUikitRemoteDeps } from "uikit/remote/ui";
import { theme } from "./theme";
setupUikitRemoteDeps({
theme,
remoteUrl: "https://static.example.com/uikit/GBadge.js",
});
یا مستقیم روی خود کامپوننت:
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 ندارد، فونت را در خود سامانه مقصد ست کنید:
@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 از این مقدار استفاده میکند:
var(--uikit-font-family, inherit)
Build Remote UI
برای ساخت فایل remote:
yarn build:remote
خروجی remote:
dist/remote/GBadge.js
این فایل باید روی static server یا CDN داخلی deploy شود.
مثلاً:
https://uikit.d.aiengines.ir/GBadge.js
Deploy Remote UI
بعد از تغییر در GBadge.remote.jsx یا کامپوننتهای remote:
yarn build:remote
سپس فایل زیر را deploy کنید:
dist/remote/GBadge.js
سامانههای مقصد بعد از reload صفحه، نسخه جدید remote را دریافت میکنند.
اگر cache سمت browser یا CDN فعال است، باید cache فایل remote کنترل شود.
Important Notes
اگر از مسیر زیر استفاده شود:
import { GBadge } from "uikit/remote/ui";
خود کامپوننت از فایل remote استفاده میکند.
اما اگر کامپوننتی از مسیر معمولی package import شود، مثل:
import { SomeComponent } from "uikit";
یا:
import { SomeComponent } from "uikit/table";
آن کامپوننت داخل build سامانه مقصد bundle میشود و برای آپدیت شدن نیاز به rebuild یا yarn upgrade دارد.
پس:
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:
yarn add @tanstack/react-table
using:
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 اضافه کنید:
<Sidebar
items={items}
currentPath={router.asPath}
linkComponent={NextLink}
>
<Sidebar.Exit />
</Sidebar>
عنوان و آیکون خروج قابل تغییر هستند:
<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.