polymorph-ui-components-mcp 0.0.1 → 0.1.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/build/docs/CHANGELOG.md +14 -0
- package/build/docs/Chat.md +211 -0
- package/build/docs/ChatBubble.md +199 -0
- package/build/docs/ChatComposer.md +96 -0
- package/build/docs/ChatHeader.md +74 -0
- package/build/docs/ChatMessage.md +108 -0
- package/build/docs/ChatMessageList.md +63 -0
- package/build/docs/ChatSuggestions.md +52 -0
- package/build/docs/ChatToolStatus.md +47 -0
- package/build/docs/Draggable.md +88 -0
- package/build/docs/GUIDELINES.md +67 -0
- package/build/docs/MediaPlayer.md +93 -0
- package/build/docs/MediaUpload.md +150 -0
- package/build/docs/Resizable.md +93 -0
- package/build/docs/Tabs.md +1 -1
- package/build/docs/_index.json +48 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ChatMessage
|
|
2
|
+
|
|
3
|
+
A single chat bubble. The primitive is the **party** — every message is from one of two sides: `'sender'` (your side; aligned right with an accent bubble) or `'responder'` (the other side; aligned left). `role` takes those primitives directly, plus recognized extensions that map onto them — `'user'` → sender, `'assistant'`/`'system'` → responder, and any custom string → responder. So LLM-style message arrays drop in unchanged while layout and styling stay driven by the two-party primitive (use the exported `partyOf(role)` to resolve a role yourself). Renders pre-sanitized `html` (e.g. rendered markdown) when provided, otherwise plain `content` text. While `streaming` with no content yet, it shows a typing indicator (the `LoadingDots` component). Avatar, a header row (author/time), and attachments are supplied as snippets, keeping the component free of any app-specific data shape. Basic markdown elements (`p`, `a`, `code`, `pre`, lists) are styled via `:global` so injected HTML looks right.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script>
|
|
9
|
+
import { ChatMessage } from 'polymorph-ui-components';
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<ChatMessage role="sender" content="Hello!" />
|
|
13
|
+
<ChatMessage role="responder" html="<p>Hi — how can I help?</p>" />
|
|
14
|
+
<ChatMessage role="responder" content="" streaming={true} />
|
|
15
|
+
|
|
16
|
+
<!-- LLM-style roles are recognized extensions and map onto the same two parties -->
|
|
17
|
+
<ChatMessage role="user" content="Renders identically to sender" />
|
|
18
|
+
<ChatMessage role="assistant" content="Renders identically to responder" />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Required | Default | Description |
|
|
24
|
+
| ----------- | ------------------------------------- | -------- | ------- | --------------------------------------------------------------- |
|
|
25
|
+
| role | `ChatRole` | Yes | `-` | The message's party or an extension mapping to one (`sender`/`user` → sender side; everything else → responder). Drives alignment and bubble styling. See Type Reference. |
|
|
26
|
+
| content | `string` | No | `''` | Plain-text content. Rendered as text unless `html` is set. |
|
|
27
|
+
| html | `string` | No | `-` | Pre-sanitized HTML rendered in place of `content`. |
|
|
28
|
+
| streaming | `boolean` | No | `false` | Shows a typing indicator when there is no content yet. |
|
|
29
|
+
| status | `'sending' \| 'sent' \| 'error'` | No | `-` | `error` tints the bubble with the error color. |
|
|
30
|
+
| avatar | `Snippet` | No | `-` | Avatar shown beside the bubble. |
|
|
31
|
+
| header | `Snippet` | No | `-` | Header row above the bubble (author name, timestamp, etc.). |
|
|
32
|
+
| attachments | `Snippet` | No | `-` | Content rendered below the bubble. |
|
|
33
|
+
| allowCopy | `boolean` | No | `false` | Show a built-in copy button in the hover actions row. |
|
|
34
|
+
| actions | `Snippet` | No | `-` | Extra custom actions appended to the actions row. |
|
|
35
|
+
| copyLabel / retryLabel / feedbackUpLabel / feedbackDownLabel | `string` | No | `…` | Aria-labels for the action buttons. |
|
|
36
|
+
| testId | `string` | No | `-` | `data-pw` on the root element. |
|
|
37
|
+
| classes | `string` | No | `-` | Class string on the root element. |
|
|
38
|
+
|
|
39
|
+
## Events
|
|
40
|
+
|
|
41
|
+
| Event | Type | Description |
|
|
42
|
+
| ---------- | ------------------------------------- | ------------------------------------------------------------ |
|
|
43
|
+
| onretry | `(() => void) \| null` | Enables the retry button. Fires when retry is pressed. |
|
|
44
|
+
| onfeedback | `((value: 'up' \| 'down') => void) \| null` | Enables the 👍/👎 buttons. Fires with the chosen rating. |
|
|
45
|
+
| oncopy | `(text: string) => void` | Fires after a successful copy, with the copied text. |
|
|
46
|
+
|
|
47
|
+
Actions appear on hover (and always on touch devices) below the bubble; the copy button briefly shows a checkmark on success. Actions (copy/retry/feedback) apply to **responder** messages.
|
|
48
|
+
|
|
49
|
+
## Type Reference
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// The primitive — every message is from one of two sides of the conversation.
|
|
53
|
+
type ChatParty = 'sender' | 'responder';
|
|
54
|
+
|
|
55
|
+
// Roles are the primitives plus recognized extensions; custom strings are allowed.
|
|
56
|
+
type ChatRole = ChatParty | 'user' | 'assistant' | 'system' | (string & {});
|
|
57
|
+
|
|
58
|
+
// Resolve any role to its party (exported from the package):
|
|
59
|
+
partyOf(role: ChatRole): ChatParty; // 'sender' | 'user' → 'sender'; everything else → 'responder'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## CSS Variables
|
|
63
|
+
|
|
64
|
+
| Variable | Default | CSS Property | Description |
|
|
65
|
+
| ------------------------------------- | ------------- | -------------- | -------------------------------------------- |
|
|
66
|
+
| `--chat-message-max-width` | `82%` | max-width | Max width of the message. |
|
|
67
|
+
| `--chat-message-margin` | `0` | margin | Margin around the message. |
|
|
68
|
+
| `--chat-message-gap` | `10px` | gap | Gap between avatar and bubble. |
|
|
69
|
+
| `--chat-message-content-gap` | `6px` | gap | Gap between header, bubble, attachments. |
|
|
70
|
+
| `--chat-message-header-font-size` | `0.75rem` | font-size | Header row font size. |
|
|
71
|
+
| `--chat-message-header-color` | `#71717a` | color | Header row color. |
|
|
72
|
+
| `--chat-message-bubble-padding` | `9px 13px` | padding | Bubble padding. |
|
|
73
|
+
| `--chat-message-bubble-border-radius` | `16px` | border-radius | Bubble corner rounding. |
|
|
74
|
+
| `--chat-message-font-size` | `0.9375rem` | font-size | Bubble text size. |
|
|
75
|
+
| `--chat-message-line-height` | `1.5` | line-height | Bubble line height. |
|
|
76
|
+
| `--chat-message-color` | `#27272a` | color | Default bubble text color. |
|
|
77
|
+
| `--chat-message-background` | `#f4f4f5` | background | Default bubble background. |
|
|
78
|
+
| `--chat-message-border` | `none` | border | Default bubble border. |
|
|
79
|
+
| `--chat-message-box-shadow` | `none` | box-shadow | Default bubble shadow. |
|
|
80
|
+
| `--chat-message-sender-color` | `#ffffff` | color | Sender bubble text color. |
|
|
81
|
+
| `--chat-message-sender-background` | `#18181b` | background | Sender bubble background. |
|
|
82
|
+
| `--chat-message-sender-border` | `none` | border | Sender bubble border. |
|
|
83
|
+
| `--chat-message-sender-border-radius` | `16px` | border-radius | Sender bubble corner rounding. |
|
|
84
|
+
| `--chat-message-responder-color` | `#27272a` | color | Responder bubble text color. |
|
|
85
|
+
| `--chat-message-responder-background` | `transparent` | background | Responder bubble background. |
|
|
86
|
+
| `--chat-message-responder-border` | `none` | border | Responder bubble border. |
|
|
87
|
+
| `--chat-message-responder-padding` | `2px 0` | padding | Responder bubble padding. |
|
|
88
|
+
| `--chat-message-error-color` | `#e0334b` | color | Bubble color when `status` is `error`. |
|
|
89
|
+
| `--chat-message-attachments-gap` | `8px` | gap | Gap between attachments. |
|
|
90
|
+
| `--chat-message-attachments-margin` | `4px 0 0 0` | margin | Margin above attachments. |
|
|
91
|
+
| `--chat-message-link-color` | `#6d28d9` | color | Link color inside rendered HTML. |
|
|
92
|
+
| `--chat-message-code-font-family` | `ui-monospace, monospace` | font-family | Inline/code-block font. |
|
|
93
|
+
| `--chat-message-code-background` | `rgba(0,0,0,0.05)` | background | Inline code background. |
|
|
94
|
+
| `--chat-message-pre-background` | `rgba(0,0,0,0.05)` | background | Code-block background. |
|
|
95
|
+
| `--chat-message-paragraph-margin` | `0 0 0.5em 0` | margin | Paragraph spacing inside rendered HTML. |
|
|
96
|
+
| `--chat-message-actions-gap` | `2px` | gap | Gap between action buttons. |
|
|
97
|
+
| `--chat-message-actions-opacity` | `0` | opacity | Resting opacity of the actions row (revealed on hover). |
|
|
98
|
+
| `--chat-message-action-size` | `28px` | height/width | Size of each action button. |
|
|
99
|
+
| `--chat-message-action-color` | `#71717a` | color | Icon color of action buttons. |
|
|
100
|
+
| `--chat-message-action-hover-background-color` | `#f4f4f5` | background | Action button hover background. |
|
|
101
|
+
|
|
102
|
+
## Web Component
|
|
103
|
+
|
|
104
|
+
Tag: `<pui-chat-message>`
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<pui-chat-message role="sender" content="Hello!"></pui-chat-message>
|
|
108
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ChatMessageList
|
|
2
|
+
|
|
3
|
+
A scrollable, auto-scrolling container for a conversation. It renders each message with `ChatMessage` by default, or a fully custom `message` snippet (use this to add avatars, attachments, or timestamps). When there are no messages, the `empty` snippet is shown. **Smart auto-scroll** keeps the latest content in view only while you're already near the bottom — if you scroll up to read history it won't yank you down, and a **jump-to-latest** button appears instead. Opt-in message actions (`allowCopy`, `onretry`, `onfeedback`) are applied to the default-rendered messages: copy and feedback on assistant messages, retry on the most recent assistant message. Implemented with a Svelte action (no effects), respecting `prefers-reduced-motion`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script>
|
|
9
|
+
import { ChatMessageList } from 'polymorph-ui-components';
|
|
10
|
+
|
|
11
|
+
let messages = $state([
|
|
12
|
+
{ id: '1', role: 'user', content: 'Hi' },
|
|
13
|
+
{ id: '2', role: 'assistant', content: 'Hello!' }
|
|
14
|
+
]);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<ChatMessageList {messages} />
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Required | Default | Description |
|
|
23
|
+
| ---------- | ----------------------------- | -------- | ------- | --------------------------------------------------------------- |
|
|
24
|
+
| messages | `ChatMessageData[]` | Yes | `-` | Messages to render. |
|
|
25
|
+
| autoscroll | `boolean` | No | `true` | Auto-scroll to the latest message as content changes. |
|
|
26
|
+
| message | `Snippet<[ChatMessageData]>` | No | `-` | Custom per-message rendering; overrides the default bubble. |
|
|
27
|
+
| empty | `Snippet` | No | `-` | Shown when there are no messages. |
|
|
28
|
+
| jumpLabel | `string` | No | `'Jump to latest'` | Aria-label for the jump-to-latest button. |
|
|
29
|
+
| jumpIcon | `Snippet` | No | `-` | Custom jump-to-latest icon. Falls back to a built-in asset. |
|
|
30
|
+
| allowCopy | `boolean` | No | `false` | Show copy buttons on assistant messages (default rendering). |
|
|
31
|
+
| testId | `string` | No | `-` | `data-pw` on the root element. |
|
|
32
|
+
| classes | `string` | No | `-` | Class string on the root element. |
|
|
33
|
+
|
|
34
|
+
## Events
|
|
35
|
+
|
|
36
|
+
| Event | Type | Description |
|
|
37
|
+
| ---------- | ---------------------------------------------------------- | --------------------------------------------------- |
|
|
38
|
+
| onretry | `() => void` | Enables retry on the most recent assistant message. |
|
|
39
|
+
| onfeedback | `(value: 'up' \| 'down', message: ChatMessageData) => void`| Enables feedback on assistant messages. |
|
|
40
|
+
|
|
41
|
+
## CSS Variables
|
|
42
|
+
|
|
43
|
+
| Variable | Default | CSS Property | Description |
|
|
44
|
+
| ------------------------------------- | -------------- | --------------- | ------------------------------------ |
|
|
45
|
+
| `--chat-message-list-gap` | `1rem` | gap | Gap between messages. |
|
|
46
|
+
| `--chat-message-list-padding` | `0.75rem 1.5rem` | padding | Padding of the list. |
|
|
47
|
+
| `--chat-message-list-scroll-behavior` | `smooth` | scroll-behavior | Scroll behavior (auto when reduced motion). |
|
|
48
|
+
| `--chat-message-list-jump-size` | `36px` | height/width | Size of the jump-to-latest button. |
|
|
49
|
+
| `--chat-message-list-jump-bottom` | `8px` | bottom | Sticky offset of the jump button. |
|
|
50
|
+
| `--chat-message-list-jump-background-color` | `#ffffff` | background | Jump button background. |
|
|
51
|
+
| `--chat-message-list-jump-color` | `#52525b` | color | Jump button icon color. |
|
|
52
|
+
| `--chat-message-list-jump-border` | `1px solid #e4e4e7` | border | Jump button border. |
|
|
53
|
+
| `--chat-message-list-jump-box-shadow` | `0 4px 12px rgba(0,0,0,0.12)` | box-shadow | Jump button shadow. |
|
|
54
|
+
|
|
55
|
+
## Web Component
|
|
56
|
+
|
|
57
|
+
Tag: `<pui-chat-message-list>`
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<pui-chat-message-list></pui-chat-message-list>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Set `.messages` via JavaScript.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ChatSuggestions
|
|
2
|
+
|
|
3
|
+
A row of tappable prompt chips (the `Pill` component) — typically shown on an empty conversation to seed the first message. Each item can be a plain string or an object with a display `label` and an underlying `value`; selecting a chip fires `onselect` with the value and index.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script>
|
|
9
|
+
import { ChatSuggestions } from 'polymorph-ui-components';
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<ChatSuggestions
|
|
13
|
+
items={['Track my order', 'Return policy?', { label: 'Talk to a human', value: 'handoff' }]}
|
|
14
|
+
onselect={(value) => console.log(value)}
|
|
15
|
+
/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Props
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Required | Default | Description |
|
|
21
|
+
| -------- | ------------------- | -------- | ------- | ----------------------------------------------------------- |
|
|
22
|
+
| items | `ChatSuggestion[]` | Yes | `-` | Chips. `ChatSuggestion = string \| { label: string; value?: string }`. |
|
|
23
|
+
| disabled | `boolean` | No | `false` | Disable all chips. |
|
|
24
|
+
| testId | `string` | No | `-` | `data-pw` on the root element. |
|
|
25
|
+
| classes | `string` | No | `-` | Class string on the root element. |
|
|
26
|
+
|
|
27
|
+
## Events
|
|
28
|
+
|
|
29
|
+
| Event | Type | Description |
|
|
30
|
+
| -------- | ------------------------------------- | ------------------------------------------------ |
|
|
31
|
+
| onselect | `(value: string, index: number) => void` | Fires when a chip is selected. |
|
|
32
|
+
|
|
33
|
+
## CSS Variables
|
|
34
|
+
|
|
35
|
+
| Variable | Default | CSS Property | Description |
|
|
36
|
+
| ------------------------------- | ------- | ------------ | ---------------------------- |
|
|
37
|
+
| `--chat-suggestions-width` | `100%` | width | Width of the row. |
|
|
38
|
+
| `--chat-suggestions-gap` | `8px` | gap | Gap between chips. |
|
|
39
|
+
| `--chat-suggestions-flex-wrap` | `wrap` | flex-wrap | Wrapping behavior. |
|
|
40
|
+
| `--chat-suggestions-padding` | `0` | padding | Padding around the row. |
|
|
41
|
+
|
|
42
|
+
Chips are `Pill` instances — theme them with the `--pill-*` variables.
|
|
43
|
+
|
|
44
|
+
## Web Component
|
|
45
|
+
|
|
46
|
+
Tag: `<pui-chat-suggestions>`
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<pui-chat-suggestions></pui-chat-suggestions>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Set `.items` and `.onselect` via JavaScript.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# ChatToolStatus
|
|
2
|
+
|
|
3
|
+
A compact status pill that shows what the assistant is doing — "Searching the catalog…", "Calling a tool…", "Thinking…". It pairs a spinner (the `Loader` component) with a label, and the leading indicator can be replaced with a snippet. Render it above the composer or floating over the conversation while `toolStatus` is non-null.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script>
|
|
9
|
+
import { ChatToolStatus } from 'polymorph-ui-components';
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<ChatToolStatus label="Searching the catalog…" />
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Required | Default | Description |
|
|
18
|
+
| ------- | --------- | -------- | ------- | ----------------------------------------------------------- |
|
|
19
|
+
| label | `string` | Yes | `-` | Status text. |
|
|
20
|
+
| icon | `Snippet` | No | `-` | Leading indicator. Falls back to the built-in spinner. |
|
|
21
|
+
| testId | `string` | No | `-` | `data-pw` on the root element. |
|
|
22
|
+
| classes | `string` | No | `-` | Class string on the root element. |
|
|
23
|
+
|
|
24
|
+
## CSS Variables
|
|
25
|
+
|
|
26
|
+
| Variable | Default | CSS Property | Description |
|
|
27
|
+
| ------------------------------------- | -------------------------- | -------------- | ---------------------------- |
|
|
28
|
+
| `--chat-tool-status-gap` | `8px` | gap | Gap between spinner and label.|
|
|
29
|
+
| `--chat-tool-status-padding` | `8px 14px` | padding | Pill padding. |
|
|
30
|
+
| `--chat-tool-status-background` | `#ffffff` | background | Pill background. |
|
|
31
|
+
| `--chat-tool-status-border` | `1px solid #e4e4e7` | border | Pill border. |
|
|
32
|
+
| `--chat-tool-status-border-radius` | `999px` | border-radius | Pill corner rounding. |
|
|
33
|
+
| `--chat-tool-status-box-shadow` | `0 6px 20px rgba(0,0,0,0.08)` | box-shadow | Pill shadow. |
|
|
34
|
+
| `--chat-tool-status-color` | `#52525b` | color | Label color. |
|
|
35
|
+
| `--chat-tool-status-font-size` | `0.85rem` | font-size | Label font size. |
|
|
36
|
+
| `--chat-tool-status-font-weight` | `500` | font-weight | Label weight. |
|
|
37
|
+
| `--chat-tool-status-max-width` | `100%` | max-width | Max pill width. |
|
|
38
|
+
| `--chat-tool-status-indicator-color` | `currentColor` | color | Spinner color. |
|
|
39
|
+
| `--chat-tool-status-spinner-size` | `14px` | height/width | Spinner size. |
|
|
40
|
+
|
|
41
|
+
## Web Component
|
|
42
|
+
|
|
43
|
+
Tag: `<pui-chat-tool-status>`
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<pui-chat-tool-status label="Searching…"></pui-chat-tool-status>
|
|
47
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Draggable
|
|
2
|
+
|
|
3
|
+
A wrapper that lets the user drag its content to reposition it. Dragging applies a `transform: translate(x, y)` (so it never affects document flow) and updates the bindable `x`/`y`, which makes the position observable and persistable. You can drag by the whole element or restrict to a `handle` selector, constrain to one `axis`, and keep it on screen with `bounds`. The wrapper is also keyboard-movable — focus it and use the arrow keys (by `step`).
|
|
4
|
+
|
|
5
|
+
Drag changes position only, layered on top of the element's natural/CSS placement — so use it together with whatever positioning the element already has (static, `fixed`, etc.).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```svelte
|
|
10
|
+
<script>
|
|
11
|
+
import { Draggable } from 'polymorph-ui-components';
|
|
12
|
+
|
|
13
|
+
let x = $state(0);
|
|
14
|
+
let y = $state(0);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<Draggable bind:x bind:y handle=".title-bar">
|
|
18
|
+
<div class="window">
|
|
19
|
+
<div class="title-bar">Drag here</div>
|
|
20
|
+
<div class="content">…</div>
|
|
21
|
+
</div>
|
|
22
|
+
</Draggable>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Required | Default | Description |
|
|
28
|
+
| --------- | ------------------- | -------- | ---------------- | ------------------------------------------------------------------------ |
|
|
29
|
+
| x | `number` | No | `0` | Bindable. Horizontal translate offset in px. |
|
|
30
|
+
| y | `number` | No | `0` | Bindable. Vertical translate offset in px. |
|
|
31
|
+
| axis | `'both'\|'x'\|'y'` | No | `'both'` | Constrain movement to one axis. |
|
|
32
|
+
| handle | `string` | No | `-` | CSS selector; only drags starting within a matching element begin. Without it, the whole element drags (skipping native interactive elements). |
|
|
33
|
+
| bounds | `'viewport'\|null` | No | `null` | Keep the element within the viewport. |
|
|
34
|
+
| disabled | `boolean` | No | `false` | Disable dragging and keyboard movement. |
|
|
35
|
+
| step | `number` | No | `16` | Pixels per arrow-key press. |
|
|
36
|
+
| dragLabel | `string` | No | `'Drag to move'` | Aria-label for the draggable region. |
|
|
37
|
+
| children | `Snippet` | No | `-` | The content to make draggable. |
|
|
38
|
+
| testId | `string` | No | `-` | `data-pw` on the root element. |
|
|
39
|
+
| classes | `string` | No | `-` | Class string on the root element. |
|
|
40
|
+
|
|
41
|
+
## Events
|
|
42
|
+
|
|
43
|
+
| Event | Type | Description |
|
|
44
|
+
| ----------- | --------------------------------- | ------------------------------------ |
|
|
45
|
+
| ondragstart | `(pos: { x, y }) => void` | Fires when a drag begins. |
|
|
46
|
+
| ondrag | `(pos: { x, y }) => void` | Fires continuously during a drag. |
|
|
47
|
+
| ondragend | `(pos: { x, y }) => void` | Fires when a drag ends. |
|
|
48
|
+
|
|
49
|
+
## Keyboard Interactions
|
|
50
|
+
|
|
51
|
+
Focus the draggable region (focusable when not `disabled`) and move it with the keyboard:
|
|
52
|
+
|
|
53
|
+
| Key | Action |
|
|
54
|
+
| ---------------------------- | ------------------------------------------------------- |
|
|
55
|
+
| `Arrow Left` / `Arrow Right` | Move by `step` px horizontally (ignored when `axis="y"`).|
|
|
56
|
+
| `Arrow Up` / `Arrow Down` | Move by `step` px vertically (ignored when `axis="x"`). |
|
|
57
|
+
|
|
58
|
+
Arrow keys only move the wrapper when the wrapper itself holds focus, so interactive children keep their own keyboard behavior. Movement is clamped to the viewport when `bounds="viewport"`.
|
|
59
|
+
|
|
60
|
+
## Accessibility
|
|
61
|
+
|
|
62
|
+
The draggable region carries `aria-label={dragLabel}` (default `'Drag to move'`) and is keyboard-operable, so dragging is never the only way to reposition. With a `handle` set, only that region begins a drag; without one, drags that start on native interactive elements (buttons, links, inputs, selects, textareas) are ignored so those controls keep working.
|
|
63
|
+
|
|
64
|
+
## Type Reference
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
type DragAxis = 'both' | 'x' | 'y';
|
|
68
|
+
type DragPosition = { x: number; y: number };
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## CSS Variables
|
|
72
|
+
|
|
73
|
+
| Variable | Default | CSS Property | Description |
|
|
74
|
+
| --------------------------------- | ------------------- | ------------ | -------------------------------------------- |
|
|
75
|
+
| `--draggable-width` | `fit-content` | width | Width of the wrapper. |
|
|
76
|
+
| `--draggable-height` | `fit-content` | height | Height of the wrapper. |
|
|
77
|
+
| `--draggable-cursor` | `grab` | cursor | Cursor at rest (set to `default` when using a `handle`). |
|
|
78
|
+
| `--draggable-cursor-active` | `grabbing` | cursor | Cursor while dragging. |
|
|
79
|
+
| `--draggable-focus-outline` | `2px solid #3b5bdb` | outline | Focus ring when keyboard-focused. |
|
|
80
|
+
| `--draggable-focus-outline-offset`| `2px` | outline-offset | Focus ring offset. |
|
|
81
|
+
|
|
82
|
+
## Web Component
|
|
83
|
+
|
|
84
|
+
Tag: `<pui-draggable>`
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<pui-draggable handle=".title-bar"></pui-draggable>
|
|
88
|
+
```
|
package/build/docs/GUIDELINES.md
CHANGED
|
@@ -378,3 +378,70 @@ The `classes` prop is the recommended way to implement theme variants (primary,
|
|
|
378
378
|
```
|
|
379
379
|
|
|
380
380
|
This approach keeps components free from hardcoded design presets while giving consumers full control over theming. Multiple variant systems can coexist, and variants compose naturally with space-separated class names.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## 10. No Inline SVG — Import Assets and Expose Icon Overrides
|
|
385
|
+
|
|
386
|
+
Do not hardcode `<svg>…</svg>` markup inside a component template. Inline SVG bloats the template, can't be themed or swapped by consumers, and duplicates markup across states.
|
|
387
|
+
|
|
388
|
+
Instead, follow the two-part icon pattern used across the library (`Tabs`, `Checkbox`, `Pill`, `ThemeSwitcher`, …):
|
|
389
|
+
|
|
390
|
+
### 1. Default icon — import an asset as a raw string and render it
|
|
391
|
+
|
|
392
|
+
Add the icon under `src/lib/assets/<name>.svg` (use `stroke="currentColor"` / `fill="currentColor"` so it inherits color via CSS), import it with the `?raw` suffix, and render it with `{@html}`.
|
|
393
|
+
|
|
394
|
+
```svelte
|
|
395
|
+
<!-- Bad — inline SVG in the template -->
|
|
396
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z" /></svg>
|
|
397
|
+
|
|
398
|
+
<!-- Good — asset imported as a raw string -->
|
|
399
|
+
<script lang="ts">
|
|
400
|
+
import playSvg from '$lib/assets/play.svg?raw';
|
|
401
|
+
</script>
|
|
402
|
+
|
|
403
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
404
|
+
{@html playSvg}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 2. Override — expose a `Snippet` prop and prefer it when provided
|
|
408
|
+
|
|
409
|
+
Let consumers replace the icon by passing a snippet. Type the prop as `Snippet` in `properties.ts`, and render the snippet when present, falling back to the default asset otherwise.
|
|
410
|
+
|
|
411
|
+
```svelte
|
|
412
|
+
<script lang="ts">
|
|
413
|
+
import playSvg from '$lib/assets/play.svg?raw';
|
|
414
|
+
let { playIcon /* ?: Snippet */ } = $props();
|
|
415
|
+
</script>
|
|
416
|
+
|
|
417
|
+
{#if typeof playIcon === 'function'}
|
|
418
|
+
{@render playIcon()}
|
|
419
|
+
{:else}
|
|
420
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
421
|
+
{@html playSvg}
|
|
422
|
+
{/if}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
// properties.ts
|
|
427
|
+
import type { Snippet } from 'svelte';
|
|
428
|
+
|
|
429
|
+
export type OptionalProperties = {
|
|
430
|
+
/** Snippet rendering a custom play icon. Falls back to the built-in asset when omitted. */
|
|
431
|
+
playIcon?: Snippet;
|
|
432
|
+
};
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Sizing
|
|
436
|
+
|
|
437
|
+
Because `{@html}` content and snippet content are not scoped by Svelte, size icons with a `:global()` selector scoped under the icon's container, exposing a CSS variable so consumers can resize:
|
|
438
|
+
|
|
439
|
+
```css
|
|
440
|
+
.control :global(svg),
|
|
441
|
+
.control :global(img) {
|
|
442
|
+
height: var(--component-control-icon-size, 100%);
|
|
443
|
+
width: var(--component-control-icon-size, 100%);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
In the web-component wrapper, declare icon props as `{ type: 'Object' }` (snippets are not attribute-serializable); custom-element consumers fall back to the built-in asset defaults.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# MediaPlayer
|
|
2
|
+
|
|
3
|
+
An image or video player with a hover-revealed control overlay. For `type="image"` it renders the source through the `Img` component (with optional `fallback`). For `type="video"` it renders the video plus a centered play/pause control and a bottom-aligned mute/unmute control that appear on hover; the controls reuse the `Button` component. Built-in icons (imported from assets) are used for the controls and can be replaced with snippet props. The `playing` and `muted` states are bindable. Set `controls` to fall back to the browser's native video controls (the custom overlay is then hidden). Unstyled by default — every dimension, the overlay color, and the control appearance are driven by CSS variables.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script>
|
|
9
|
+
import { MediaPlayer } from 'polymorph-ui-components';
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<MediaPlayer type="image" src="/photo.jpg" alt="A photo" />
|
|
13
|
+
|
|
14
|
+
<MediaPlayer type="video" src="/clip.mp4" />
|
|
15
|
+
|
|
16
|
+
<!-- Swap a control icon with your own markup -->
|
|
17
|
+
<MediaPlayer type="video" src="/clip.mp4">
|
|
18
|
+
{#snippet playIcon()}
|
|
19
|
+
<img src="/icons/play.svg" alt="" />
|
|
20
|
+
{/snippet}
|
|
21
|
+
</MediaPlayer>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Required | Default | Description |
|
|
27
|
+
| ---------- | --------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| src | `string` | Yes | `-` | URL of the image or video to display. |
|
|
29
|
+
| type | `'image' \| 'video'` | Yes | `-` | Whether the source is rendered as an image or a video with controls. |
|
|
30
|
+
| alt | `string` | No | `''` | Alternative text for the image (ignored for video). |
|
|
31
|
+
| fallback | `string` | No | `-` | Fallback image URL used (via the `Img` component) if `src` fails to load. Image type only. |
|
|
32
|
+
| autoplay | `boolean` | No | `true` | Whether the video begins playing automatically (video only). |
|
|
33
|
+
| loop | `boolean` | No | `false` | Whether the video restarts when it ends (video only). |
|
|
34
|
+
| controls | `boolean` | No | `false` | When true, uses the browser's native video controls and hides the custom overlay (video only). |
|
|
35
|
+
| playing | `boolean` | No | `true` | Bindable. Reflects whether the video is currently playing. Updates the centered control icon. |
|
|
36
|
+
| muted | `boolean` | No | `true` | Bindable. Reflects whether the video audio is muted. Updates the mute control icon. |
|
|
37
|
+
| playIcon | `Snippet` | No | `-` | Snippet rendering a custom play-control icon. Falls back to the built-in asset when omitted. |
|
|
38
|
+
| pauseIcon | `Snippet` | No | `-` | Snippet rendering a custom pause-control icon. Falls back to the built-in asset when omitted. |
|
|
39
|
+
| muteIcon | `Snippet` | No | `-` | Snippet rendering a custom muted-control icon. Falls back to the built-in asset when omitted. |
|
|
40
|
+
| unmuteIcon | `Snippet` | No | `-` | Snippet rendering a custom unmuted-control icon. Falls back to the built-in asset when omitted. |
|
|
41
|
+
| testId | `string` | No | `-` | Test selector value applied as `data-pw` on the outermost element. |
|
|
42
|
+
| classes | `string` | No | `-` | CSS class string applied to the top-level element. Useful for theming via CSS variable override classes. |
|
|
43
|
+
|
|
44
|
+
## Events
|
|
45
|
+
|
|
46
|
+
| Event | Type | Description |
|
|
47
|
+
| -------------- | -------------------------- | ------------------------------------------------------------------- |
|
|
48
|
+
| onplay | `(event: Event) => void` | Fires when video playback starts. `playing` is set to true first. |
|
|
49
|
+
| onpause | `(event: Event) => void` | Fires when video playback pauses. `playing` is set to false first. |
|
|
50
|
+
| onvolumechange | `(muted: boolean) => void` | Fires when the mute control is toggled, with the new muted state. |
|
|
51
|
+
|
|
52
|
+
## CSS Variables
|
|
53
|
+
|
|
54
|
+
Override these custom properties to theme the component.
|
|
55
|
+
|
|
56
|
+
| Variable | Default | CSS Property | Description |
|
|
57
|
+
| ---------------------------------------------- | ----------------------------- | ---------------- | ------------------------------------------------------- |
|
|
58
|
+
| `--media-player-height` | `400px` | height | Height of the player container. |
|
|
59
|
+
| `--media-player-width` | `fit-content` | width | Width of the player container. |
|
|
60
|
+
| `--media-player-border-radius` | `14px` | border-radius | Corner rounding of the player container. |
|
|
61
|
+
| `--media-player-overflow` | `hidden` | overflow | Overflow behavior of the container. |
|
|
62
|
+
| `--media-player-background` | `transparent` | background | Background behind the media. |
|
|
63
|
+
| `--media-player-media-height` | `100%` | height | Height of the image/video element. |
|
|
64
|
+
| `--media-player-media-width` | `fit-content` | width | Width of the image/video element. |
|
|
65
|
+
| `--media-player-media-object-fit` | `contain` | object-fit | Object-fit of the media element. |
|
|
66
|
+
| `--media-player-media-border-radius` | `inherit` | border-radius | Corner rounding of the media element. |
|
|
67
|
+
| `--media-player-media-cursor` | `pointer` | cursor | Cursor over the video element. |
|
|
68
|
+
| `--media-player-overlay-z-index` | `20` | z-index | Stacking order of the control overlay. |
|
|
69
|
+
| `--media-player-overlay-color` | `transparent` | background-color | Overlay background color at rest. |
|
|
70
|
+
| `--media-player-overlay-hover-color` | `#0000004d` | background-color | Overlay background color on hover. |
|
|
71
|
+
| `--media-player-overlay-transition` | `background-color 0.2s ease` | transition | Transition applied to the overlay. |
|
|
72
|
+
| `--media-player-center-controls-visibility` | `hidden` | visibility | Resting visibility of the centered control. |
|
|
73
|
+
| `--media-player-bottom-controls-visibility` | `hidden` | visibility | Resting visibility of the bottom control. |
|
|
74
|
+
| `--media-player-bottom-controls-justify` | `flex-end` | justify-content | Horizontal alignment of the bottom control. |
|
|
75
|
+
| `--media-player-bottom-controls-padding` | `12px` | padding | Padding around the bottom control row. |
|
|
76
|
+
| `--media-player-control-padding` | `0px` | padding | Inner padding of each control button. |
|
|
77
|
+
| `--media-player-control-border` | `none` | border | Border of each control button. |
|
|
78
|
+
| `--media-player-control-border-radius` | `50%` | border-radius | Corner rounding of each control button. |
|
|
79
|
+
| `--media-player-control-background-color` | `transparent` | background-color | Background color of each control button. |
|
|
80
|
+
| `--media-player-control-color` | `#ffffff` | color | Icon color of each control button. |
|
|
81
|
+
| `--media-player-control-hover-background-color`| `var(--media-player-control-background-color)` | background-color | Control background color on hover. |
|
|
82
|
+
| `--media-player-control-hover-color` | `var(--media-player-control-color)` | color | Control icon color on hover. |
|
|
83
|
+
| `--media-player-center-control-size` | `64px` | height/width | Size of the centered play/pause control. |
|
|
84
|
+
| `--media-player-bottom-control-size` | `24px` | height/width | Size of the bottom mute control. |
|
|
85
|
+
| `--media-player-control-icon-size` | `100%` | height/width | Size of the icon inside each control (relative to the control). |
|
|
86
|
+
|
|
87
|
+
## Web Component
|
|
88
|
+
|
|
89
|
+
Tag: `<pui-media-player>`
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<pui-media-player type="video" src="/clip.mp4"></pui-media-player>
|
|
93
|
+
```
|