>
export default ReactComponent
}
+
+///
+declare module '@koupr/ui' {
+ export * from 'dist/types.d.ts'
+}
diff --git a/src/hooks/page-pagination.ts b/src/hooks/page-pagination.ts
index d6bec2c..6edcd76 100644
--- a/src/hooks/page-pagination.ts
+++ b/src/hooks/page-pagination.ts
@@ -1,49 +1,31 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
-
-export type NavigateArgs = {
- search: string
-}
-
-export type NavigateFunction = (args: NavigateArgs) => void
-
-export type LocationObject = {
- search: string
-}
-
-export type StorageOptions = {
- enabled?: boolean
- prefix?: string
- namespace?: string
-}
+import { StorageOptions } from '../types'
export type UsePagePaginationOptions = {
- navigate: NavigateFunction
- location: LocationObject
+ navigateFn?: (href: string) => void
+ searchFn: () => string
storage?: StorageOptions
steps?: number[]
}
export const usePagePagination = ({
- navigate,
- location,
+ navigateFn,
+ searchFn,
storage = {
- enabled: false,
prefix: 'app',
namespace: 'main',
},
steps = [5, 10, 20, 40, 80, 100],
}: UsePagePaginationOptions) => {
- const queryParams = useMemo(
- () => new URLSearchParams(location.search),
- [location.search],
- )
+ const search = searchFn()
+ const queryParams = useMemo(() => new URLSearchParams(search), [search])
const page = Number(queryParams.get('page')) || 1
const storageSizeKey = useMemo(
() => `${storage.prefix}_${storage.namespace}_pagination_size`,
[storage],
)
const [size, setSize] = useState(
- localStorage.getItem(storageSizeKey) && storage.enabled
+ localStorage.getItem(storageSizeKey) && storage
? parseInt(localStorage.getItem(storageSizeKey) as string)
: steps[0],
)
@@ -51,7 +33,7 @@ export const usePagePagination = ({
const _setSize = useCallback(
(size: number) => {
setSize(size)
- if (size && storage.enabled) {
+ if (size && storage) {
localStorage.setItem(storageSizeKey, JSON.stringify(size))
}
},
@@ -61,16 +43,16 @@ export const usePagePagination = ({
useEffect(() => {
if (!queryParams.has('page')) {
queryParams.set('page', '1')
- navigate({ search: `?${queryParams.toString()}` })
+ navigateFn?.(`?${queryParams.toString()}`)
}
- }, [queryParams, navigate])
+ }, [queryParams, navigateFn])
const setPage = useCallback(
(page: number) => {
queryParams.set('page', String(page))
- navigate({ search: `?${queryParams.toString()}` })
+ navigateFn?.(`?${queryParams.toString()}`)
},
- [queryParams, navigate],
+ [queryParams, navigateFn],
)
return { page, size, steps, setPage, setSize: _setSize }
diff --git a/src/react-select.ts b/src/react-select.ts
index 0f1a8d2..4377b13 100644
--- a/src/react-select.ts
+++ b/src/react-select.ts
@@ -4,6 +4,7 @@ export type ReactSelectStylesOptions = {
colorMode?: ColorMode
}
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function reactSelectStyles(options?: ReactSelectStylesOptions): any {
let bg = 'transparent'
if (options?.colorMode === 'light') {
diff --git a/src/stories/bundles/app.stories.tsx b/src/stories/bundles/app.stories.tsx
new file mode 100644
index 0000000..5f8216a
--- /dev/null
+++ b/src/stories/bundles/app.stories.tsx
@@ -0,0 +1,49 @@
+import { useEffect } from 'react'
+import { Meta, StoryObj } from '@storybook/react'
+import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
+import { Layout } from '../common/layout'
+import { List } from '../common/list'
+import { Page } from '../common/page'
+import { Settings } from '../common/settings'
+
+export const App = () => {
+ const location = useLocation()
+ const navigate = useNavigate()
+
+ useEffect(() => {
+ if (location.pathname === '/workspace') {
+ navigate('/workspace/list')
+ }
+ }, [location.pathname, navigate])
+
+ return (
+
+
+ }
+ children={
+ <>
+ } />
+ } />
+ >
+ }
+ />
+
+
+ )
+}
+
+const meta: Meta
= {
+ title: 'Bundles/App',
+ component: App,
+ parameters: {
+ layout: 'fullscreen',
+ },
+ excludeStories: /App/,
+}
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {}
diff --git a/src/stories/bundles/layout.stories.tsx b/src/stories/bundles/layout.stories.tsx
index 10f3308..12740b9 100644
--- a/src/stories/bundles/layout.stories.tsx
+++ b/src/stories/bundles/layout.stories.tsx
@@ -1,150 +1,15 @@
-import { useState } from 'react'
-import { Button, IconButton, MenuItem, useDisclosure } from '@chakra-ui/react'
-import {
- AccountMenu,
- AppBar,
- AuxiliaryDrawer,
- IconAdd,
- IconAdmin,
- IconFlag,
- IconGroup,
- IconStacks,
- IconTune,
- IconUpload,
- IconWorkspaces,
- Logo,
- NumberTag,
- SearchBar,
- Shell, // @ts-expect-error ignored
-} from '@koupr/ui'
import type { Meta, StoryObj } from '@storybook/react'
-import cx from 'classnames'
-import { Link, useLocation, useNavigate } from 'react-router-dom'
+import { Layout } from '../common/layout'
-const meta: Meta = {
+const meta: Meta = {
title: 'Bundles/Layout',
- component: Shell,
+ component: Layout,
parameters: {
layout: 'fullscreen',
},
}
export default meta
-type Story = StoryObj
+type Story = StoryObj
-export const ShellWithAppBar: Story = {
- render: () => {
- const location = useLocation()
- const navigate = useNavigate()
- const tasks = useDisclosure()
- const uploads = useDisclosure()
- const [query, setQuery] = useState('')
-
- return (
- }
- topBar={
- } aria-label="Filters" />
- }
- onSearch={setQuery}
- onClear={() => setQuery('')}
- />
- }
- buttons={
- <>
- }
- variant="solid"
- colorScheme="blue"
- >
- New Workspace
-
- } aria-label="Cloud Console" />
- }
- header="Uploads"
- body={<>>}
- isOpen={uploads.isOpen}
- onClose={uploads.onClose}
- onOpen={uploads.onOpen}
- />
- }
- header="Tasks"
- body={<>>}
- hasBadge={true}
- isOpen={tasks.isOpen}
- onOpen={tasks.onOpen}
- onClose={tasks.onClose}
- />
-
-
-
-
- >
- }
- />
- >
- }
- />
- }
- items={[
- {
- href: '/',
- icon: ,
- primaryText: 'Workspaces',
- secondaryText: 'Isolated containers for files and folders.',
- },
- {
- href: '/group',
- icon: ,
- primaryText: 'Groups',
- secondaryText: 'Allows assigning permissions to a group of users.',
- },
- {
- href: '/organizations',
- icon: ,
- primaryText: 'Organizations',
- secondaryText: 'Umbrellas for workspaces and users.',
- },
- ]}
- pathnameFn={() => location.pathname}
- navigateFn={navigate}
- >
- )
- },
-}
+export const Default: Story = {}
diff --git a/src/stories/bundles/page.stories.tsx b/src/stories/bundles/page.stories.tsx
new file mode 100644
index 0000000..fa662b8
--- /dev/null
+++ b/src/stories/bundles/page.stories.tsx
@@ -0,0 +1,35 @@
+import { useEffect } from 'react'
+import { Meta, StoryObj } from '@storybook/react'
+import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
+import { List } from '../common/list'
+import { Page } from '../common/page'
+import { Settings } from '../common/settings'
+
+const meta: Meta = {
+ title: 'Bundles/Page',
+ component: Page,
+ render: () => {
+ const location = useLocation()
+ const navigate = useNavigate()
+
+ useEffect(() => {
+ if (location.pathname === '/') {
+ navigate('/workspace/list')
+ }
+ }, [location.pathname, navigate])
+
+ return (
+
+ }>
+ } />
+ } />
+
+
+ )
+ },
+}
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {}
diff --git a/src/stories/common/data.ts b/src/stories/common/data.ts
new file mode 100644
index 0000000..06694db
--- /dev/null
+++ b/src/stories/common/data.ts
@@ -0,0 +1,33 @@
+export type Hero = {
+ name: string
+ symbol: string
+ dateOfBirth: string
+}
+
+export const items: Hero[] = [
+ {
+ name: 'Bruce Wayne',
+ symbol: 'Batman',
+ dateOfBirth: '1915-04-07',
+ },
+ {
+ name: 'Tony Stark',
+ symbol: 'Iron Man',
+ dateOfBirth: '1970-05-29',
+ },
+ {
+ name: 'Steven Rogers',
+ symbol: 'Captain America',
+ dateOfBirth: '1918-07-04',
+ },
+ {
+ name: 'Clinton Barton',
+ symbol: 'Hawkeye',
+ dateOfBirth: '1975-01-01',
+ },
+ {
+ name: 'Natasha Romanoff',
+ symbol: 'Black Widow',
+ dateOfBirth: '1984-12-03',
+ },
+]
diff --git a/src/stories/common/layout.tsx b/src/stories/common/layout.tsx
new file mode 100644
index 0000000..9e5ea0b
--- /dev/null
+++ b/src/stories/common/layout.tsx
@@ -0,0 +1,146 @@
+import { ReactElement, useEffect, useState } from 'react'
+import { Button, IconButton, MenuItem, useDisclosure } from '@chakra-ui/react'
+import {
+ AccountMenu,
+ AppBar,
+ AuxiliaryDrawer,
+ IconAdd,
+ IconAdmin,
+ IconFlag,
+ IconGroup,
+ IconStacks,
+ IconTune,
+ IconUpload,
+ IconWorkspaces,
+ Logo,
+ NumberTag,
+ SearchBar,
+ Shell,
+} from '@koupr/ui'
+import cx from 'classnames'
+import { Link, useLocation, useNavigate } from 'react-router-dom'
+
+export type LayoutProps = {
+ children?: ReactElement
+}
+
+export const Layout = ({ children }: LayoutProps) => {
+ const location = useLocation()
+ const navigate = useNavigate()
+ const tasks = useDisclosure()
+ const uploads = useDisclosure()
+ const [query, setQuery] = useState('')
+
+ useEffect(() => {
+ if (location.pathname === '/') {
+ navigate('/workspace')
+ }
+ }, [navigate])
+
+ return (
+ }
+ topBar={
+ } aria-label="Filters" />}
+ onSearch={setQuery}
+ onClear={() => setQuery('')}
+ />
+ }
+ buttons={
+ <>
+ }
+ variant="solid"
+ colorScheme="blue"
+ >
+ New Workspace
+
+ } aria-label="Cloud Console" />
+ }
+ header="Uploads"
+ body={<>>}
+ isOpen={uploads.isOpen}
+ onClose={uploads.onClose}
+ onOpen={uploads.onOpen}
+ />
+ }
+ header="Tasks"
+ body={<>>}
+ hasBadge={true}
+ isOpen={tasks.isOpen}
+ onOpen={tasks.onOpen}
+ onClose={tasks.onClose}
+ />
+
+
+
+
+ >
+ }
+ />
+ >
+ }
+ />
+ }
+ items={[
+ {
+ href: '/workspace',
+ icon: ,
+ primaryText: 'Workspaces',
+ secondaryText: 'Isolated containers for files and folders.',
+ },
+ {
+ href: '/group',
+ icon: ,
+ primaryText: 'Groups',
+ secondaryText: 'Allows assigning permissions to a group of users.',
+ },
+ {
+ href: '/organizations',
+ icon: ,
+ primaryText: 'Organizations',
+ secondaryText: 'Umbrellas for workspaces and users.',
+ },
+ ]}
+ pathnameFn={() => location.pathname}
+ navigateFn={navigate}
+ >
+ {children}
+
+ )
+}
diff --git a/src/stories/common/list.tsx b/src/stories/common/list.tsx
new file mode 100644
index 0000000..67d7b72
--- /dev/null
+++ b/src/stories/common/list.tsx
@@ -0,0 +1,86 @@
+import { Avatar, Link } from '@chakra-ui/react'
+import {
+ DataTable,
+ IconChat,
+ IconFavorite,
+ IconLogout,
+ PagePagination,
+ usePagePagination,
+} from '@koupr/ui'
+import cx from 'classnames'
+import { useLocation, useNavigate } from 'react-router-dom'
+import { items } from './data'
+
+export const List = () => {
+ const navigate = useNavigate()
+ const location = useLocation()
+ const { page, size, steps, setPage, setSize } = usePagePagination({
+ navigateFn: navigate,
+ searchFn: () => location.search,
+ })
+
+ return (
+
+
(
+
+ ),
+ },
+ {
+ title: 'Symbol',
+ renderCell: (item) => {item.symbol},
+ },
+ {
+ title: 'Date of Birth',
+ renderCell: (item) => (
+ {new Date(item.dateOfBirth).toLocaleString()}
+ ),
+ },
+ ]}
+ actions={[
+ {
+ label: 'Mark As Favorite',
+ icon: ,
+ onClick: (item) => console.log(`Marking ${item.name} as favorite!`),
+ },
+ {
+ label: 'Start Conversation',
+ icon: ,
+ isDisabled: true,
+ onClick: (item) =>
+ console.log(`Starting a conversation with ${item.name}.`),
+ },
+ {
+ label: 'Remove From Organization',
+ icon: ,
+ isDestructive: true,
+ onClick: (item) =>
+ console.log(`Removing ${item.name} from organization...`),
+ },
+ ]}
+ />
+
+
+ )
+}
diff --git a/src/stories/common/page.tsx b/src/stories/common/page.tsx
new file mode 100644
index 0000000..13faf35
--- /dev/null
+++ b/src/stories/common/page.tsx
@@ -0,0 +1,28 @@
+import { NavBar } from '@koupr/ui'
+import cx from 'classnames'
+import { Outlet, useLocation, useNavigate } from 'react-router-dom'
+
+export const Page = () => {
+ const location = useLocation()
+ const navigate = useNavigate()
+
+ return (
+
+ location.pathname}
+ />
+
+
+ )
+}
diff --git a/src/stories/common/settings.tsx b/src/stories/common/settings.tsx
new file mode 100644
index 0000000..65a2c75
--- /dev/null
+++ b/src/stories/common/settings.tsx
@@ -0,0 +1,81 @@
+import { IconButton, Switch } from '@chakra-ui/react'
+import { Form, IconDelete, IconEdit } from '@koupr/ui'
+import cx from 'classnames'
+
+export const Settings = () => (
+
),
}
diff --git a/src/stories/components/logo.stories.tsx b/src/stories/components/logo.stories.tsx
index 5066625..9e76057 100644
--- a/src/stories/components/logo.stories.tsx
+++ b/src/stories/components/logo.stories.tsx
@@ -1,4 +1,3 @@
-// @ts-expect-error ignored
import { Logo } from '@koupr/ui'
import { Meta, StoryObj } from '@storybook/react'
diff --git a/src/stories/components/nav-bar.stories.tsx b/src/stories/components/nav-bar.stories.tsx
index 6acae1e..92000d4 100644
--- a/src/stories/components/nav-bar.stories.tsx
+++ b/src/stories/components/nav-bar.stories.tsx
@@ -1,7 +1,8 @@
-// @ts-expect-error ignored
+import { useEffect } from 'react'
import { NavBar } from '@koupr/ui'
import { Meta, StoryObj } from '@storybook/react'
-import { useLocation, useNavigate } from 'react-router-dom'
+import cx from 'classnames'
+import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
const meta: Meta