nitro-web 0.0.85 → 0.0.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/globals.ts +10 -6
- package/package.json +14 -6
- package/types/{required-globals.d.ts → globals.d.ts} +3 -1
- package/.editorconfig +0 -9
- package/components/auth/auth.api.js +0 -410
- package/components/auth/reset.tsx +0 -86
- package/components/auth/signin.tsx +0 -76
- package/components/auth/signup.tsx +0 -62
- package/components/billing/stripe.api.js +0 -268
- package/components/dashboard/dashboard.tsx +0 -32
- package/components/partials/element/accordion.tsx +0 -102
- package/components/partials/element/avatar.tsx +0 -40
- package/components/partials/element/button.tsx +0 -98
- package/components/partials/element/calendar.tsx +0 -125
- package/components/partials/element/dropdown.tsx +0 -248
- package/components/partials/element/filters.tsx +0 -194
- package/components/partials/element/github-link.tsx +0 -16
- package/components/partials/element/initials.tsx +0 -66
- package/components/partials/element/message.tsx +0 -141
- package/components/partials/element/modal.tsx +0 -90
- package/components/partials/element/sidebar.tsx +0 -195
- package/components/partials/element/tooltip.tsx +0 -154
- package/components/partials/element/topbar.tsx +0 -15
- package/components/partials/form/checkbox.tsx +0 -150
- package/components/partials/form/drop-handler.tsx +0 -68
- package/components/partials/form/drop.tsx +0 -141
- package/components/partials/form/field-color.tsx +0 -86
- package/components/partials/form/field-currency.tsx +0 -158
- package/components/partials/form/field-date.tsx +0 -252
- package/components/partials/form/field.tsx +0 -231
- package/components/partials/form/form-error.tsx +0 -27
- package/components/partials/form/location.tsx +0 -225
- package/components/partials/form/select.tsx +0 -360
- package/components/partials/is-first-render.ts +0 -14
- package/components/partials/not-found.tsx +0 -7
- package/components/partials/styleguide.tsx +0 -407
- package/semver-updater.cjs +0 -13
- package/tsconfig.json +0 -38
- package/tsconfig.types.json +0 -15
- package/types/core-only-globals.d.ts +0 -9
- package/types.ts +0 -60
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// Todo: show correct message type, e.g. error, warning, info, success `${store.message.type || 'success'}`
|
|
2
|
-
import { isObject, isString, queryObject } from 'nitro-web/util'
|
|
3
|
-
import { X, CircleCheck } from 'lucide-react'
|
|
4
|
-
import { MessageObject } from 'nitro-web/types'
|
|
5
|
-
import { twMerge } from 'nitro-web'
|
|
6
|
-
|
|
7
|
-
type MessageProps = {
|
|
8
|
-
className?: string
|
|
9
|
-
classNameWrapper?: string
|
|
10
|
-
position?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Shows a message
|
|
14
|
-
* Triggered by navigating to a link with a valid query string, or by setting store.message to a string or more explicitly, to an object
|
|
15
|
-
**/
|
|
16
|
-
export function Message({ className, classNameWrapper, position='top-right' }: MessageProps) {
|
|
17
|
-
const devDontHide = false
|
|
18
|
-
const [store, setStore] = useTracked()
|
|
19
|
-
const [visible, setVisible] = useState(false)
|
|
20
|
-
const location = useLocation()
|
|
21
|
-
const messageQueryMap = {
|
|
22
|
-
'added': { type: 'success', text: 'Added successfully 👍️' },
|
|
23
|
-
'created': { type: 'success', text: 'Created successfully 👍️' },
|
|
24
|
-
'error': { type: 'error', text: 'Sorry, there was an error' },
|
|
25
|
-
'oauth-error': { type: 'error', text: 'There was an error trying to signin, please try again' },
|
|
26
|
-
'removed': { type: 'success', text: 'Removed' },
|
|
27
|
-
'signin': { type: 'error', text: 'Please sign in to access this page' },
|
|
28
|
-
'updated': { type: 'success', text: 'Updated successfully' },
|
|
29
|
-
'unauth': { type: 'error', text: 'You are unauthorised' },
|
|
30
|
-
}
|
|
31
|
-
const colorMap = {
|
|
32
|
-
'error': 'text-danger',
|
|
33
|
-
'warning': 'text-warning',
|
|
34
|
-
'info': 'text-info',
|
|
35
|
-
'success': 'text-success',
|
|
36
|
-
}
|
|
37
|
-
const positionMap = {
|
|
38
|
-
'top-left': ['sm:items-start sm:justify-start', 'sm:translate-y-0 sm:translate-x-[-0.5rem]'],
|
|
39
|
-
'top-center': ['sm:items-start sm:justify-center', 'sm:translate-y-[-0.5rem]'],
|
|
40
|
-
'top-right': ['sm:items-start sm:justify-end', 'sm:translate-y-0 sm:translate-x-1'],
|
|
41
|
-
'bottom-left': ['sm:items-end sm:justify-start', 'sm:translate-y-0 sm:translate-x-[-0.5rem]'],
|
|
42
|
-
'bottom-center': ['sm:items-end sm:justify-center', 'sm:translate-y-1'],
|
|
43
|
-
'bottom-right': ['sm:items-end sm:justify-end', 'sm:translate-y-0 sm:translate-x-1'],
|
|
44
|
-
}
|
|
45
|
-
const color = colorMap[(store.message as MessageObject)?.type || 'success']
|
|
46
|
-
const positionArr = positionMap[(position as keyof typeof positionMap)]
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
return () => {
|
|
50
|
-
setStore(s => ({ ...s, message: '' }))
|
|
51
|
-
}
|
|
52
|
-
}, [])
|
|
53
|
-
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
// Finds a message in a query string and show it
|
|
56
|
-
let message
|
|
57
|
-
const query = queryObject(location.search, true)
|
|
58
|
-
for (const key in query) {
|
|
59
|
-
if (!query.hasOwnProperty(key)) continue
|
|
60
|
-
for (const key2 in messageQueryMap) {
|
|
61
|
-
if (key != key2) continue
|
|
62
|
-
// @ts-expect-error
|
|
63
|
-
message = { ...messageQueryMap[key] }
|
|
64
|
-
if (query[key] !== true) message.text = decodeURIComponent(query[key])
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (message) setStore(s => ({ ...s, message: message }))
|
|
68
|
-
}, [location.search])
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
// Message detection and autohiding
|
|
72
|
-
const now = new Date().getTime()
|
|
73
|
-
const messageObject = store.message as MessageObject
|
|
74
|
-
|
|
75
|
-
if (!store.message) {
|
|
76
|
-
return
|
|
77
|
-
// Convert a string into a message object
|
|
78
|
-
} else if (isString(store.message)) {
|
|
79
|
-
setStore(s => ({ ...s, message: { type: 'success', text: store.message as string, date: now }}))
|
|
80
|
-
// Add a date to the message
|
|
81
|
-
} else if (!messageObject.date) {
|
|
82
|
-
setStore(s => ({ ...s, message: { ...messageObject, date: now }}))
|
|
83
|
-
// Show message and hide it again after some time. Send back cleanup if store.message changes
|
|
84
|
-
} else if (messageObject && now - 500 < messageObject.date) {
|
|
85
|
-
const timeout1 = setTimeout(() => setVisible(true), 50)
|
|
86
|
-
if (messageObject.timeout !== 0 && !devDontHide) var timeout2 = setTimeout(hide, messageObject.timeout || 5000)
|
|
87
|
-
return () => {
|
|
88
|
-
clearTimeout(timeout1)
|
|
89
|
-
clearTimeout(timeout2)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}, [JSON.stringify(store.message)])
|
|
93
|
-
|
|
94
|
-
function hide() {
|
|
95
|
-
setVisible(false)
|
|
96
|
-
setTimeout(() => setStore(s => ({ ...s, message: undefined })), 250)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<>
|
|
101
|
-
{/* Global notification live region, render this permanently at the end of the document */}
|
|
102
|
-
<div
|
|
103
|
-
aria-live="assertive"
|
|
104
|
-
className={`${twMerge(`pointer-events-none items-end justify-center fixed inset-0 flex px-4 py-6 sm:p-6 z-[101] nitro-message ${positionArr[0]} ${classNameWrapper || ''}`)}`}
|
|
105
|
-
>
|
|
106
|
-
<div className="flex flex-col items-center space-y-4">
|
|
107
|
-
{isObject(store.message) && (
|
|
108
|
-
<div className={twMerge(
|
|
109
|
-
'overflow-hidden translate-y-[0.5rem] opacity-0 pointer-events-auto max-w-[350px] rounded-md bg-white shadow-lg ring-1 ring-black/5 transition text-sm font-medium text-gray-900',
|
|
110
|
-
positionArr[1],
|
|
111
|
-
(visible ? 'translate-x-0 translate-y-0 sm:translate-x-0 sm:translate-y-0 opacity-100' : ''),
|
|
112
|
-
className
|
|
113
|
-
)}>
|
|
114
|
-
<div className="p-3">
|
|
115
|
-
<div className="flex items-start gap-3 leading-[1.4em]">
|
|
116
|
-
<div className="flex items-center shrink-0 min-h-[1.4em]">
|
|
117
|
-
<CircleCheck aria-hidden="true" size={19} className={`${color}`} />
|
|
118
|
-
</div>
|
|
119
|
-
<div className="flex flex-1 items-center min-h-[1.4em]">
|
|
120
|
-
<p>{typeof store.message === 'object' && store.message?.text}</p>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="flex items-center shrink-0 min-h-[1.4em]">
|
|
123
|
-
<button
|
|
124
|
-
type="button"
|
|
125
|
-
onClick={hide}
|
|
126
|
-
className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2
|
|
127
|
-
focus:ring-indigo-500 focus:ring-offset-2"
|
|
128
|
-
>
|
|
129
|
-
<span className="sr-only">Close</span>
|
|
130
|
-
<X aria-hidden="true" size={19} />
|
|
131
|
-
</button>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
</>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { IsFirstRender, twMerge } from 'nitro-web'
|
|
2
|
-
import SvgX1 from 'nitro-web/client/imgs/icons/x1.svg'
|
|
3
|
-
|
|
4
|
-
type ModalProps = {
|
|
5
|
-
show: boolean
|
|
6
|
-
setShow: (show: boolean) => void
|
|
7
|
-
children: React.ReactNode
|
|
8
|
-
className?: string
|
|
9
|
-
rootClassName?: string
|
|
10
|
-
dismissable?: boolean
|
|
11
|
-
maxWidth?: string
|
|
12
|
-
minHeight?: string
|
|
13
|
-
[key: string]: unknown
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Modal({ show, setShow, children, maxWidth, minHeight, dismissable = true, className, rootClassName }: ModalProps) {
|
|
17
|
-
const [state, setState] = useState(show ? 'open' : 'close')
|
|
18
|
-
const containerEl = useRef<HTMLDivElement>(null)
|
|
19
|
-
const isFirst = IsFirstRender()
|
|
20
|
-
|
|
21
|
-
const states = {
|
|
22
|
-
'close': {
|
|
23
|
-
root: 'left-[-100vw] transition-[left] duration-0 delay-200',
|
|
24
|
-
bg: 'opacity-0',
|
|
25
|
-
container: 'opacity-0 scale-[0.97]',
|
|
26
|
-
},
|
|
27
|
-
'close-now': {
|
|
28
|
-
root: '',
|
|
29
|
-
bg: '',
|
|
30
|
-
container: 'opacity-0 !transition-none',
|
|
31
|
-
},
|
|
32
|
-
'open': {
|
|
33
|
-
root: 'left-0 transition-none model-open',
|
|
34
|
-
bg: 'opacity-100 duration-200',
|
|
35
|
-
container: 'opacity-100 scale-[1] duration-200',
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
const stateObj = states[state as keyof typeof states]
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (isFirst) return
|
|
42
|
-
if (show) {
|
|
43
|
-
setState('open')
|
|
44
|
-
} else {
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
// If another modal is being opened, force close the container for a smoother transition
|
|
47
|
-
if (document.getElementsByClassName('modal-open').length > 1) {
|
|
48
|
-
setState('close-now')
|
|
49
|
-
} else {
|
|
50
|
-
setState('close')
|
|
51
|
-
}
|
|
52
|
-
}, 10)
|
|
53
|
-
}
|
|
54
|
-
// There is a bug during hot-reloading where the modal does't open if we don't ensure
|
|
55
|
-
// the same truthy/falsey type is used.
|
|
56
|
-
}, [!!show])
|
|
57
|
-
|
|
58
|
-
function onClick(e: React.MouseEvent) {
|
|
59
|
-
const clickedOnModal = containerEl.current && containerEl.current.contains(e.target as Node)
|
|
60
|
-
if (!clickedOnModal && dismissable) {
|
|
61
|
-
setShow(false)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div
|
|
67
|
-
onClick={(e) => e.stopPropagation()}
|
|
68
|
-
class={`${twMerge(`fixed top-0 w-[100vw] h-[100vh] z-[100] ${stateObj.root} ${rootClassName||''}`)} nitro-modal`}
|
|
69
|
-
>
|
|
70
|
-
<div class={`!absolute inset-0 box-content bg-gray-500/70 transition-opacity ${stateObj.bg}`}></div>
|
|
71
|
-
<div class={`relative h-[100vh] overflow-y-auto transition-[opacity,transform] ${stateObj.container}`}>
|
|
72
|
-
<div class="flex items-center justify-center min-h-full" onMouseDown={onClick}>
|
|
73
|
-
<div
|
|
74
|
-
ref={containerEl}
|
|
75
|
-
style={{ maxWidth: maxWidth || '550px', minHeight: minHeight }}
|
|
76
|
-
class={`relative w-full mx-6 mt-4 mb-8 bg-white rounded-lg shadow-lg p-9 ${className||''}`}
|
|
77
|
-
>
|
|
78
|
-
<div
|
|
79
|
-
class="absolute top-0 right-0 p-3 m-1 cursor-pointer"
|
|
80
|
-
onClick={() => { if (dismissable) { setShow(false) }}}
|
|
81
|
-
>
|
|
82
|
-
<SvgX1 />
|
|
83
|
-
</div>
|
|
84
|
-
{children}
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// Component: https://tailwindui.com/components/application-ui/application-shells/sidebar#component-a69d85b6237ea2ad506c00ef1cd39a38
|
|
2
|
-
import { css } from 'twin.macro'
|
|
3
|
-
import avatarImg from 'nitro-web/client/imgs/avatar.jpg'
|
|
4
|
-
import { injectedConfig } from 'nitro-web'
|
|
5
|
-
import {
|
|
6
|
-
Bars3Icon,
|
|
7
|
-
HomeIcon,
|
|
8
|
-
UsersIcon,
|
|
9
|
-
ArrowLeftCircleIcon,
|
|
10
|
-
PaintBrushIcon,
|
|
11
|
-
} from '@heroicons/react/24/outline'
|
|
12
|
-
import { XIcon } from 'lucide-react'
|
|
13
|
-
|
|
14
|
-
const sidebarWidth = 'w-80'
|
|
15
|
-
|
|
16
|
-
export type SidebarProps = {
|
|
17
|
-
Logo?: React.FC<{ width?: string, height?: string }>;
|
|
18
|
-
menu?: { name: string; to: string; Icon: React.FC<{ className?: string }> }[]
|
|
19
|
-
links?: { name: string; to: string; initial: string }[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function classNames(...classes: string[]) {
|
|
23
|
-
return classes.filter(Boolean).join(' ')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function Sidebar({ Logo, menu, links }: SidebarProps) {
|
|
27
|
-
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
28
|
-
return (
|
|
29
|
-
<>
|
|
30
|
-
{/* desktop sidebar */}
|
|
31
|
-
<div css={style} className={
|
|
32
|
-
'fixed inset-y-0 z-50 flex flex-col ease-in-out lg:left-0 lg:translate-x-0 lg:!delay-0 lg:!duration-0 ' +
|
|
33
|
-
(
|
|
34
|
-
sidebarOpen
|
|
35
|
-
? 'left-0 translate-x-[0px] sidebar-transition '
|
|
36
|
-
: 'left-[-100%] translate-x-[-100%] sidebar-transition-delay '
|
|
37
|
-
) +
|
|
38
|
-
sidebarWidth
|
|
39
|
-
}>
|
|
40
|
-
<div className={
|
|
41
|
-
'absolute left-full top-0 flex w-16 justify-center pt-5 lg:hidden duration-300 ease ' +
|
|
42
|
-
(sidebarOpen ? 'opacity-100' : 'opacity-0')
|
|
43
|
-
}>
|
|
44
|
-
<button type="button" onClick={() => setSidebarOpen(false)} className="-m-2.5 p-2.5">
|
|
45
|
-
<XIcon aria-hidden="true" strokeWidth={1.5} size={24} className="text-white" />
|
|
46
|
-
</button>
|
|
47
|
-
</div>
|
|
48
|
-
<SidebarContents Logo={Logo} menu={menu} links={links} />
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
{/* mobile backdrop */}
|
|
52
|
-
<div
|
|
53
|
-
css={style}
|
|
54
|
-
onClick={() => setSidebarOpen(false)}
|
|
55
|
-
className={'fixed w-full z-[49] inset-0 bg-gray-900/70 ease-linear lg:hidden ' +
|
|
56
|
-
(
|
|
57
|
-
sidebarOpen
|
|
58
|
-
? 'left-0 opacity-100 sidebar-transition '
|
|
59
|
-
: 'left-[-100%] opacity-0 sidebar-transition-delay '
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
/>
|
|
63
|
-
|
|
64
|
-
{/* mobile sidebar topbar */}
|
|
65
|
-
<div className="sticky top-0 z-40 flex items-center gap-x-6 bg-white px-4 py-4 shadow-sm sm:px-6 lg:hidden">
|
|
66
|
-
<button type="button" onClick={() => setSidebarOpen(true)} className="-m-2.5 p-2.5 text-gray-700 lg:hidden">
|
|
67
|
-
<Bars3Icon aria-hidden="true" className="size-6" />
|
|
68
|
-
</button>
|
|
69
|
-
<div className="flex-1 text-sm/6 font-semibold text-gray-900">Dashboard</div>
|
|
70
|
-
<Link to="#">
|
|
71
|
-
<img alt="" src={avatarImg} className="size-8 rounded-full bg-gray-50" />
|
|
72
|
-
</Link>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
<div class={`${sidebarWidth}`} />
|
|
76
|
-
</>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function SidebarContents ({ Logo, menu, links }: SidebarProps) {
|
|
81
|
-
const location = useLocation()
|
|
82
|
-
const [store] = useTracked()
|
|
83
|
-
const user = store.user
|
|
84
|
-
|
|
85
|
-
function isActive(path: string) {
|
|
86
|
-
if (path == '/' && location.pathname == path) return 'is-active'
|
|
87
|
-
else if (path != '/' && location.pathname.match(`^${path}`)) return 'is-active'
|
|
88
|
-
else return ''
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const _menu = menu || [
|
|
92
|
-
{ name: 'Dashboard', to: '/', Icon: HomeIcon },
|
|
93
|
-
{ name: injectedConfig.isDemo ? 'Design System' : 'Style Guide', to: '/styleguide', Icon: PaintBrushIcon },
|
|
94
|
-
{ name: 'Pricing', to: '/pricing', Icon: UsersIcon },
|
|
95
|
-
{ name: 'Signout', to: '/signout', Icon: ArrowLeftCircleIcon },
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
const _links = links || [
|
|
99
|
-
{ name: 'Nitro on Github', to: 'https://github.com/boycce/nitro-web', initial: 'G' },
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
// Sidebar component, swap this element with another sidebar if you like
|
|
103
|
-
return (
|
|
104
|
-
<div className="flex grow flex-col gap-y-8 overflow-y-auto bg-white py-5 px-10 lg:border-r lg:border-gray-200">
|
|
105
|
-
{Logo && (
|
|
106
|
-
<div className="flex h-16 shrink-0 items-center gap-2 justify-bedtween">
|
|
107
|
-
<Link to="/">
|
|
108
|
-
<Logo width="70" height={undefined} />
|
|
109
|
-
</Link>
|
|
110
|
-
<span className="text-[9px] text-gray-900 font-semibold mt-4">{injectedConfig.version}</span>
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
<nav className="flex flex-1 flex-col">
|
|
114
|
-
<ul role="list" className="flex flex-1 flex-col gap-y-7">
|
|
115
|
-
<li>
|
|
116
|
-
<ul role="list" className="-mx-2 space-y-1">
|
|
117
|
-
{_menu.map((item) => (
|
|
118
|
-
<li key={item.name}>
|
|
119
|
-
<Link
|
|
120
|
-
to={item.to}
|
|
121
|
-
className={classNames(
|
|
122
|
-
isActive(item.to)
|
|
123
|
-
? 'bg-gray-50 text-indigo-600'
|
|
124
|
-
: 'text-gray-700 hover:bg-gray-50 hover:text-indigo-600',
|
|
125
|
-
'group flex gap-x-3 items-center rounded-md p-2 text-md/6 font-semibold'
|
|
126
|
-
)}
|
|
127
|
-
>
|
|
128
|
-
{ item.Icon &&
|
|
129
|
-
<item.Icon
|
|
130
|
-
className={classNames(
|
|
131
|
-
isActive(item.to) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-600',
|
|
132
|
-
'size-5 shrink-0'
|
|
133
|
-
)}
|
|
134
|
-
/>
|
|
135
|
-
}
|
|
136
|
-
{item.name}
|
|
137
|
-
</Link>
|
|
138
|
-
</li>
|
|
139
|
-
))}
|
|
140
|
-
</ul>
|
|
141
|
-
</li>
|
|
142
|
-
<li>
|
|
143
|
-
<div className="text-xs/6 font-semibold text-gray-400">Other Links</div>
|
|
144
|
-
<ul role="list" className="-mx-2 mt-2 space-y-1">
|
|
145
|
-
{_links.map((team) => (
|
|
146
|
-
<li key={team.name}>
|
|
147
|
-
<Link
|
|
148
|
-
to={team.to}
|
|
149
|
-
className={classNames(
|
|
150
|
-
isActive(team.to)
|
|
151
|
-
? 'bg-gray-50 text-indigo-600'
|
|
152
|
-
: 'text-gray-700 hover:bg-gray-50 hover:text-indigo-600',
|
|
153
|
-
'group flex gap-x-3 rounded-md p-2 text-md/6 font-semibold'
|
|
154
|
-
)}
|
|
155
|
-
>
|
|
156
|
-
<span
|
|
157
|
-
className={classNames(
|
|
158
|
-
isActive(team.to)
|
|
159
|
-
? 'border-indigo-600 text-indigo-600'
|
|
160
|
-
: 'border-gray-200 text-gray-400 group-hover:border-indigo-600 group-hover:text-indigo-600',
|
|
161
|
-
'flex size-6 shrink-0 items-center justify-center rounded-lg border bg-white text-[0.625rem] font-medium'
|
|
162
|
-
)}
|
|
163
|
-
>
|
|
164
|
-
{team.initial}
|
|
165
|
-
</span>
|
|
166
|
-
<span className="truncate">{team.name}</span>
|
|
167
|
-
</Link>
|
|
168
|
-
</li>
|
|
169
|
-
))}
|
|
170
|
-
</ul>
|
|
171
|
-
</li>
|
|
172
|
-
|
|
173
|
-
<li className="-mx-6 mt-auto hidden lg:block">
|
|
174
|
-
<Link
|
|
175
|
-
to="#"
|
|
176
|
-
className="flex items-center gap-x-4 px-6 py-3 text-sm/6 font-semibold text-gray-900 hover:bg-gray-50"
|
|
177
|
-
>
|
|
178
|
-
<img alt="" src={avatarImg} className="size-8 rounded-full bg-gray-50" />
|
|
179
|
-
<span aria-hidden="true" class="truncate1 flex-1">{user?.name || 'Guest'}</span>
|
|
180
|
-
</Link>
|
|
181
|
-
</li>
|
|
182
|
-
</ul>
|
|
183
|
-
</nav>
|
|
184
|
-
</div>
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const style = css`
|
|
189
|
-
&.sidebar-transition-delay {
|
|
190
|
-
transition: transform 300ms, opacity 300ms, left 0ms 300ms;
|
|
191
|
-
}
|
|
192
|
-
&.sidebar-transition {
|
|
193
|
-
transition: transform 300ms, opacity 300ms, left 0ms 0ms;
|
|
194
|
-
}
|
|
195
|
-
`
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// todo: finish tailwind conversion
|
|
2
|
-
import { css } from 'twin.macro'
|
|
3
|
-
|
|
4
|
-
type TooltipProps = {
|
|
5
|
-
children: React.ReactNode
|
|
6
|
-
className?: string
|
|
7
|
-
classNamePopup?: string
|
|
8
|
-
isSmall?: boolean
|
|
9
|
-
text?: React.ReactNode
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function Tooltip({ text, children, className, classNamePopup, isSmall }: TooltipProps) {
|
|
13
|
-
return (
|
|
14
|
-
<div class={`${className} relative inline-block align-middle nitro-tooltip`} css={style}>
|
|
15
|
-
{
|
|
16
|
-
text
|
|
17
|
-
? <>
|
|
18
|
-
<div class="tooltip-trigger ">{children}</div>
|
|
19
|
-
<div class={`tooltip-popup ${classNamePopup||''} ${isSmall ? 'is-small' : ''}`}>{text}</div>
|
|
20
|
-
</>
|
|
21
|
-
: children
|
|
22
|
-
}
|
|
23
|
-
</div>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const style = css`
|
|
28
|
-
.tooltip-popup {
|
|
29
|
-
position: absolute;
|
|
30
|
-
display: block;
|
|
31
|
-
margin-top: -10000px;
|
|
32
|
-
width: 200px;
|
|
33
|
-
padding: 14px;
|
|
34
|
-
font-weight: 400;
|
|
35
|
-
font-size: 11.5px;
|
|
36
|
-
line-height: 1.3;
|
|
37
|
-
letter-spacing: 0.5px;
|
|
38
|
-
text-align: center;
|
|
39
|
-
border-radius: 6px;
|
|
40
|
-
background: black;
|
|
41
|
-
color: white;
|
|
42
|
-
opacity: 0;
|
|
43
|
-
transition: opacity 0.15s ease, transform 0.15s ease, margin-top 0s 0.15s;
|
|
44
|
-
white-space: break-spaces;
|
|
45
|
-
overflow-wrap: break-word;
|
|
46
|
-
pointer-events: none;
|
|
47
|
-
z-index: 9999;
|
|
48
|
-
&:after {
|
|
49
|
-
content: '';
|
|
50
|
-
position: absolute;
|
|
51
|
-
border-width: 6px;
|
|
52
|
-
border-style: solid;
|
|
53
|
-
}
|
|
54
|
-
// Variation
|
|
55
|
-
&.is-small {
|
|
56
|
-
width: 160px;
|
|
57
|
-
padding: 10px;
|
|
58
|
-
font-size: 11px;
|
|
59
|
-
}
|
|
60
|
-
// Positions
|
|
61
|
-
&.is-top,
|
|
62
|
-
&.is-top-left,
|
|
63
|
-
&:not(.is-top-left):not(.is-left):not(.is-right):not(.is-bottom):not(.is-bottom-left) {
|
|
64
|
-
bottom: 100%;
|
|
65
|
-
left: 50%;
|
|
66
|
-
transform: translateX(-50%) translateY(-15px);
|
|
67
|
-
&:after {
|
|
68
|
-
top: 100%;
|
|
69
|
-
left: 50%;
|
|
70
|
-
margin-left: -6px;
|
|
71
|
-
border-color: black transparent transparent transparent;
|
|
72
|
-
}
|
|
73
|
-
&.is-top-left {
|
|
74
|
-
left: 0px;
|
|
75
|
-
transform: translateX(0%) translateY(-15px);
|
|
76
|
-
&:after {
|
|
77
|
-
left: 28px;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
&.is-bottom,
|
|
82
|
-
&.is-bottom-left {
|
|
83
|
-
top: 100%;
|
|
84
|
-
left: 50%;
|
|
85
|
-
transform: translateX(-50%) translateY(15px) ;
|
|
86
|
-
&:after {
|
|
87
|
-
bottom: 100%;
|
|
88
|
-
left: 50%;
|
|
89
|
-
margin-left: -6px;
|
|
90
|
-
border-color: transparent transparent black transparent;
|
|
91
|
-
}
|
|
92
|
-
&.is-bottom-left {
|
|
93
|
-
left: 0px;
|
|
94
|
-
transform: translateX(0%) translateY(15px);
|
|
95
|
-
&:after {
|
|
96
|
-
left: 28px;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
&.is-left {
|
|
101
|
-
top: 50%;
|
|
102
|
-
right: 100%;
|
|
103
|
-
transform: translateX(-15px) translateY(-50%);
|
|
104
|
-
&:after {
|
|
105
|
-
top: 50%;
|
|
106
|
-
right: -12px;
|
|
107
|
-
margin-top: -6px;
|
|
108
|
-
border-color: transparent transparent transparent black;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
&.is-right {
|
|
112
|
-
top: 50%;
|
|
113
|
-
left: 100%;
|
|
114
|
-
transform: translateX(15px) translateY(-50%);
|
|
115
|
-
&:after {
|
|
116
|
-
top: 50%;
|
|
117
|
-
left: -12px;
|
|
118
|
-
margin-top: -6px;
|
|
119
|
-
border-color: transparent black transparent transparent;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
.tooltip-trigger {
|
|
124
|
-
/* trigger can come before tooltip-popup or wrap it */
|
|
125
|
-
position: relative !important;
|
|
126
|
-
&:hover .tooltip-popup,
|
|
127
|
-
&:hover + .tooltip-popup,
|
|
128
|
-
.tooltip-popup.is-active,
|
|
129
|
-
& + .tooltip-popup.is-active {
|
|
130
|
-
opacity: 1;
|
|
131
|
-
margin-top: 0;
|
|
132
|
-
transition: opacity 0.15s ease, transform 0.15s ease, margin-top 0s 0s;
|
|
133
|
-
&.is-top,
|
|
134
|
-
&:not(.is-top-left):not(.is-left):not(.is-right):not(.is-bottom):not(.is-bottom-left) {
|
|
135
|
-
transform: translateX(-50%) translateY(-10px);
|
|
136
|
-
}
|
|
137
|
-
&.is-top-left {
|
|
138
|
-
transform: translateX(0%) translateY(-10px);
|
|
139
|
-
}
|
|
140
|
-
&.is-bottom {
|
|
141
|
-
transform: translateX(-50%) translateY(10px);
|
|
142
|
-
}
|
|
143
|
-
&.is-bottom-left {
|
|
144
|
-
transform: translateX(0%) translateY(10px);
|
|
145
|
-
}
|
|
146
|
-
&.is-left {
|
|
147
|
-
transform: translateX(-10px) translateY(-50%);
|
|
148
|
-
}
|
|
149
|
-
&.is-right {
|
|
150
|
-
transform: translateX(10px) translateY(-50%);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
`
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
type TopbarProps = {
|
|
2
|
-
title: React.ReactNode
|
|
3
|
-
subtitle?: React.ReactNode
|
|
4
|
-
className?: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function Topbar({ title, subtitle, className }: TopbarProps) {
|
|
8
|
-
return (
|
|
9
|
-
<div class={`flex flex-col min-h-12 gap-0.5 mb-6 nitro-topbar ${className||''}`}>
|
|
10
|
-
<div class="text-2xl font-bold">{title}</div>
|
|
11
|
-
{ subtitle && <div class="text-sm text-muted-foreground">{subtitle}</div>}
|
|
12
|
-
{/* { submenu && <div class="pt-2 text-large weight-500">{submenu}</div> } */}
|
|
13
|
-
</div>
|
|
14
|
-
)
|
|
15
|
-
}
|