@pathmx/react 0.5.0
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/LICENSE.md +172 -0
- package/README.md +22 -0
- package/index.tsx +130 -0
- package/package.json +37 -0
- package/ui/action-button.tsx +57 -0
- package/ui/button.tsx +38 -0
- package/ui/drawer.tsx +103 -0
- package/ui/forms.tsx +86 -0
- package/ui/index.ts +9 -0
- package/ui/popover.tsx +78 -0
- package/ui/select.tsx +81 -0
- package/ui/tooltip.tsx +51 -0
- package/ui/ui.css +422 -0
- package/ui/utils.ts +5 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# PathMX Proprietary Beta License
|
|
2
|
+
|
|
3
|
+
Version 0.1 — July 21, 2026
|
|
4
|
+
|
|
5
|
+
Copyright © 2026 DUO Interactive, LLC.
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
This license governs the first-party PathMX software distributed in the npm
|
|
9
|
+
package `@pathmx/core`, including its command-line interface, bundled
|
|
10
|
+
library surfaces, runtime and Player components, and accompanying
|
|
11
|
+
documentation (the “Software”). DUO Interactive, LLC is the “Licensor.” This
|
|
12
|
+
license does not cover the private PathMX source-code repository or separately
|
|
13
|
+
identified third-party materials.
|
|
14
|
+
|
|
15
|
+
“Project” means Paths, Sources, responses, data, and other content authored or
|
|
16
|
+
processed with the Software.
|
|
17
|
+
|
|
18
|
+
“Application” means software or a service created by you in which the Software
|
|
19
|
+
supports product-specific functionality but is not itself offered as a
|
|
20
|
+
general-purpose PathMX product.
|
|
21
|
+
|
|
22
|
+
“Redistributable Components” means runtime, Player, or library portions of the
|
|
23
|
+
Software incorporated into a Project, Application, or generated output through
|
|
24
|
+
ordinary use of the Software or ordinary compilation and bundling tools.
|
|
25
|
+
|
|
26
|
+
“Authorized Users” means your employees and individual contractors who use the
|
|
27
|
+
Software on your behalf, and your hosting, continuous-integration, and other
|
|
28
|
+
service providers that possess or run necessary copies solely to provide
|
|
29
|
+
services to you.
|
|
30
|
+
|
|
31
|
+
## 1. Agreement
|
|
32
|
+
|
|
33
|
+
By installing, accessing, or using the Software, you agree to this license. If
|
|
34
|
+
you use the Software on behalf of an organization, you represent that you have
|
|
35
|
+
authority to accept this license for that organization. If you do not agree,
|
|
36
|
+
do not install or use the Software.
|
|
37
|
+
|
|
38
|
+
## 2. License grant
|
|
39
|
+
|
|
40
|
+
Subject to this license, the Licensor grants you a non-exclusive, worldwide,
|
|
41
|
+
royalty-free, non-transferable, and non-sublicensable license, perpetual for
|
|
42
|
+
the version of the Software to which it applies unless terminated under
|
|
43
|
+
Section 10, to:
|
|
44
|
+
|
|
45
|
+
- install, execute, and use the Software for personal, educational,
|
|
46
|
+
internal-business, or commercial purposes;
|
|
47
|
+
- use the Software to author, integrate, build, test, serve, play, host, and
|
|
48
|
+
operate Projects and Applications;
|
|
49
|
+
- import, compile, bundle, configure, and integrate the Software through its
|
|
50
|
+
documented library and command-line interfaces;
|
|
51
|
+
- create and distribute independent Projects, Applications, plugins, and
|
|
52
|
+
integrations that use those interfaces without modifying the Software
|
|
53
|
+
itself; and
|
|
54
|
+
- make copies reasonably necessary for local development, testing, continuous
|
|
55
|
+
integration, backup, and hosted production environments.
|
|
56
|
+
|
|
57
|
+
Your Authorized Users may exercise those rights solely on your behalf. You are
|
|
58
|
+
responsible for their compliance with this license.
|
|
59
|
+
|
|
60
|
+
You may reproduce and distribute Redistributable Components only as
|
|
61
|
+
incorporated into your Project, Application, or generated output, and only when
|
|
62
|
+
you preserve applicable license and proprietary notices. You may host and
|
|
63
|
+
commercially provide access to that Project, Application, or output. You may
|
|
64
|
+
not distribute Redistributable Components as a standalone package or as a
|
|
65
|
+
general-purpose substitute for the Software.
|
|
66
|
+
|
|
67
|
+
Each lawful recipient of a Redistributable Component may execute and use that
|
|
68
|
+
component, display its output, and make technically necessary transitory copies
|
|
69
|
+
solely as incorporated into the Project, Application, or output in which they
|
|
70
|
+
received it. This limited recipient permission does not include a right to
|
|
71
|
+
extract, modify, or redistribute the component separately.
|
|
72
|
+
|
|
73
|
+
## 3. Restrictions
|
|
74
|
+
|
|
75
|
+
Except where applicable law does not allow the restriction, you may not:
|
|
76
|
+
|
|
77
|
+
- modify, adapt, translate, or create derivative works of the Software, except
|
|
78
|
+
for the compilation, bundling, configuration, and integration expressly
|
|
79
|
+
permitted in Section 2;
|
|
80
|
+
- redistribute, sublicense, sell, lease, or otherwise provide the Software to
|
|
81
|
+
another person as a standalone package, product, or service except to an
|
|
82
|
+
Authorized User or as a Redistributable Component under Section 2;
|
|
83
|
+
- resell, rebrand, or provide the Software as a hosted or managed service that
|
|
84
|
+
gives third parties general-purpose access to substantial PathMX authoring,
|
|
85
|
+
building, serving, Player-administration, or library functionality for their
|
|
86
|
+
own unrelated projects;
|
|
87
|
+
- reverse engineer, decompile, disassemble, or otherwise attempt to derive the
|
|
88
|
+
source code of the Software; or
|
|
89
|
+
- remove or alter copyright, license, trademark, or other proprietary notices.
|
|
90
|
+
|
|
91
|
+
For clarity, the managed-service restriction does not prohibit a hosted
|
|
92
|
+
Project or Application in which PathMX supports the licensee's own
|
|
93
|
+
product-specific experience and is not itself the general-purpose product
|
|
94
|
+
offered to customers.
|
|
95
|
+
|
|
96
|
+
## 4. Your content
|
|
97
|
+
|
|
98
|
+
As between you and the Licensor, you retain ownership of the Paths, Sources,
|
|
99
|
+
responses, data, independent application code, and other content that you
|
|
100
|
+
author or provide while using the Software. You are responsible for that
|
|
101
|
+
content and for having the rights needed to use it.
|
|
102
|
+
|
|
103
|
+
Generated output may contain Redistributable Components or third-party
|
|
104
|
+
materials. Your ownership of your content does not change the ownership or
|
|
105
|
+
license terms of those components and materials.
|
|
106
|
+
|
|
107
|
+
## 5. Ownership and trademarks
|
|
108
|
+
|
|
109
|
+
The Software is licensed, not sold. The Licensor and its suppliers retain all
|
|
110
|
+
rights in the Software not expressly granted by this license. This license
|
|
111
|
+
does not grant permission to use the PathMX name, logos, or other marks except
|
|
112
|
+
as reasonably necessary to identify the Software accurately.
|
|
113
|
+
|
|
114
|
+
## 6. Beta status and changes
|
|
115
|
+
|
|
116
|
+
The Software is a proprietary beta release. Its APIs, behavior, file formats,
|
|
117
|
+
and features may change or be removed without notice. The Licensor has no
|
|
118
|
+
obligation to provide support, maintenance, updates, continued availability,
|
|
119
|
+
or backward compatibility. A later version may be offered under different
|
|
120
|
+
license terms, including an open-source license. Different terms for a later
|
|
121
|
+
version do not change the license previously granted for an earlier version.
|
|
122
|
+
|
|
123
|
+
## 7. Third-party materials
|
|
124
|
+
|
|
125
|
+
The Software and Redistributable Components may include or depend on
|
|
126
|
+
third-party materials governed by their own license terms. Those terms control
|
|
127
|
+
for the applicable third-party materials. You must preserve third-party notices
|
|
128
|
+
when the applicable third-party terms require it.
|
|
129
|
+
|
|
130
|
+
## 8. Disclaimer of warranties
|
|
131
|
+
|
|
132
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED “AS IS” AND
|
|
133
|
+
“AS AVAILABLE,” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EXPRESS,
|
|
134
|
+
IMPLIED, OR STATUTORY, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
135
|
+
PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, OR THAT THE SOFTWARE WILL BE
|
|
136
|
+
SECURE, ERROR-FREE, OR UNINTERRUPTED.
|
|
137
|
+
|
|
138
|
+
## 9. Limitation of liability
|
|
139
|
+
|
|
140
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR AND ITS CONTRIBUTORS,
|
|
141
|
+
SUPPLIERS, AND AFFILIATES WILL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
|
|
142
|
+
SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR LOSS OF DATA,
|
|
143
|
+
PROFITS, REVENUE, GOODWILL, OR BUSINESS OPPORTUNITY, ARISING FROM OR RELATED TO
|
|
144
|
+
THE SOFTWARE OR THIS LICENSE, EVEN IF ADVISED OF THE POSSIBILITY OF THOSE
|
|
145
|
+
DAMAGES.
|
|
146
|
+
|
|
147
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THEIR TOTAL LIABILITY FOR ALL CLAIMS
|
|
148
|
+
ARISING FROM OR RELATED TO THE SOFTWARE OR THIS LICENSE WILL NOT EXCEED THE
|
|
149
|
+
GREATER OF THE AMOUNT YOU PAID FOR THE SOFTWARE DURING THE TWELVE MONTHS BEFORE
|
|
150
|
+
THE EVENT GIVING RISE TO THE CLAIM OR US$100.
|
|
151
|
+
|
|
152
|
+
## 10. Termination
|
|
153
|
+
|
|
154
|
+
This license terminates if you materially breach it and do not cure the breach
|
|
155
|
+
within thirty days after receiving written notice from the Licensor. On
|
|
156
|
+
termination, you and your Authorized Users must stop using and delete your
|
|
157
|
+
copies of the Software. Sections 4, 5, 8, 9, 10, and 11 survive termination.
|
|
158
|
+
Termination does not withdraw the limited permission already granted by
|
|
159
|
+
Section 2 to lawful recipients of Redistributable Components distributed
|
|
160
|
+
before termination.
|
|
161
|
+
|
|
162
|
+
## 11. General
|
|
163
|
+
|
|
164
|
+
If a provision of this license is unenforceable, it will be modified only to
|
|
165
|
+
the minimum extent necessary to make it enforceable, and the remaining
|
|
166
|
+
provisions will continue in effect. Failure to enforce a provision is not a
|
|
167
|
+
waiver. You may not assign or transfer this license without the Licensor's
|
|
168
|
+
written consent. The Licensor may assign this license together with ownership
|
|
169
|
+
of the Software to a successor owner. This license binds and benefits the
|
|
170
|
+
permitted successors and assigns of you and the Licensor. This license is the
|
|
171
|
+
entire agreement concerning the Software unless you and the Licensor have
|
|
172
|
+
signed a separate agreement that expressly replaces it.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# `@pathmx/react`
|
|
2
|
+
|
|
3
|
+
React bindings and shared UI primitives for PathMX applications.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pathmx plugins add react
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Application integrations import the framework bindings from the package root:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { PathMXProvider, useActions, useSource } from "@pathmx/react"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Reusable accessible controls are available from the explicit UI subpath:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { ActionButton, Button } from "@pathmx/react/ui"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This package follows the PathMX CLI release channel and currently requires
|
|
22
|
+
React 19 or newer.
|
package/index.tsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createContext, useCallback, useContext, type ReactNode } from "react"
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client"
|
|
3
|
+
import type {
|
|
4
|
+
ActionAffordance,
|
|
5
|
+
SourceDescription,
|
|
6
|
+
SourceRelation,
|
|
7
|
+
SourceView,
|
|
8
|
+
} from "@pathmx/core"
|
|
9
|
+
|
|
10
|
+
export type PathMXReactValue = Readonly<{
|
|
11
|
+
description: SourceDescription
|
|
12
|
+
invoke(
|
|
13
|
+
action: ActionAffordance,
|
|
14
|
+
input?: Readonly<Record<string, string | readonly string[]>>,
|
|
15
|
+
): Promise<Response>
|
|
16
|
+
}>
|
|
17
|
+
|
|
18
|
+
const PathMXContext = createContext<PathMXReactValue | undefined>(undefined)
|
|
19
|
+
|
|
20
|
+
function actionBody(
|
|
21
|
+
action: ActionAffordance,
|
|
22
|
+
input: Readonly<Record<string, string | readonly string[]>>,
|
|
23
|
+
) {
|
|
24
|
+
const body = new URLSearchParams({ _target: action.target.id })
|
|
25
|
+
for (const [name, value] of Object.entries(input)) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
for (const item of value) body.append(name, item)
|
|
28
|
+
} else {
|
|
29
|
+
body.set(name, value as string)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return body
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function invokePathMXAction(
|
|
36
|
+
action: ActionAffordance,
|
|
37
|
+
input: Readonly<Record<string, string | readonly string[]>> = {},
|
|
38
|
+
) {
|
|
39
|
+
if (action.status !== "available") {
|
|
40
|
+
throw new Error(action.reason ?? `${action.title} is unavailable.`)
|
|
41
|
+
}
|
|
42
|
+
const response = await fetch(action.href, {
|
|
43
|
+
method: action.method.toUpperCase(),
|
|
44
|
+
credentials: "same-origin",
|
|
45
|
+
headers: {
|
|
46
|
+
Accept: "application/json",
|
|
47
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
48
|
+
},
|
|
49
|
+
body: actionBody(action, input),
|
|
50
|
+
})
|
|
51
|
+
if (!response.ok) throw new Error(`Action failed (${response.status}).`)
|
|
52
|
+
document.dispatchEvent(
|
|
53
|
+
new CustomEvent("pmx:action", { detail: { action: action.id } }),
|
|
54
|
+
)
|
|
55
|
+
return response
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function PathMXProvider(
|
|
59
|
+
props: Readonly<{
|
|
60
|
+
description: SourceDescription
|
|
61
|
+
children: ReactNode
|
|
62
|
+
}>,
|
|
63
|
+
) {
|
|
64
|
+
const invoke = useCallback<PathMXReactValue["invoke"]>(invokePathMXAction, [])
|
|
65
|
+
return (
|
|
66
|
+
<PathMXContext.Provider value={{ description: props.description, invoke }}>
|
|
67
|
+
{props.children}
|
|
68
|
+
</PathMXContext.Provider>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function usePathMX() {
|
|
73
|
+
const value = useContext(PathMXContext)
|
|
74
|
+
if (!value) throw new Error("usePathMX must be used inside PathMXProvider.")
|
|
75
|
+
return value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function useSource(): SourceView {
|
|
79
|
+
return usePathMX().description.source
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function useRelations(): readonly SourceRelation[] {
|
|
83
|
+
return usePathMX().description.relations
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function useActions(): readonly ActionAffordance[] {
|
|
87
|
+
return usePathMX().description.actions
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function useAction(id: string) {
|
|
91
|
+
const context = usePathMX()
|
|
92
|
+
const action = context.description.actions.find((item) => item.id === id)
|
|
93
|
+
return {
|
|
94
|
+
action,
|
|
95
|
+
invoke: (input?: Readonly<Record<string, string | readonly string[]>>) => {
|
|
96
|
+
if (!action) throw new Error(`Action not available: ${id}`)
|
|
97
|
+
return context.invoke(action, input)
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Mount React roots that may appear, update, or leave during PathMX renders. */
|
|
103
|
+
export function mountPathMXApps(
|
|
104
|
+
selector: string,
|
|
105
|
+
view: (element: HTMLElement) => ReactNode,
|
|
106
|
+
) {
|
|
107
|
+
const roots = new Map<HTMLElement, Root>()
|
|
108
|
+
const reconcile = () => {
|
|
109
|
+
for (const [element, root] of roots) {
|
|
110
|
+
if (element.isConnected && element.matches(selector)) continue
|
|
111
|
+
root.unmount()
|
|
112
|
+
roots.delete(element)
|
|
113
|
+
}
|
|
114
|
+
for (const element of document.querySelectorAll<HTMLElement>(selector)) {
|
|
115
|
+
let root = roots.get(element)
|
|
116
|
+
if (!root) {
|
|
117
|
+
root = createRoot(element)
|
|
118
|
+
roots.set(element, root)
|
|
119
|
+
}
|
|
120
|
+
root.render(view(element))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
reconcile()
|
|
124
|
+
document.addEventListener("pmx:render", reconcile)
|
|
125
|
+
return () => {
|
|
126
|
+
document.removeEventListener("pmx:render", reconcile)
|
|
127
|
+
for (const root of roots.values()) root.unmount()
|
|
128
|
+
roots.clear()
|
|
129
|
+
}
|
|
130
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pathmx/react",
|
|
3
|
+
"description": "React bindings and UI primitives for PathMX applications.",
|
|
4
|
+
"version": "0.5.0",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/pathmx/pathmx-beta.git",
|
|
9
|
+
"directory": "pathmx/react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/pathmx/pathmx-beta#readme",
|
|
12
|
+
"bugs": "https://github.com/pathmx/pathmx-beta/issues",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"registry": "https://registry.npmjs.org/",
|
|
16
|
+
"tag": "beta"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./index.tsx",
|
|
21
|
+
"./ui": "./ui/index.ts"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"bun": ">=1.4.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@pathmx/core": "^0.5.0",
|
|
28
|
+
"react": ">=19.0.0",
|
|
29
|
+
"react-dom": ">=19.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@base-ui/react": "^1.7.0",
|
|
33
|
+
"class-variance-authority": "^0.7.1",
|
|
34
|
+
"clsx": "^2.1.1",
|
|
35
|
+
"lucide-react": "^1.34.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react"
|
|
2
|
+
import { Button } from "./button.tsx"
|
|
3
|
+
import { cn } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
export type ActionButtonProps = Readonly<{
|
|
6
|
+
label: string
|
|
7
|
+
icon: ReactNode
|
|
8
|
+
appearance?: "plain" | "floating"
|
|
9
|
+
size?: "sm" | "default"
|
|
10
|
+
pressed?: boolean
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
className?: string
|
|
13
|
+
tabIndex?: number
|
|
14
|
+
}> &
|
|
15
|
+
Omit<
|
|
16
|
+
ComponentProps<typeof Button>,
|
|
17
|
+
| "aria-label"
|
|
18
|
+
| "aria-pressed"
|
|
19
|
+
| "children"
|
|
20
|
+
| "className"
|
|
21
|
+
| "disabled"
|
|
22
|
+
| "size"
|
|
23
|
+
| "title"
|
|
24
|
+
| "variant"
|
|
25
|
+
>
|
|
26
|
+
|
|
27
|
+
/** Shared accessible icon action for React-owned PathMX Plugin surfaces. */
|
|
28
|
+
export function ActionButton({
|
|
29
|
+
label,
|
|
30
|
+
icon,
|
|
31
|
+
appearance = "plain",
|
|
32
|
+
size = "default",
|
|
33
|
+
pressed,
|
|
34
|
+
disabled,
|
|
35
|
+
className,
|
|
36
|
+
tabIndex,
|
|
37
|
+
...props
|
|
38
|
+
}: ActionButtonProps) {
|
|
39
|
+
return (
|
|
40
|
+
<Button
|
|
41
|
+
type="button"
|
|
42
|
+
variant="ghost"
|
|
43
|
+
size={size === "sm" ? "icon-sm" : "icon"}
|
|
44
|
+
data-slot="action-button"
|
|
45
|
+
data-appearance={appearance}
|
|
46
|
+
aria-label={label}
|
|
47
|
+
aria-pressed={pressed}
|
|
48
|
+
title={label}
|
|
49
|
+
disabled={disabled}
|
|
50
|
+
tabIndex={tabIndex}
|
|
51
|
+
className={cn("pmx-ui-action-button", className)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{icon}
|
|
55
|
+
</Button>
|
|
56
|
+
)
|
|
57
|
+
}
|
package/ui/button.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva("pmx-ui-button", {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: "pmx-ui-button-default",
|
|
9
|
+
outline: "pmx-ui-button-outline",
|
|
10
|
+
ghost: "pmx-ui-button-ghost",
|
|
11
|
+
destructive: "pmx-ui-button-destructive",
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
default: "pmx-ui-button-size-default",
|
|
15
|
+
sm: "pmx-ui-button-size-sm",
|
|
16
|
+
icon: "pmx-ui-button-size-icon",
|
|
17
|
+
"icon-sm": "pmx-ui-button-size-icon-sm",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { variant: "default", size: "default" },
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export function Button({
|
|
24
|
+
className,
|
|
25
|
+
variant = "default",
|
|
26
|
+
size = "default",
|
|
27
|
+
...props
|
|
28
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
29
|
+
return (
|
|
30
|
+
<ButtonPrimitive
|
|
31
|
+
data-slot="button"
|
|
32
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { buttonVariants }
|
package/ui/drawer.tsx
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer"
|
|
2
|
+
import { createContext, useContext, useMemo, type ComponentProps } from "react"
|
|
3
|
+
import { cn } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
const DrawerContext = createContext({ showSwipeHandle: false })
|
|
6
|
+
|
|
7
|
+
export function Drawer({
|
|
8
|
+
showSwipeHandle = false,
|
|
9
|
+
swipeDirection = "down",
|
|
10
|
+
...props
|
|
11
|
+
}: DrawerPrimitive.Root.Props & { showSwipeHandle?: boolean }) {
|
|
12
|
+
const value = useMemo(() => ({ showSwipeHandle }), [showSwipeHandle])
|
|
13
|
+
return (
|
|
14
|
+
<DrawerContext.Provider value={value}>
|
|
15
|
+
<DrawerPrimitive.Root
|
|
16
|
+
data-slot="drawer"
|
|
17
|
+
swipeDirection={swipeDirection}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
</DrawerContext.Provider>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function DrawerTrigger(props: DrawerPrimitive.Trigger.Props) {
|
|
25
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DrawerClose(props: DrawerPrimitive.Close.Props) {
|
|
29
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function DrawerContent({
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: DrawerPrimitive.Popup.Props) {
|
|
37
|
+
const { showSwipeHandle } = useContext(DrawerContext)
|
|
38
|
+
return (
|
|
39
|
+
<DrawerPrimitive.Portal data-slot="drawer-portal">
|
|
40
|
+
<DrawerPrimitive.Backdrop
|
|
41
|
+
data-slot="drawer-overlay"
|
|
42
|
+
className="pmx-ui-drawer-overlay"
|
|
43
|
+
/>
|
|
44
|
+
<DrawerPrimitive.Viewport
|
|
45
|
+
data-slot="drawer-viewport"
|
|
46
|
+
className="pmx-ui-drawer-viewport"
|
|
47
|
+
>
|
|
48
|
+
<DrawerPrimitive.Popup
|
|
49
|
+
data-slot="drawer-popup"
|
|
50
|
+
data-swipe-axis="y"
|
|
51
|
+
className={cn("pmx-ui-drawer-popup", className)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{showSwipeHandle && (
|
|
55
|
+
<div data-slot="drawer-swipe-handle" aria-hidden="true" />
|
|
56
|
+
)}
|
|
57
|
+
<DrawerPrimitive.Content
|
|
58
|
+
data-slot="drawer-content"
|
|
59
|
+
className="pmx-ui-drawer-content"
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</DrawerPrimitive.Content>
|
|
63
|
+
</DrawerPrimitive.Popup>
|
|
64
|
+
</DrawerPrimitive.Viewport>
|
|
65
|
+
</DrawerPrimitive.Portal>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function DrawerHeader({ className, ...props }: ComponentProps<"div">) {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
data-slot="drawer-header"
|
|
73
|
+
className={cn("pmx-ui-drawer-header", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function DrawerTitle({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: DrawerPrimitive.Title.Props) {
|
|
83
|
+
return (
|
|
84
|
+
<DrawerPrimitive.Title
|
|
85
|
+
data-slot="drawer-title"
|
|
86
|
+
className={cn("pmx-ui-drawer-title", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function DrawerDescription({
|
|
93
|
+
className,
|
|
94
|
+
...props
|
|
95
|
+
}: DrawerPrimitive.Description.Props) {
|
|
96
|
+
return (
|
|
97
|
+
<DrawerPrimitive.Description
|
|
98
|
+
data-slot="drawer-description"
|
|
99
|
+
className={cn("pmx-ui-visually-hidden", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
package/ui/forms.tsx
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
|
2
|
+
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
|
|
3
|
+
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
|
|
4
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
useContext,
|
|
8
|
+
type ComponentProps,
|
|
9
|
+
type CSSProperties,
|
|
10
|
+
} from "react"
|
|
11
|
+
import { cn } from "./utils.ts"
|
|
12
|
+
|
|
13
|
+
export function Label({ className, ...props }: ComponentProps<"label">) {
|
|
14
|
+
return (
|
|
15
|
+
<label
|
|
16
|
+
data-slot="label"
|
|
17
|
+
className={cn("pmx-ui-label", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Switch({ className, ...props }: SwitchPrimitive.Root.Props) {
|
|
24
|
+
return (
|
|
25
|
+
<SwitchPrimitive.Root
|
|
26
|
+
data-slot="switch"
|
|
27
|
+
className={cn("pmx-ui-switch", className)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
<SwitchPrimitive.Thumb data-slot="switch-thumb" />
|
|
31
|
+
</SwitchPrimitive.Root>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ToggleGroupContext = createContext({ spacing: 2 })
|
|
36
|
+
|
|
37
|
+
export function ToggleGroup({
|
|
38
|
+
className,
|
|
39
|
+
spacing = 2,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: ToggleGroupPrimitive.Props & { spacing?: number }) {
|
|
43
|
+
return (
|
|
44
|
+
<ToggleGroupPrimitive
|
|
45
|
+
data-slot="toggle-group"
|
|
46
|
+
data-spacing={spacing}
|
|
47
|
+
style={{ "--pmx-ui-group-gap": spacing } as CSSProperties}
|
|
48
|
+
className={cn("pmx-ui-toggle-group", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
<ToggleGroupContext.Provider value={{ spacing }}>
|
|
52
|
+
{children}
|
|
53
|
+
</ToggleGroupContext.Provider>
|
|
54
|
+
</ToggleGroupPrimitive>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function ToggleGroupItem({
|
|
59
|
+
className,
|
|
60
|
+
...props
|
|
61
|
+
}: TogglePrimitive.Props) {
|
|
62
|
+
const { spacing } = useContext(ToggleGroupContext)
|
|
63
|
+
return (
|
|
64
|
+
<TogglePrimitive
|
|
65
|
+
data-slot="toggle-group-item"
|
|
66
|
+
data-spacing={spacing}
|
|
67
|
+
className={cn("pmx-ui-toggle-group-item", className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function Separator({
|
|
74
|
+
className,
|
|
75
|
+
orientation = "horizontal",
|
|
76
|
+
...props
|
|
77
|
+
}: SeparatorPrimitive.Props) {
|
|
78
|
+
return (
|
|
79
|
+
<SeparatorPrimitive
|
|
80
|
+
data-slot="separator"
|
|
81
|
+
orientation={orientation}
|
|
82
|
+
className={cn("pmx-ui-separator", className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
package/ui/index.ts
ADDED
package/ui/popover.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
|
|
2
|
+
import type { ComponentProps } from "react"
|
|
3
|
+
import { cn } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
export function Popover(props: PopoverPrimitive.Root.Props) {
|
|
6
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function PopoverTrigger(props: PopoverPrimitive.Trigger.Props) {
|
|
10
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function PopoverContent({
|
|
14
|
+
className,
|
|
15
|
+
align = "end",
|
|
16
|
+
alignOffset = 0,
|
|
17
|
+
side = "top",
|
|
18
|
+
sideOffset = 12,
|
|
19
|
+
...props
|
|
20
|
+
}: PopoverPrimitive.Popup.Props &
|
|
21
|
+
Pick<
|
|
22
|
+
PopoverPrimitive.Positioner.Props,
|
|
23
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
24
|
+
>) {
|
|
25
|
+
return (
|
|
26
|
+
<PopoverPrimitive.Portal>
|
|
27
|
+
<PopoverPrimitive.Positioner
|
|
28
|
+
align={align}
|
|
29
|
+
alignOffset={alignOffset}
|
|
30
|
+
side={side}
|
|
31
|
+
sideOffset={sideOffset}
|
|
32
|
+
className="pmx-ui-positioner"
|
|
33
|
+
>
|
|
34
|
+
<PopoverPrimitive.Popup
|
|
35
|
+
data-slot="popover-content"
|
|
36
|
+
className={cn("pmx-ui-popover-content", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
</PopoverPrimitive.Positioner>
|
|
40
|
+
</PopoverPrimitive.Portal>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function PopoverHeader({ className, ...props }: ComponentProps<"div">) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
data-slot="popover-header"
|
|
48
|
+
className={cn("pmx-ui-popover-header", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function PopoverTitle({
|
|
55
|
+
className,
|
|
56
|
+
...props
|
|
57
|
+
}: PopoverPrimitive.Title.Props) {
|
|
58
|
+
return (
|
|
59
|
+
<PopoverPrimitive.Title
|
|
60
|
+
data-slot="popover-title"
|
|
61
|
+
className={cn("pmx-ui-popover-title", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function PopoverDescription({
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}: PopoverPrimitive.Description.Props) {
|
|
71
|
+
return (
|
|
72
|
+
<PopoverPrimitive.Description
|
|
73
|
+
data-slot="popover-description"
|
|
74
|
+
className={cn("pmx-ui-visually-hidden", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
package/ui/select.tsx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Select as SelectPrimitive } from "@base-ui/react/select"
|
|
2
|
+
import { Check, ChevronDown } from "lucide-react"
|
|
3
|
+
import { cn } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
export const Select = SelectPrimitive.Root
|
|
6
|
+
|
|
7
|
+
export function SelectValue(props: SelectPrimitive.Value.Props) {
|
|
8
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function SelectTrigger({
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
...props
|
|
15
|
+
}: SelectPrimitive.Trigger.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<SelectPrimitive.Trigger
|
|
18
|
+
data-slot="select-trigger"
|
|
19
|
+
className={cn("pmx-ui-select-trigger", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
<SelectPrimitive.Icon
|
|
24
|
+
render={<ChevronDown aria-hidden="true" size={17} />}
|
|
25
|
+
/>
|
|
26
|
+
</SelectPrimitive.Trigger>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function SelectContent({
|
|
31
|
+
className,
|
|
32
|
+
children,
|
|
33
|
+
side = "bottom",
|
|
34
|
+
sideOffset = 6,
|
|
35
|
+
align = "end",
|
|
36
|
+
alignItemWithTrigger = false,
|
|
37
|
+
...props
|
|
38
|
+
}: SelectPrimitive.Popup.Props &
|
|
39
|
+
Pick<
|
|
40
|
+
SelectPrimitive.Positioner.Props,
|
|
41
|
+
"side" | "sideOffset" | "align" | "alignItemWithTrigger"
|
|
42
|
+
>) {
|
|
43
|
+
return (
|
|
44
|
+
<SelectPrimitive.Portal>
|
|
45
|
+
<SelectPrimitive.Positioner
|
|
46
|
+
side={side}
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
align={align}
|
|
49
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
50
|
+
className="pmx-ui-positioner"
|
|
51
|
+
>
|
|
52
|
+
<SelectPrimitive.Popup
|
|
53
|
+
data-slot="select-content"
|
|
54
|
+
className={cn("pmx-ui-select-content", className)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
|
58
|
+
</SelectPrimitive.Popup>
|
|
59
|
+
</SelectPrimitive.Positioner>
|
|
60
|
+
</SelectPrimitive.Portal>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function SelectItem({
|
|
65
|
+
className,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: SelectPrimitive.Item.Props) {
|
|
69
|
+
return (
|
|
70
|
+
<SelectPrimitive.Item
|
|
71
|
+
data-slot="select-item"
|
|
72
|
+
className={cn("pmx-ui-select-item", className)}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
76
|
+
<SelectPrimitive.ItemIndicator data-slot="select-item-indicator">
|
|
77
|
+
<Check aria-hidden="true" size={16} />
|
|
78
|
+
</SelectPrimitive.ItemIndicator>
|
|
79
|
+
</SelectPrimitive.Item>
|
|
80
|
+
)
|
|
81
|
+
}
|
package/ui/tooltip.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
|
2
|
+
import { cn } from "./utils.ts"
|
|
3
|
+
|
|
4
|
+
export function TooltipProvider({
|
|
5
|
+
delay = 350,
|
|
6
|
+
...props
|
|
7
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<TooltipPrimitive.Provider
|
|
10
|
+
data-slot="tooltip-provider"
|
|
11
|
+
delay={delay}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Tooltip(props: TooltipPrimitive.Root.Props) {
|
|
18
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) {
|
|
22
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function TooltipContent({
|
|
26
|
+
className,
|
|
27
|
+
side = "top",
|
|
28
|
+
sideOffset = 7,
|
|
29
|
+
children,
|
|
30
|
+
...props
|
|
31
|
+
}: TooltipPrimitive.Popup.Props &
|
|
32
|
+
Pick<TooltipPrimitive.Positioner.Props, "side" | "sideOffset">) {
|
|
33
|
+
return (
|
|
34
|
+
<TooltipPrimitive.Portal>
|
|
35
|
+
<TooltipPrimitive.Positioner
|
|
36
|
+
side={side}
|
|
37
|
+
sideOffset={sideOffset}
|
|
38
|
+
className="pmx-ui-positioner"
|
|
39
|
+
>
|
|
40
|
+
<TooltipPrimitive.Popup
|
|
41
|
+
data-slot="tooltip-content"
|
|
42
|
+
className={cn("pmx-ui-tooltip-content", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
<TooltipPrimitive.Arrow data-slot="tooltip-arrow" />
|
|
47
|
+
</TooltipPrimitive.Popup>
|
|
48
|
+
</TooltipPrimitive.Positioner>
|
|
49
|
+
</TooltipPrimitive.Portal>
|
|
50
|
+
)
|
|
51
|
+
}
|
package/ui/ui.css
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
:where([data-slot="button"], [data-slot="action-button"]) {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
flex: none;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
gap: 0.45rem;
|
|
8
|
+
border: 0;
|
|
9
|
+
font: inherit;
|
|
10
|
+
white-space: nowrap;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
user-select: none;
|
|
13
|
+
touch-action: manipulation;
|
|
14
|
+
transition:
|
|
15
|
+
color 160ms ease,
|
|
16
|
+
border-color 160ms ease,
|
|
17
|
+
background-color 160ms ease,
|
|
18
|
+
transform 160ms ease;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:where([data-slot="button"], [data-slot="action-button"]):disabled {
|
|
22
|
+
cursor: default;
|
|
23
|
+
opacity: 0.42;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:where([data-slot="button"], [data-slot="action-button"]):focus-visible,
|
|
27
|
+
:where(
|
|
28
|
+
[data-slot="select-trigger"],
|
|
29
|
+
[data-slot="switch"],
|
|
30
|
+
[data-slot="toggle-group-item"]
|
|
31
|
+
):focus-visible {
|
|
32
|
+
outline: 2px solid var(--pmx-ui-accent, var(--pmx-color-accent, #3897ff));
|
|
33
|
+
outline-offset: 2px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.pmx-ui-button-default,
|
|
37
|
+
.pmx-ui-button-outline {
|
|
38
|
+
min-height: 2.5rem;
|
|
39
|
+
padding-inline: 0.9rem;
|
|
40
|
+
border-radius: 0.6rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.pmx-ui-button-default {
|
|
44
|
+
color: var(--pmx-ui-on-accent, var(--pmx-color-bg, white));
|
|
45
|
+
background: var(--pmx-ui-accent, var(--pmx-color-accent, #2563eb));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.pmx-ui-button-outline {
|
|
49
|
+
color: inherit;
|
|
50
|
+
border: 1px solid var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
51
|
+
background: var(--pmx-ui-surface, var(--pmx-color-surface, Canvas));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.pmx-ui-button-ghost {
|
|
55
|
+
color: inherit;
|
|
56
|
+
background: transparent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.pmx-ui-button-destructive {
|
|
60
|
+
min-height: 2.5rem;
|
|
61
|
+
padding-inline: 0.9rem;
|
|
62
|
+
color: var(--pmx-ui-on-destructive, white);
|
|
63
|
+
background: var(--pmx-ui-destructive, var(--pmx-color-danger, #b91c1c));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.pmx-ui-button-size-default {
|
|
67
|
+
min-height: 2.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.pmx-ui-button-size-sm {
|
|
71
|
+
min-height: 2.25rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.pmx-ui-button-size-icon,
|
|
75
|
+
.pmx-ui-button-size-icon-sm {
|
|
76
|
+
padding: 0;
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.pmx-ui-button-size-icon {
|
|
81
|
+
width: 2.75rem;
|
|
82
|
+
height: 2.75rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.pmx-ui-button-size-icon-sm {
|
|
86
|
+
width: 2.5rem;
|
|
87
|
+
height: 2.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.pmx-ui-action-button {
|
|
91
|
+
border: 1px solid transparent;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.pmx-ui-action-button[data-appearance="floating"] {
|
|
95
|
+
color: var(--pmx-ui-fg, var(--pmx-color-fg, CanvasText));
|
|
96
|
+
border-color: var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
97
|
+
background: var(--pmx-ui-surface, var(--pmx-color-surface, Canvas));
|
|
98
|
+
box-shadow: 0 4px 12px rgb(0 0 0 / 14%);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.pmx-ui-action-button:hover,
|
|
102
|
+
.pmx-ui-action-button[aria-pressed="true"] {
|
|
103
|
+
color: var(--pmx-ui-accent, var(--pmx-color-accent, #2563eb));
|
|
104
|
+
background: color-mix(
|
|
105
|
+
in oklab,
|
|
106
|
+
currentColor 10%,
|
|
107
|
+
var(--pmx-ui-surface, var(--pmx-color-surface, Canvas))
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.pmx-ui-action-button svg {
|
|
112
|
+
width: 1.2rem;
|
|
113
|
+
height: 1.2rem;
|
|
114
|
+
stroke-width: 1.8;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.pmx-ui-positioner {
|
|
118
|
+
z-index: 1002;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.pmx-ui-popover-content,
|
|
122
|
+
.pmx-ui-select-content,
|
|
123
|
+
.pmx-ui-drawer-popup {
|
|
124
|
+
box-sizing: border-box;
|
|
125
|
+
color: var(--pmx-ui-fg, var(--pmx-color-fg, CanvasText));
|
|
126
|
+
border: 1px solid var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
127
|
+
background: var(--pmx-ui-surface, var(--pmx-color-surface, Canvas));
|
|
128
|
+
box-shadow: 0 6px 20px rgb(0 0 0 / 16%);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.pmx-ui-popover-content {
|
|
132
|
+
width: min(22.5rem, calc(100vw - 2rem));
|
|
133
|
+
padding: 1rem;
|
|
134
|
+
border-radius: 0.625rem;
|
|
135
|
+
transform-origin: var(--transform-origin);
|
|
136
|
+
transition:
|
|
137
|
+
opacity 160ms ease,
|
|
138
|
+
transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.pmx-ui-popover-content[data-starting-style],
|
|
142
|
+
.pmx-ui-popover-content[data-ending-style] {
|
|
143
|
+
opacity: 0;
|
|
144
|
+
transform: translateY(0.35rem) scale(0.97);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.pmx-ui-popover-header {
|
|
148
|
+
margin-block-end: 0.75rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.pmx-ui-popover-title,
|
|
152
|
+
.pmx-ui-drawer-title {
|
|
153
|
+
margin: 0;
|
|
154
|
+
font-size: 0.95rem;
|
|
155
|
+
font-weight: 650;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.pmx-ui-visually-hidden {
|
|
159
|
+
position: absolute;
|
|
160
|
+
width: 1px;
|
|
161
|
+
height: 1px;
|
|
162
|
+
padding: 0;
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
clip: rect(0 0 0 0);
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
border: 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.pmx-ui-drawer-overlay {
|
|
170
|
+
position: fixed;
|
|
171
|
+
z-index: 1000;
|
|
172
|
+
inset: 0;
|
|
173
|
+
min-height: 100dvh;
|
|
174
|
+
background: rgb(0 0 0 / 44%);
|
|
175
|
+
opacity: calc(1 - var(--drawer-swipe-progress, 0));
|
|
176
|
+
transition: opacity 240ms ease;
|
|
177
|
+
touch-action: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.pmx-ui-drawer-overlay[data-starting-style],
|
|
181
|
+
.pmx-ui-drawer-overlay[data-ending-style] {
|
|
182
|
+
opacity: 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.pmx-ui-drawer-viewport {
|
|
186
|
+
position: fixed;
|
|
187
|
+
z-index: 1001;
|
|
188
|
+
inset: 0;
|
|
189
|
+
pointer-events: none;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.pmx-ui-drawer-popup {
|
|
193
|
+
--pmx-drawer-translate: calc(
|
|
194
|
+
var(--drawer-swipe-movement-y, 0px) + var(--drawer-snap-point-offset, 0px)
|
|
195
|
+
);
|
|
196
|
+
position: absolute;
|
|
197
|
+
inset-inline: 0;
|
|
198
|
+
bottom: 0;
|
|
199
|
+
display: flex;
|
|
200
|
+
max-height: calc(100dvh - 2rem);
|
|
201
|
+
margin: 0;
|
|
202
|
+
overflow: hidden;
|
|
203
|
+
border-radius: 0.75rem 0.75rem 0 0;
|
|
204
|
+
pointer-events: auto;
|
|
205
|
+
outline: none;
|
|
206
|
+
transform: translate3d(0, var(--pmx-drawer-translate), 0);
|
|
207
|
+
transition: transform 380ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
208
|
+
touch-action: none;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.pmx-ui-drawer-popup[data-starting-style],
|
|
212
|
+
.pmx-ui-drawer-popup[data-ending-style] {
|
|
213
|
+
transform: translate3d(0, 100%, 0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.pmx-ui-drawer-popup[data-swiping] {
|
|
217
|
+
transition: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
[data-slot="drawer-swipe-handle"] {
|
|
221
|
+
position: absolute;
|
|
222
|
+
z-index: 1;
|
|
223
|
+
top: 0.75rem;
|
|
224
|
+
left: 50%;
|
|
225
|
+
width: 2.4rem;
|
|
226
|
+
height: 0.28rem;
|
|
227
|
+
border-radius: 999px;
|
|
228
|
+
background: var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
229
|
+
transform: translateX(-50%);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.pmx-ui-drawer-content {
|
|
233
|
+
display: flex;
|
|
234
|
+
min-height: 0;
|
|
235
|
+
flex: 1;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
overscroll-behavior: contain;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.pmx-ui-drawer-header {
|
|
242
|
+
flex: none;
|
|
243
|
+
padding: 1.7rem 1.25rem 0.5rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.pmx-ui-select-trigger {
|
|
247
|
+
box-sizing: border-box;
|
|
248
|
+
display: inline-flex;
|
|
249
|
+
width: 100%;
|
|
250
|
+
min-height: 2.45rem;
|
|
251
|
+
align-items: center;
|
|
252
|
+
justify-content: space-between;
|
|
253
|
+
gap: 0.75rem;
|
|
254
|
+
padding-inline: 0.75rem;
|
|
255
|
+
color: inherit;
|
|
256
|
+
border: 1px solid var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
257
|
+
border-radius: 0.5rem;
|
|
258
|
+
background: var(--pmx-ui-bg, var(--pmx-color-bg, Canvas));
|
|
259
|
+
font: inherit;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.pmx-ui-select-content {
|
|
264
|
+
min-width: var(--anchor-width);
|
|
265
|
+
max-height: min(20rem, var(--available-height));
|
|
266
|
+
padding: 0.3rem;
|
|
267
|
+
overflow-y: auto;
|
|
268
|
+
border-radius: 0.65rem;
|
|
269
|
+
transform-origin: var(--transform-origin);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.pmx-ui-select-item {
|
|
273
|
+
position: relative;
|
|
274
|
+
display: flex;
|
|
275
|
+
min-height: 2.35rem;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
gap: 0.75rem;
|
|
279
|
+
padding: 0.45rem 2rem 0.45rem 0.65rem;
|
|
280
|
+
border-radius: 0.42rem;
|
|
281
|
+
cursor: default;
|
|
282
|
+
outline: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.pmx-ui-select-item[data-highlighted] {
|
|
286
|
+
color: var(--pmx-ui-fg, var(--pmx-color-fg, CanvasText));
|
|
287
|
+
background: color-mix(
|
|
288
|
+
in oklab,
|
|
289
|
+
var(--pmx-ui-fg, var(--pmx-color-fg, CanvasText)) 8%,
|
|
290
|
+
transparent
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
[data-slot="select-item-indicator"] {
|
|
295
|
+
position: absolute;
|
|
296
|
+
right: 0.55rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.pmx-ui-label {
|
|
300
|
+
display: flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
color: inherit;
|
|
303
|
+
font: inherit;
|
|
304
|
+
user-select: none;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.pmx-ui-switch {
|
|
308
|
+
position: relative;
|
|
309
|
+
display: inline-flex;
|
|
310
|
+
width: 2.45rem;
|
|
311
|
+
height: 1.4rem;
|
|
312
|
+
flex: none;
|
|
313
|
+
align-items: center;
|
|
314
|
+
padding: 0.12rem;
|
|
315
|
+
border: 0;
|
|
316
|
+
border-radius: 999px;
|
|
317
|
+
border: 1px solid var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
318
|
+
background: var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
319
|
+
cursor: pointer;
|
|
320
|
+
transition: background-color 160ms ease;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.pmx-ui-switch[data-checked] {
|
|
324
|
+
background: var(--pmx-ui-accent, #3897ff);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
[data-slot="switch-thumb"] {
|
|
328
|
+
display: block;
|
|
329
|
+
width: 1.15rem;
|
|
330
|
+
height: 1.15rem;
|
|
331
|
+
border-radius: 999px;
|
|
332
|
+
background: var(--pmx-ui-surface, var(--pmx-color-surface, white));
|
|
333
|
+
box-shadow: 0 1px 3px rgb(0 0 0 / 20%);
|
|
334
|
+
transform: translateX(0);
|
|
335
|
+
transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.pmx-ui-switch[data-checked] [data-slot="switch-thumb"] {
|
|
339
|
+
transform: translateX(1.05rem);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.pmx-ui-toggle-group {
|
|
343
|
+
display: flex;
|
|
344
|
+
width: 100%;
|
|
345
|
+
gap: calc(var(--pmx-ui-group-gap, 0) * 0.25rem);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.pmx-ui-toggle-group-item {
|
|
349
|
+
min-height: 2.35rem;
|
|
350
|
+
flex: 1;
|
|
351
|
+
padding: 0.4rem 0.7rem;
|
|
352
|
+
color: inherit;
|
|
353
|
+
border: 1px solid var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
354
|
+
background: var(--pmx-ui-bg, var(--pmx-color-bg, Canvas));
|
|
355
|
+
font: inherit;
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.pmx-ui-toggle-group-item:first-child {
|
|
360
|
+
border-radius: 0.48rem 0 0 0.48rem;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.pmx-ui-toggle-group-item:last-child {
|
|
364
|
+
border-radius: 0 0.48rem 0.48rem 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.pmx-ui-toggle-group-item:not(:first-child) {
|
|
368
|
+
margin-inline-start: -1px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.pmx-ui-toggle-group-item[data-pressed] {
|
|
372
|
+
position: relative;
|
|
373
|
+
z-index: 1;
|
|
374
|
+
color: var(--pmx-ui-accent, #3897ff);
|
|
375
|
+
border-color: currentColor;
|
|
376
|
+
background: color-mix(in oklab, currentColor 8%, transparent);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.pmx-ui-separator {
|
|
380
|
+
display: block;
|
|
381
|
+
width: 100%;
|
|
382
|
+
height: 1px;
|
|
383
|
+
flex: none;
|
|
384
|
+
background: var(--pmx-ui-border, var(--pmx-color-border, #d4d4d8));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.pmx-ui-tooltip-content {
|
|
388
|
+
max-width: 15rem;
|
|
389
|
+
padding: 0.35rem 0.5rem;
|
|
390
|
+
color: var(--pmx-ui-bg, var(--pmx-color-bg, Canvas));
|
|
391
|
+
border-radius: 0.35rem;
|
|
392
|
+
background: var(--pmx-ui-fg, var(--pmx-color-fg, CanvasText));
|
|
393
|
+
box-shadow: 0 4px 12px rgb(0 0 0 / 16%);
|
|
394
|
+
font:
|
|
395
|
+
600 0.72rem/1.2 system-ui,
|
|
396
|
+
sans-serif;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
[data-slot="tooltip-arrow"] {
|
|
400
|
+
width: 0.5rem;
|
|
401
|
+
height: 0.5rem;
|
|
402
|
+
background: var(--pmx-ui-fg, var(--pmx-color-fg, CanvasText));
|
|
403
|
+
transform: rotate(45deg);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@media (pointer: coarse) {
|
|
407
|
+
.pmx-ui-button-size-icon-sm,
|
|
408
|
+
.pmx-ui-button-size-icon {
|
|
409
|
+
width: 2.75rem;
|
|
410
|
+
height: 2.75rem;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@media (prefers-reduced-motion: reduce) {
|
|
415
|
+
:where([data-slot="button"], [data-slot="action-button"]),
|
|
416
|
+
.pmx-ui-popover-content,
|
|
417
|
+
.pmx-ui-drawer-overlay,
|
|
418
|
+
.pmx-ui-drawer-popup,
|
|
419
|
+
[data-slot="switch-thumb"] {
|
|
420
|
+
transition: none;
|
|
421
|
+
}
|
|
422
|
+
}
|