docs: create docs workspace (#5174)

* docs: migrate ui docs to docs universe

* created yarn workspace

* added eslint and tsconfig configurations

* fix eslint configurations

* fixed eslint configurations

* shared tailwind configurations

* added shared ui package

* added more shared components

* migrating more components

* made details components shared

* move InlineCode component

* moved InputText

* moved Loading component

* Moved Modal component

* moved Select components

* Moved Tooltip component

* moved Search components

* moved ColorMode provider

* Moved Notification components and providers

* used icons package

* use UI colors in api-reference

* moved Navbar component

* used Navbar and Search in UI docs

* added Feedback to UI docs

* general enhancements

* fix color mode

* added copy colors file from ui-preset

* added features and enhancements to UI docs

* move Sidebar component and provider

* general fixes and preparations for deployment

* update docusaurus version

* adjusted versions

* fix output directory

* remove rootDirectory property

* fix yarn.lock

* moved code component

* added vale for all docs MD and MDX

* fix tests

* fix vale error

* fix deployment errors

* change ignore commands

* add output directory

* fix docs test

* general fixes

* content fixes

* fix announcement script

* added changeset

* fix vale checks

* added nofilter option

* fix vale error
This commit is contained in:
Shahed Nasser
2023-09-21 20:57:15 +03:00
committed by GitHub
parent 19c5d5ba36
commit fa7c94b4cc
3209 changed files with 32188 additions and 31018 deletions
@@ -0,0 +1,112 @@
"use client"
import React, { createContext, useContext, useEffect, useState } from "react"
import { Analytics, AnalyticsBrowser } from "@segment/analytics-next"
export type ExtraData = {
section?: string
[key: string]: any
}
export type AnalyticsContextType = {
loaded: boolean
analytics: Analytics | null
track: (
event: string,
options?: Record<string, any>,
callback?: () => void
) => void
}
const AnalyticsContext = createContext<AnalyticsContextType | null>(null)
export type AnalyticsProviderProps = {
writeKey?: string
children?: React.ReactNode
}
const LOCAL_STORAGE_KEY = "ajs_anonymous_id"
export const AnalyticsProvider = ({
writeKey = "temp",
children,
}: AnalyticsProviderProps) => {
// loaded is used to ensure that a connection has been made to segment
// even if it failed. This is to ensure that the connection isn't
// continuously retried
const [loaded, setLoaded] = useState<boolean>(false)
const [analytics, setAnalytics] = useState<Analytics | null>(null)
const analyticsBrowser = new AnalyticsBrowser()
const init = () => {
if (!loaded) {
analyticsBrowser
.load(
{ writeKey },
{
initialPageview: true,
user: {
localStorage: {
key: LOCAL_STORAGE_KEY,
},
},
}
)
.then((instance) => {
setAnalytics(instance[0])
})
.catch((e) =>
console.error(`Could not connect to Segment. Error: ${e}`)
)
.finally(() => setLoaded(true))
}
}
const track = async (
event: string,
options?: Record<string, any>,
callback?: () => void
) => {
if (analytics) {
void analytics.track(
event,
{
...options,
uuid: analytics.user().anonymousId(),
},
callback
)
} else if (callback) {
console.warn(
"Segment is either not installed or not configured. Simulating success..."
)
callback()
}
}
useEffect(() => {
init()
}, [])
return (
<AnalyticsContext.Provider
value={{
analytics,
track,
loaded,
}}
>
{children}
</AnalyticsContext.Provider>
)
}
export const useAnalytics = () => {
const context = useContext(AnalyticsContext)
if (!context) {
throw new Error("useAnalytics must be used within a AnalyticsProvider")
}
return context
}
@@ -0,0 +1,74 @@
"use client"
import React, { createContext, useContext, useEffect, useState } from "react"
export type ColorMode = "light" | "dark"
export type ColorModeContextType = {
colorMode: ColorMode
setColorMode: (value: ColorMode) => void
toggleColorMode: () => void
}
const ColorModeContext = createContext<ColorModeContextType | null>(null)
export type ColorModeProviderProps = {
children: React.ReactNode
}
export const ColorModeProvider = ({ children }: ColorModeProviderProps) => {
const [colorMode, setColorMode] = useState<ColorMode>("light")
const [loaded, setLoaded] = useState(false)
const toggleColorMode = () =>
setColorMode(colorMode === "light" ? "dark" : "light")
useEffect(() => {
if (loaded) {
return
}
const theme = localStorage.getItem("theme")
if (theme && (theme === "light" || theme === "dark")) {
setColorMode(theme)
setLoaded(true)
}
}, [])
useEffect(() => {
document.querySelector("html")?.setAttribute("data-theme", colorMode)
}, [colorMode])
useEffect(() => {
if (!loaded) {
return
}
const theme = localStorage.getItem("theme")
if (theme !== colorMode) {
localStorage.setItem("theme", colorMode)
}
}, [loaded, colorMode])
return (
<ColorModeContext.Provider
value={{
colorMode,
setColorMode,
toggleColorMode,
}}
>
{children}
</ColorModeContext.Provider>
)
}
export const useColorMode = (): ColorModeContextType => {
const context = useContext(ColorModeContext)
if (!context) {
throw new Error("useColorMode must be used inside a ColorModeProvider")
}
return context
}
@@ -0,0 +1,63 @@
"use client"
import React, {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react"
export type MobileContextType = {
isMobile?: boolean
}
const MobileContext = createContext<MobileContextType | null>(null)
export type MobileProviderProps = {
children: React.ReactNode
}
export const MobileProvider = ({ children }: MobileProviderProps) => {
const [isMobile, setIsMobile] = useState(false)
const handleResize = useCallback(() => {
if (window.innerWidth < 1025 && !isMobile) {
setIsMobile(true)
} else if (window.innerWidth >= 1025 && isMobile) {
setIsMobile(false)
}
}, [isMobile])
useEffect(() => {
window.addEventListener("resize", handleResize)
return () => {
window.removeEventListener("resize", handleResize)
}
}, [handleResize])
useEffect(() => {
handleResize()
}, [])
return (
<MobileContext.Provider
value={{
isMobile,
}}
>
{children}
</MobileContext.Provider>
)
}
export const useMobile = () => {
const context = useContext(MobileContext)
if (!context) {
throw new Error("useMobile must be used within a MobileProvider")
}
return context
}
@@ -0,0 +1,53 @@
"use client"
import React, { useContext, useState } from "react"
import { createContext } from "react"
import { Modal, type ModalProps } from "@/components"
export type ModalContextType = {
modalProps: ModalProps | null
setModalProps: (value: ModalProps | null) => void
closeModal: () => void
}
const ModalContext = createContext<ModalContextType | null>(null)
export type ModalProviderProps = {
children?: React.ReactNode
}
export const ModalProvider = ({ children }: ModalProviderProps) => {
const [modalProps, setModalProps] = useState<ModalProps | null>(null)
const closeModal = () => {
setModalProps(null)
}
return (
<ModalContext.Provider
value={{
modalProps,
setModalProps,
closeModal,
}}
>
{children}
{modalProps && (
<>
<div className="bg-medusa-bg-overlay dark:bg-medusa-bg-overlay-dark fixed top-0 left-0 z-[499] h-screen w-screen"></div>
<Modal {...modalProps} onClose={closeModal} />
</>
)}
</ModalContext.Provider>
)
}
export const useModal = () => {
const context = useContext(ModalContext)
if (!context) {
throw new Error("useModal must be used within a ModalProvider")
}
return context
}
@@ -0,0 +1,55 @@
"use client"
import React, { createContext, useContext, useState, useEffect } from "react"
import { usePathname } from "next/navigation"
export type NavbarContextType = {
activeItem: string | null
setActiveItem: (value: string) => void
}
const NavbarContext = createContext<NavbarContextType | null>(null)
export type NavbarProviderProps = {
children: React.ReactNode
basePath?: string
}
export const NavbarProvider = ({
children,
basePath = "",
}: NavbarProviderProps) => {
const [activeItem, setActiveItem] = useState<string | null>(null)
const pathname = usePathname()
const assemblePathName = (path: string) =>
`${basePath}/${path.charAt(0) === "/" ? path.substring(1) : path}`
useEffect(() => {
const newPath = assemblePathName(pathname)
if (activeItem !== newPath) {
setActiveItem(newPath)
}
}, [pathname, activeItem])
return (
<NavbarContext.Provider
value={{
activeItem,
setActiveItem,
}}
>
{children}
</NavbarContext.Provider>
)
}
export const useNavbar = (): NavbarContextType => {
const context = useContext(NavbarContext)
if (!context) {
throw new Error("useNavbar must be used inside a NavbarProvider")
}
return context
}
@@ -0,0 +1,135 @@
"use client"
import React, { createContext, useContext, useReducer } from "react"
import { NotificationItemProps, NotificationContainer } from "@/components"
import uuid from "react-uuid"
export type NotificationItemType = {
id?: string
} & NotificationItemProps
export type NotificationContextType = {
notifications: NotificationItemType[]
addNotification: (value: NotificationItemType) => void
generateId: () => string
removeNotification: (id: string) => void
updateNotification: (
id: string,
updatedData: Partial<Omit<NotificationItemType, "id">>
) => void
}
export enum NotificationReducerActionTypes {
ADD = "add",
REMOVE = "remove",
UPDATE = "update",
}
export type NotificationReducerAction =
| {
type: NotificationReducerActionTypes.ADD
notification: NotificationItemType
}
| {
type: NotificationReducerActionTypes.REMOVE
id: string
}
| {
type: NotificationReducerActionTypes.UPDATE
id: string
updatedData: Partial<Omit<NotificationItemType, "id">>
}
const notificationReducer = (
state: NotificationItemType[],
action: NotificationReducerAction
) => {
switch (action.type) {
case NotificationReducerActionTypes.ADD:
return [...state, action.notification]
case NotificationReducerActionTypes.REMOVE:
return state.filter((notification) => notification.id !== action.id)
case NotificationReducerActionTypes.UPDATE:
return state.map((notification) => {
if (notification.id === action.id) {
return {
...notification,
...action.updatedData,
}
}
return notification
})
default:
return state
}
}
const NotificationContext = createContext<NotificationContextType | null>(null)
export type NotificationProviderProps = {
children?: React.ReactNode
}
export const NotificationProvider = ({
children,
}: NotificationProviderProps) => {
const [notifications, dispatch] = useReducer(notificationReducer, [])
const generateId = () => uuid()
const addNotification = (notification: NotificationItemType) => {
if (!notification.id) {
notification.id = generateId()
}
dispatch({
type: NotificationReducerActionTypes.ADD,
notification,
})
}
const updateNotification = (
id: string,
updatedData: Partial<Omit<NotificationItemType, "id">>
) => {
dispatch({
type: NotificationReducerActionTypes.UPDATE,
id,
updatedData,
})
}
const removeNotification = (id: string) => {
dispatch({
type: NotificationReducerActionTypes.REMOVE,
id,
})
}
return (
<NotificationContext.Provider
value={{
notifications,
addNotification,
generateId,
removeNotification,
updateNotification,
}}
>
{children}
<NotificationContainer />
</NotificationContext.Provider>
)
}
export const useNotifications = (): NotificationContextType => {
const context = useContext(NotificationContext)
if (!context) {
throw new Error(
"useNotifications must be used within a NotificationProvider"
)
}
return context
}
@@ -0,0 +1,39 @@
"use client"
import React, { createContext, useContext, useState } from "react"
export type PageLoadingContextType = {
isLoading: boolean
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
}
const PageLoadingContext = createContext<PageLoadingContextType | null>(null)
export type PageLoadingProviderProps = {
children?: React.ReactNode
}
export const PageLoadingProvider = ({ children }: PageLoadingProviderProps) => {
const [isLoading, setIsLoading] = useState(true)
return (
<PageLoadingContext.Provider
value={{
isLoading,
setIsLoading,
}}
>
{children}
</PageLoadingContext.Provider>
)
}
export const usePageLoading = (): PageLoadingContextType => {
const context = useContext(PageLoadingContext)
if (!context) {
throw new Error("usePageLoading must be used inside a PageLoadingProvider")
}
return context
}
@@ -0,0 +1,108 @@
"use client"
import React, {
createContext,
useContext,
useEffect,
useState,
useMemo,
} from "react"
import { SearchModal, SearchModalProps } from "@/components"
import { checkArraySameElms } from "../../utils"
import algoliasearch, { SearchClient } from "algoliasearch/lite"
export type SearchContextType = {
isOpen: boolean
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
defaultFilters: string[]
setDefaultFilters: (value: string[]) => void
searchClient: SearchClient
}
const SearchContext = createContext<SearchContextType | null>(null)
export type AlgoliaProps = {
appId: string
apiKey: string
mainIndexName: string
indices: string[]
}
export type SearchProviderProps = {
children: React.ReactNode
initialDefaultFilters?: string[]
algolia: AlgoliaProps
searchProps: Omit<SearchModalProps, "algolia">
}
export const SearchProvider = ({
children,
initialDefaultFilters = [],
searchProps,
algolia,
}: SearchProviderProps) => {
const [isOpen, setIsOpen] = useState(false)
const [defaultFilters, setDefaultFilters] = useState<string[]>(
initialDefaultFilters
)
const searchClient: SearchClient = useMemo(() => {
const algoliaClient = algoliasearch(algolia.appId, algolia.apiKey)
return {
...algoliaClient,
async search(requests) {
if (requests.every(({ params }) => !params?.query)) {
return Promise.resolve({
results: requests.map(() => ({
hits: [],
nbHits: 0,
nbPages: 0,
page: 0,
processingTimeMS: 0,
hitsPerPage: 0,
exhaustiveNbHits: false,
query: "",
params: "",
})),
})
}
return algoliaClient.search(requests)
},
}
}, [algolia.appId, algolia.apiKey])
useEffect(() => {
if (
initialDefaultFilters.length &&
!checkArraySameElms(defaultFilters, initialDefaultFilters)
) {
setDefaultFilters(initialDefaultFilters)
}
}, [initialDefaultFilters])
return (
<SearchContext.Provider
value={{
isOpen,
setIsOpen,
defaultFilters,
setDefaultFilters,
searchClient,
}}
>
{children}
<SearchModal {...searchProps} algolia={algolia} />
</SearchContext.Provider>
)
}
export const useSearch = (): SearchContextType => {
const context = useContext(SearchContext)
if (!context) {
throw new Error("useSearch must be used inside a SearchProvider")
}
return context
}
@@ -0,0 +1,318 @@
"use client"
import React, {
ReactNode,
createContext,
useCallback,
useContext,
useEffect,
useReducer,
useState,
} from "react"
import { usePathname } from "next/navigation"
export enum SidebarItemSections {
TOP = "top",
BOTTOM = "bottom",
MOBILE = "mobile",
}
export type SidebarItemType = {
path?: string
title: string
additionalElms?: React.ReactNode
children?: SidebarItemType[]
loaded?: boolean
isPathHref?: boolean
}
export type SidebarSectionItemsType = {
[k in SidebarItemSections]: SidebarItemType[]
}
export type SidebarContextType = {
items: SidebarSectionItemsType
activePath: string | null
getActiveItem: () => SidebarItemType | undefined
setActivePath: (path: string | null) => void
isItemActive: (item: SidebarItemType, checkChildren?: boolean) => boolean
addItems: (
item: SidebarItemType[],
options?: {
section?: SidebarItemSections
parent?: {
path: string
changeLoaded?: boolean
}
indexPosition?: number
ignoreExisting?: boolean
}
) => void
findItemInSection: (
section: SidebarItemType[],
item: Partial<SidebarItemType>,
checkChildren?: boolean
) => SidebarItemType | undefined
mobileSidebarOpen: boolean
setMobileSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>
isSidebarEmpty: () => boolean
desktopSidebarOpen: boolean
setDesktopSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>
}
export const SidebarContext = createContext<SidebarContextType | null>(null)
export type ActionOptionsType = {
section?: SidebarItemSections
parent?: {
path: string
changeLoaded?: boolean
}
indexPosition?: number
ignoreExisting?: boolean
}
export type ActionType = {
type: "add" | "update"
items: SidebarItemType[]
options?: ActionOptionsType
}
export const reducer = (
state: SidebarSectionItemsType,
{ type, items, options }: ActionType
) => {
const {
section = SidebarItemSections.TOP,
parent,
indexPosition,
} = options || {}
switch (type) {
case "add":
return {
...state,
[section]:
indexPosition !== undefined
? [
...state[section].slice(0, indexPosition),
...items,
...state[section].slice(indexPosition),
]
: [...state[section], ...items],
}
case "update":
// find item index
return {
...state,
[section]: state[section].map((i) => {
if (i.path && parent?.path && i.path === parent?.path) {
return {
...i,
children: [...(i.children || []), ...items],
loaded: parent.changeLoaded ? true : i.loaded,
}
}
return i
}),
}
default:
return state
}
}
export type SidebarProviderProps = {
children?: ReactNode
isLoading?: boolean
setIsLoading?: React.Dispatch<React.SetStateAction<boolean>>
initialItems?: SidebarSectionItemsType
shouldHandleHashChange?: boolean
shouldHandlePathChange?: boolean
}
export const SidebarProvider = ({
children,
isLoading = false,
setIsLoading,
initialItems,
shouldHandleHashChange = false,
shouldHandlePathChange = false,
}: SidebarProviderProps) => {
const [items, dispatch] = useReducer(reducer, {
top: initialItems?.top || [],
bottom: initialItems?.bottom || [],
mobile: initialItems?.mobile || [],
})
const [activePath, setActivePath] = useState<string | null>("")
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<boolean>(false)
const [desktopSidebarOpen, setDesktopSidebarOpen] = useState(true)
const pathname = usePathname()
const findItemInSection = useCallback(
(
section: SidebarItemType[],
item: Partial<SidebarItemType>,
checkChildren = true
): SidebarItemType | undefined => {
return section.find((i) => {
if (!item.path) {
return !i.path && i.title === item.title
} else {
return (
i.path === item.path ||
(checkChildren && i.children && findItemInSection(i.children, item))
)
}
})
},
[]
)
const getActiveItem = useCallback(() => {
if (activePath === null) {
return undefined
}
return (
findItemInSection(items.mobile, { path: activePath }) ||
findItemInSection(items.top, { path: activePath }) ||
findItemInSection(items.bottom, { path: activePath })
)
}, [activePath, items, findItemInSection])
const addItems = (
newItems: SidebarItemType[],
options?: {
section?: SidebarItemSections
parent?: {
path: string
changeLoaded?: boolean
}
indexPosition?: number
ignoreExisting?: boolean
}
) => {
const {
section = SidebarItemSections.TOP,
parent,
ignoreExisting = false,
} = options || {}
if (!ignoreExisting) {
const selectedSection =
section === SidebarItemSections.BOTTOM ? items.bottom : items.top
newItems = newItems.filter(
(item) => !findItemInSection(selectedSection, item)
)
}
if (!newItems.length) {
return
}
dispatch({
type: parent ? "update" : "add",
items: newItems,
options,
})
}
const isItemActive = useCallback(
(item: SidebarItemType, checkChildren = false): boolean => {
return (
item.path === activePath ||
(checkChildren && activePath?.split("_")[0] === item.path)
)
},
[activePath]
)
const isSidebarEmpty = useCallback((): boolean => {
return Object.values(items).every(
(sectionItems) => sectionItems.length === 0
)
}, [items])
const init = () => {
const currentPath = location.hash.replace("#", "")
if (currentPath) {
setActivePath(currentPath)
}
}
// this is mainly triggered by Algolia
const handleHashChange = useCallback(() => {
const currentPath = location.hash.replace("#", "")
if (currentPath !== activePath) {
setActivePath(currentPath)
}
}, [activePath])
useEffect(() => {
if (!shouldHandleHashChange) {
return
}
init()
const handleScroll = () => {
if (window.scrollY === 0) {
setActivePath("")
// can't use next router as it doesn't support
// changing url without scrolling
history.replaceState({}, "", location.pathname)
}
}
window.addEventListener("scroll", handleScroll)
window.addEventListener("hashchange", handleHashChange)
return () => {
window.removeEventListener("scroll", handleScroll)
window.removeEventListener("hashchange", handleHashChange)
}
}, [handleHashChange, shouldHandleHashChange])
useEffect(() => {
if (isLoading && items.top.length && items.bottom.length) {
setIsLoading?.(false)
}
}, [items, isLoading, setIsLoading])
useEffect(() => {
if (shouldHandlePathChange && pathname !== activePath) {
setActivePath(pathname)
}
}, [shouldHandlePathChange, pathname])
return (
<SidebarContext.Provider
value={{
items,
addItems,
activePath,
setActivePath,
isItemActive,
findItemInSection,
mobileSidebarOpen,
setMobileSidebarOpen,
isSidebarEmpty,
getActiveItem,
desktopSidebarOpen,
setDesktopSidebarOpen,
}}
>
{children}
</SidebarContext.Provider>
)
}
export const useSidebar = (): SidebarContextType => {
const context = useContext(SidebarContext)
if (!context) {
throw new Error("useSidebar must be used inside a SidebarProvider")
}
return context
}
@@ -0,0 +1,9 @@
export * from "./Analytics"
export * from "./ColorMode"
export * from "./Mobile"
export * from "./Modal"
export * from "./Navbar"
export * from "./Notification"
export * from "./PageLoading"
export * from "./Search"
export * from "./Sidebar"