@sableui/react 0.14.0 → 0.16.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/CHANGELOG.md +131 -0
- package/DESIGN.md +3 -1
- package/bin/sable-lint.mjs +5 -1
- package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
- package/dist/blocks/index.d.ts +12 -1
- package/dist/blocks/index.js +4 -0
- package/dist/blocks/message-reactions-block.js +87 -0
- package/dist/blocks/message-reactions-block.meta.js +23 -0
- package/dist/blocks/wallet-block.js +2 -2
- package/dist/components/app-shell/app-shell-frame.js +16 -0
- package/dist/components/app-shell/app-shell-main.js +12 -0
- package/dist/components/app-shell/app-shell.js +28 -4
- package/dist/components/app-shell/app-shell.meta.js +88 -4
- package/dist/components/area-chart/area-chart.meta.js +6 -1
- package/dist/components/attachment/attachment.meta.js +2 -1
- package/dist/components/attachment/attachment.variants.js +1 -1
- package/dist/components/badge/badge.meta.js +8 -1
- package/dist/components/badge/badge.variants.js +7 -7
- package/dist/components/banner/banner.meta.js +6 -1
- package/dist/components/banner/banner.variants.js +5 -5
- package/dist/components/bar-chart/bar-chart.meta.js +6 -1
- package/dist/components/bubble/bubble.js +105 -5
- package/dist/components/bubble/bubble.meta.js +42 -6
- package/dist/components/bubble/bubble.variants.js +1 -1
- package/dist/components/bubble/index.js +2 -0
- package/dist/components/caret-menu/caret-menu.js +5 -6
- package/dist/components/chat-composer/chat-composer.meta.js +2 -2
- package/dist/components/chat-composer/chat-composer.variants.js +1 -1
- package/dist/components/color-field/color-field.js +6 -8
- package/dist/components/combobox/combobox.js +27 -5
- package/dist/components/combobox/combobox.meta.js +10 -2
- package/dist/components/command-menu/command-menu.js +14 -3
- package/dist/components/command-menu/command-menu.meta.js +9 -1
- package/dist/components/date-picker/date-picker.meta.js +2 -2
- package/dist/components/feedback/feedback.js +9 -2
- package/dist/components/feedback/feedback.meta.js +8 -1
- package/dist/components/gradient-art/gradient-art.js +8 -5
- package/dist/components/gradient-art/gradient-art.meta.js +35 -2
- package/dist/components/input/input.meta.js +11 -3
- package/dist/components/input-group/input-group.js +1 -1
- package/dist/components/input-group/input-group.meta.js +1 -1
- package/dist/components/like-button/index.js +7 -0
- package/dist/components/like-button/like-button.js +89 -0
- package/dist/components/like-button/like-button.meta.js +132 -0
- package/dist/components/line-chart/line-chart.meta.js +6 -1
- package/dist/components/menu/menu.js +1 -1
- package/dist/components/meter/meter.js +53 -7
- package/dist/components/meter/meter.meta.js +51 -4
- package/dist/components/meter/meter.variants.js +43 -7
- package/dist/components/note/note.meta.js +5 -1
- package/dist/components/note/note.variants.js +4 -4
- package/dist/components/number-field/number-field.meta.js +2 -2
- package/dist/components/otp-field/otp-field.meta.js +2 -2
- package/dist/components/panel-group/panel-group.js +5 -8
- package/dist/components/pie-chart/pie-chart.meta.js +6 -1
- package/dist/components/radar-chart/radar-chart.meta.js +6 -1
- package/dist/components/radial-chart/radial-chart.meta.js +6 -1
- package/dist/components/reaction-picker/index.js +13 -0
- package/dist/components/reaction-picker/reaction-picker.js +104 -0
- package/dist/components/reaction-picker/reaction-picker.meta.js +156 -0
- package/dist/components/search-input/search-input.meta.js +11 -3
- package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
- package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.js +5 -4
- package/dist/components/sidebar/sidebar.js +16 -1
- package/dist/components/sidebar/sidebar.meta.js +7 -2
- package/dist/components/sparkline/sparkline.meta.js +8 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -1
- package/dist/components/status-dot/status-dot.variants.js +7 -7
- package/dist/components/stepper/stepper.meta.js +2 -1
- package/dist/components/stepper/stepper.variants.js +1 -1
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/textarea/textarea.js +5 -0
- package/dist/components/textarea/textarea.meta.js +3 -3
- package/dist/components/toast/toast.js +25 -13
- package/dist/components/toast/toast.meta.js +7 -1
- package/dist/file-hashes.json +112 -100
- package/dist/foundations/accessibility.js +5 -0
- package/dist/foundations/motion.js +21 -1
- package/dist/icon-sets/reicon.js +11 -1
- package/dist/index.d.ts +425 -84
- package/dist/index.js +11 -1
- package/dist/lib/chart/types.js +8 -8
- package/dist/lib/chart/use-arrival.js +5 -4
- package/dist/lib/field-shell.js +1 -1
- package/dist/lib/highlight-glide.js +6 -6
- package/dist/lib/scrub-gesture.js +6 -6
- package/dist/lib/taxonomy.js +2 -0
- package/dist/lib/use-caret-trigger.js +11 -18
- package/dist/lib/use-match-highlight.js +69 -0
- package/dist/lib/use-smooth-wheel.js +10 -8
- package/dist/motion/Burst.js +79 -0
- package/dist/motion/index.js +2 -0
- package/dist/presets/index.d.ts +13 -2
- package/dist/presets/index.js +17 -1
- package/dist/styles.css +241 -0
- package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
- package/dist/themes/_contract.js +14 -0
- package/dist/themes/atelier/atelier.css +18 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/contrast.js +48 -1
- package/dist/themes/ember/ember.css +18 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +18 -0
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +18 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -0
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -0
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +18 -0
- package/llms/blocks/message-reactions-block.md +116 -0
- package/llms/blocks/wallet-block.md +5 -4
- package/llms/components/app-shell.md +72 -3
- package/llms/components/area-chart.md +1 -1
- package/llms/components/attachment.md +1 -1
- package/llms/components/badge.md +1 -1
- package/llms/components/banner.md +1 -1
- package/llms/components/bar-chart.md +1 -1
- package/llms/components/bubble.md +17 -7
- package/llms/components/chat-composer.md +1 -1
- package/llms/components/combobox.md +2 -1
- package/llms/components/command-menu.md +3 -2
- package/llms/components/date-picker.md +1 -1
- package/llms/components/feedback.md +2 -1
- package/llms/components/gradient-art.md +28 -1
- package/llms/components/input-group.md +1 -1
- package/llms/components/input.md +4 -2
- package/llms/components/like-button.md +91 -0
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +33 -4
- package/llms/components/note.md +1 -1
- package/llms/components/number-field.md +1 -1
- package/llms/components/otp-field.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/reaction-picker.md +115 -0
- package/llms/components/search-input.md +20 -2
- package/llms/components/segmented-bar.md +1 -1
- package/llms/components/select.md +1 -1
- package/llms/components/sidebar.md +3 -2
- package/llms/components/sparkline.md +1 -1
- package/llms/components/status-dot.md +1 -1
- package/llms/components/stepper.md +1 -1
- package/llms/components/table.md +2 -2
- package/llms/components/textarea.md +2 -2
- package/llms/components/toast.md +2 -1
- package/llms/foundations/accessibility.md +1 -0
- package/llms/foundations/motion.md +18 -2
- package/llms/reference/token-intent.md +22 -2
- package/llms/reference/utilities.md +9 -3
- package/llms-full.txt +596 -61
- package/llms.txt +10 -7
- package/package.json +1 -1
- package/registry.json +968 -68
- package/scripts/lint-rules.mjs +6 -3
- package/targets/ios.json +18 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/reaction-picker/reaction-picker.tsx
|
|
4
|
+
import { Menu as BaseMenu } from "@base-ui/react/menu";
|
|
5
|
+
import { cn } from "../../lib/cn.js";
|
|
6
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
7
|
+
import { Toolbar, ToolbarButton } from "../toolbar/toolbar.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var DEFAULT_REACTIONS = [
|
|
10
|
+
{ emoji: "\u{1F44D}", label: "Thumbs up" },
|
|
11
|
+
{ emoji: "\u2764\uFE0F", label: "Heart" },
|
|
12
|
+
{ emoji: "\u{1F602}", label: "Laughing" },
|
|
13
|
+
{ emoji: "\u{1F389}", label: "Party" },
|
|
14
|
+
{ emoji: "\u{1F62E}", label: "Surprised" },
|
|
15
|
+
{ emoji: "\u{1F622}", label: "Sad" },
|
|
16
|
+
{ emoji: "\u{1F64F}", label: "Thank you" }
|
|
17
|
+
];
|
|
18
|
+
var PRESSED = "aria-pressed:bg-primary/10 data-[checked]:bg-primary/10";
|
|
19
|
+
var MENU_CELL = `group/reaction grid size-(--control-md) shrink-0 cursor-pointer select-none place-items-center rounded-full outline-none sable-surface-interactive motion-interactive motion-press ${PRESSED}`;
|
|
20
|
+
function Glyph({ emoji, index }) {
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
"span",
|
|
23
|
+
{
|
|
24
|
+
"aria-hidden": true,
|
|
25
|
+
className: "motion-bloom",
|
|
26
|
+
style: { "--motion-bloom-delay": `${index * 24}ms` },
|
|
27
|
+
children: /* @__PURE__ */ jsx("span", { className: "block text-[calc(var(--control-md)*0.6)] leading-none transition-[scale] duration-(--motion-duration-fast) ease-standard group-hover/reaction:scale-125 group-focus-visible/reaction:scale-125 group-data-[highlighted]/reaction:scale-125", children: emoji })
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function ReactionPicker({
|
|
32
|
+
reactions = DEFAULT_REACTIONS,
|
|
33
|
+
selected = [],
|
|
34
|
+
onSelect,
|
|
35
|
+
motion,
|
|
36
|
+
className,
|
|
37
|
+
children,
|
|
38
|
+
"aria-label": ariaLabel = "Reactions",
|
|
39
|
+
...props
|
|
40
|
+
}) {
|
|
41
|
+
return /* @__PURE__ */ jsxs(
|
|
42
|
+
Toolbar,
|
|
43
|
+
{
|
|
44
|
+
"aria-label": ariaLabel,
|
|
45
|
+
"data-motion": motionAttribute(motion),
|
|
46
|
+
className: cn("gap-0.5", className),
|
|
47
|
+
...props,
|
|
48
|
+
children: [
|
|
49
|
+
reactions.map((reaction, index) => /* @__PURE__ */ jsx(
|
|
50
|
+
ToolbarButton,
|
|
51
|
+
{
|
|
52
|
+
size: "icon",
|
|
53
|
+
"aria-label": reaction.label,
|
|
54
|
+
"aria-pressed": selected.includes(reaction.emoji),
|
|
55
|
+
onClick: () => onSelect?.(reaction.emoji),
|
|
56
|
+
className: cn("group/reaction rounded-full", PRESSED),
|
|
57
|
+
children: /* @__PURE__ */ jsx(Glyph, { emoji: reaction.emoji, index })
|
|
58
|
+
},
|
|
59
|
+
reaction.emoji
|
|
60
|
+
)),
|
|
61
|
+
children
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
ReactionPicker.displayName = "ReactionPicker";
|
|
67
|
+
function MenuReactions({
|
|
68
|
+
reactions = DEFAULT_REACTIONS,
|
|
69
|
+
selected = [],
|
|
70
|
+
onSelect,
|
|
71
|
+
motion,
|
|
72
|
+
className,
|
|
73
|
+
"aria-label": ariaLabel = "Reactions",
|
|
74
|
+
...props
|
|
75
|
+
}) {
|
|
76
|
+
return /* @__PURE__ */ jsx(
|
|
77
|
+
BaseMenu.Group,
|
|
78
|
+
{
|
|
79
|
+
"aria-label": ariaLabel,
|
|
80
|
+
"data-motion": motionAttribute(motion),
|
|
81
|
+
className: cn("flex items-center gap-0.5", className),
|
|
82
|
+
...props,
|
|
83
|
+
children: reactions.map((reaction, index) => /* @__PURE__ */ jsx(
|
|
84
|
+
BaseMenu.CheckboxItem,
|
|
85
|
+
{
|
|
86
|
+
label: reaction.label,
|
|
87
|
+
"aria-label": reaction.label,
|
|
88
|
+
checked: selected.includes(reaction.emoji),
|
|
89
|
+
onCheckedChange: () => onSelect?.(reaction.emoji),
|
|
90
|
+
closeOnClick: true,
|
|
91
|
+
className: MENU_CELL,
|
|
92
|
+
children: /* @__PURE__ */ jsx(Glyph, { emoji: reaction.emoji, index })
|
|
93
|
+
},
|
|
94
|
+
reaction.emoji
|
|
95
|
+
))
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
MenuReactions.displayName = "MenuReactions";
|
|
100
|
+
export {
|
|
101
|
+
DEFAULT_REACTIONS,
|
|
102
|
+
MenuReactions,
|
|
103
|
+
ReactionPicker
|
|
104
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/components/reaction-picker/reaction-picker.meta.ts
|
|
2
|
+
var reactionPickerMeta = {
|
|
3
|
+
name: "ReactionPicker",
|
|
4
|
+
summary: "The quick row of emoji you react to a message with: a toolbar of large emoji toggles that bloom in one after another as it opens and magnify under the pointer, with MenuReactions as the same row inside a right-click menu.",
|
|
5
|
+
status: "stable",
|
|
6
|
+
keywords: [
|
|
7
|
+
"reaction",
|
|
8
|
+
"reactions",
|
|
9
|
+
"emoji",
|
|
10
|
+
"emoji picker",
|
|
11
|
+
"react",
|
|
12
|
+
"tapback",
|
|
13
|
+
"quick reactions",
|
|
14
|
+
"message actions",
|
|
15
|
+
"chat"
|
|
16
|
+
],
|
|
17
|
+
usage: {
|
|
18
|
+
description: "Use ReactionPicker wherever a reader chooses a reaction for something: in a Popover from an add-reaction control (BubbleReactionAdd is that, ready-made), or as a hover bar over a message. Use MenuReactions for the same row as the first group of the menu a message opens on right-click or long-press, above Reply and Copy. Both report which emoji the reader has already used, so choosing one again removes it.",
|
|
19
|
+
bestPractices: [
|
|
20
|
+
{
|
|
21
|
+
guidance: "do",
|
|
22
|
+
description: 'Keep the quick set short \u2014 six to eight. It is the reactions people actually use; a full emoji catalogue belongs behind a trailing "more" control passed as children.'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
guidance: "do",
|
|
26
|
+
description: "Give every reaction a `label`. It is the accessible name and the menu's typeahead, and an emoji's own spoken name differs between platforms."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
guidance: "do",
|
|
30
|
+
description: "Pass `selected` so the reader's own reactions show pressed, and toggle in `onSelect`: choosing a reaction you already gave takes it back."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
guidance: "do",
|
|
34
|
+
description: "Offer the right-click menu and a visible add-reaction control together. A context menu is invisible until you know it is there; the pill is how a first-time reader finds reactions."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
guidance: "dont",
|
|
38
|
+
description: "Do not put a ReactionPicker inside a Menu or ContextMenu. A toolbar is not a menu item, so the menu's arrow keys would skip it; use MenuReactions there.",
|
|
39
|
+
instead: ["MenuReactions"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
guidance: "dont",
|
|
43
|
+
description: "Do not use it to rate an answer or collect a verdict. Reactions are social and many-valued; a helpful / not-helpful question is Feedback.",
|
|
44
|
+
instead: ["Feedback"]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
anatomy: [
|
|
48
|
+
{
|
|
49
|
+
name: "ReactionPicker",
|
|
50
|
+
required: true,
|
|
51
|
+
description: "The toolbar: one tab stop, arrow keys move between emoji, each a toggle with `aria-pressed`."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "MenuReactions",
|
|
55
|
+
required: false,
|
|
56
|
+
description: "The same row as a group of checkbox menu items, for MenuContent and ContextMenuContent. Choosing one closes the menu."
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
related: {
|
|
61
|
+
components: ["Bubble", "ContextMenu", "Popover", "Toolbar", "Feedback"]
|
|
62
|
+
},
|
|
63
|
+
props: [
|
|
64
|
+
{
|
|
65
|
+
name: "reactions",
|
|
66
|
+
type: "readonly { emoji: string; label: string }[]",
|
|
67
|
+
default: "DEFAULT_REACTIONS",
|
|
68
|
+
description: "The choices, in order. The default is \u{1F44D} \u2764\uFE0F \u{1F602} \u{1F389} \u{1F62E} \u{1F622} \u{1F64F}."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "selected",
|
|
72
|
+
type: "readonly string[]",
|
|
73
|
+
default: "[]",
|
|
74
|
+
description: "The emoji the reader has already reacted with: shown pressed, and choosing one again removes it."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "onSelect",
|
|
78
|
+
type: "(emoji: string) => void",
|
|
79
|
+
description: "Called with the emoji the reader chose. Toggle it in or out of `selected`."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "children",
|
|
83
|
+
type: "ReactNode",
|
|
84
|
+
description: "ReactionPicker only: trailing controls after the emoji, such as a button that opens a full emoji picker."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "aria-label",
|
|
88
|
+
type: "string",
|
|
89
|
+
default: '"Reactions"',
|
|
90
|
+
description: "The name of the toolbar or menu group."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "motion",
|
|
94
|
+
type: '"full" | "subtle" | "off"',
|
|
95
|
+
default: '"full"',
|
|
96
|
+
description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps the press and drops the wave of emoji blooming in and the magnify, `off` stills it. It can only turn motion down."
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
subcomponents: [
|
|
100
|
+
{
|
|
101
|
+
name: "MenuReactions",
|
|
102
|
+
type: "{ reactions?; selected?; onSelect?; motion? } & ComponentProps<typeof Menu.Group>",
|
|
103
|
+
description: "The quick row as checkbox menu items, for the first group of a MenuContent or ContextMenuContent. Same props as ReactionPicker, without children."
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
tokensUsed: ["accent", "primary", "ring"],
|
|
107
|
+
motion: [
|
|
108
|
+
{
|
|
109
|
+
slot: "arrival",
|
|
110
|
+
recipe: "motion-bloom",
|
|
111
|
+
part: "Glyph",
|
|
112
|
+
why: "Opening the picker is the reader's own action, so the emoji answer it: each blooms in 24ms after the one before, a short wave from first to last. The surface it sits in (a Popover or a menu) arrives with its own motion-pop. Expressive only \u2014 under `subtle` the emoji are simply there."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
slot: "response",
|
|
116
|
+
recipe: "custom",
|
|
117
|
+
part: "Glyph",
|
|
118
|
+
why: "The emoji under the pointer or the keyboard highlight magnifies to 1.25 on a fast standard ease, so you can see which one a release will choose; each cell also carries motion-interactive and motion-press. The magnify rides an inner layer so it never fights the bloom over `scale`."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
slot: "transition",
|
|
122
|
+
recipe: "motion-interactive",
|
|
123
|
+
part: "Cell",
|
|
124
|
+
why: "Pressed and not pressed differ by a tint, eased by motion-interactive. The celebration belongs to the reaction that lands under the message (BubbleReaction), not to the picker that chose it."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
slot: "activity",
|
|
128
|
+
recipe: "none",
|
|
129
|
+
part: "Root",
|
|
130
|
+
why: "Choosing is instant; there is no in-between state to show."
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
a11y: [
|
|
134
|
+
"ReactionPicker is a Base UI toolbar: one tab stop, Left/Right move between emoji, Home/End jump to the ends.",
|
|
135
|
+
"Each emoji is a button named by its `label` and reporting `aria-pressed`, so a screen-reader user hears whether choosing it adds or removes their reaction. The glyph itself is aria-hidden.",
|
|
136
|
+
"MenuReactions renders checkbox menu items (`menuitemcheckbox` with `aria-checked`), reached by the menu's arrow keys and typeahead like every other row.",
|
|
137
|
+
"Under reduced motion the emoji appear at once and do not magnify; pressed state is carried by `aria-pressed` and a tint, never motion."
|
|
138
|
+
],
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
label: "In a popover",
|
|
142
|
+
code: '<Popover>\n <PopoverTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Add reaction" />}>\n <Icon name="emoji" />\n </PopoverTrigger>\n <PopoverContent side="top" className="w-auto rounded-full p-1">\n <ReactionPicker selected={mine} onSelect={toggle} />\n </PopoverContent>\n</Popover>'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: "In a right-click menu",
|
|
146
|
+
code: "<ContextMenu>\n <ContextMenuTrigger render={<Bubble />}>See you at 6.</ContextMenuTrigger>\n <ContextMenuContent>\n <MenuReactions selected={mine} onSelect={toggle} />\n <ContextMenuSeparator />\n <ContextMenuItem>Reply</ContextMenuItem>\n <ContextMenuItem>Copy text</ContextMenuItem>\n </ContextMenuContent>\n</ContextMenu>"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: "A custom set",
|
|
150
|
+
code: '<ReactionPicker\n reactions={[\n { emoji: "\u2705", label: "Done" },\n { emoji: "\u{1F440}", label: "Looking" },\n { emoji: "\u{1F680}", label: "Shipped" },\n ]}\n selected={mine}\n onSelect={toggle}\n/>'
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
export {
|
|
155
|
+
reactionPickerMeta
|
|
156
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/components/search-input/search-input.meta.ts
|
|
2
2
|
var searchInputMeta = {
|
|
3
3
|
name: "SearchInput",
|
|
4
|
-
summary: "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm),
|
|
4
|
+
summary: "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value.",
|
|
5
5
|
status: "stable",
|
|
6
6
|
keywords: [
|
|
7
7
|
"search",
|
|
@@ -24,6 +24,10 @@ var searchInputMeta = {
|
|
|
24
24
|
guidance: "do",
|
|
25
25
|
description: "When a button stands in for search (a header trigger that opens CommandMenu with \u2318K), use CommandMenuTrigger. It wears this field's shell from the same source, so the two cannot drift; a hand-styled button already did once, into a borderless chip that matched neither the field nor a button."
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
guidance: "do",
|
|
29
|
+
description: "Light the query in the results it filters with `useMatchHighlight(query)` \u2014 a callback ref for the results container, with `selector` to keep it to titles. It paints on the `highlight` pair without wrapping matches in `<mark>`, the same paint Combobox and CommandMenu use for their rows."
|
|
30
|
+
},
|
|
27
31
|
{
|
|
28
32
|
guidance: "dont",
|
|
29
33
|
description: "Do not use SearchInput as a command palette; use CommandMenu for global commands, opened from the header by CommandMenuTrigger. A SearchInput that opens a dialog on focus is a text box you cannot type in."
|
|
@@ -91,8 +95,8 @@ var searchInputMeta = {
|
|
|
91
95
|
}
|
|
92
96
|
],
|
|
93
97
|
tokensUsed: [
|
|
94
|
-
"
|
|
95
|
-
"
|
|
98
|
+
"field-border",
|
|
99
|
+
"field",
|
|
96
100
|
"foreground",
|
|
97
101
|
"muted-foreground",
|
|
98
102
|
"ring",
|
|
@@ -144,6 +148,10 @@ var searchInputMeta = {
|
|
|
144
148
|
label: "Uncontrolled, sized as a whole",
|
|
145
149
|
code: '<Field label="Search posts"><SearchInput defaultValue="roadmap" className="max-w-80" /></Field>'
|
|
146
150
|
},
|
|
151
|
+
{
|
|
152
|
+
label: "Highlighting matches in the results",
|
|
153
|
+
code: 'const [q, setQ] = useState("")\nconst results = useMatchHighlight<HTMLUListElement>(q, { selector: "[data-title]" })\nconst shown = posts.filter((post) => post.title.toLowerCase().includes(q.trim().toLowerCase()))\n\n<SearchInput value={q} onValueChange={setQ} placeholder="Search posts" />\n<ul ref={results}>\n {shown.map((post) => (\n <li key={post.id}><span data-title>{post.title}</span></li>\n ))}\n</ul>'
|
|
154
|
+
},
|
|
147
155
|
{
|
|
148
156
|
label: "Disabled",
|
|
149
157
|
code: '<SearchInput disabled placeholder="Search" />'
|
|
@@ -71,7 +71,14 @@ var segmentedBarMeta = {
|
|
|
71
71
|
"muted-foreground",
|
|
72
72
|
"success",
|
|
73
73
|
"tone-warning",
|
|
74
|
-
"destructive"
|
|
74
|
+
"destructive",
|
|
75
|
+
"destructive-mark",
|
|
76
|
+
"success-mark",
|
|
77
|
+
"tone-warning-mark",
|
|
78
|
+
"tone-info-mark",
|
|
79
|
+
"tone-1-mark",
|
|
80
|
+
"tone-2-mark",
|
|
81
|
+
"tone-3-mark"
|
|
75
82
|
],
|
|
76
83
|
motion: [
|
|
77
84
|
{
|
|
@@ -5,15 +5,15 @@ var segmentedBarSegmentVariants = cva(
|
|
|
5
5
|
{
|
|
6
6
|
variants: {
|
|
7
7
|
tone: {
|
|
8
|
-
"1": "bg-tone-1",
|
|
9
|
-
"2": "bg-tone-2",
|
|
10
|
-
"3": "bg-tone-3",
|
|
11
|
-
info: "bg-tone-info",
|
|
8
|
+
"1": "bg-tone-1-mark",
|
|
9
|
+
"2": "bg-tone-2-mark",
|
|
10
|
+
"3": "bg-tone-3-mark",
|
|
11
|
+
info: "bg-tone-info-mark",
|
|
12
12
|
primary: "bg-primary",
|
|
13
13
|
neutral: "bg-muted-foreground",
|
|
14
|
-
success: "bg-success",
|
|
15
|
-
warning: "bg-tone-warning",
|
|
16
|
-
destructive: "bg-destructive"
|
|
14
|
+
success: "bg-success-mark",
|
|
15
|
+
warning: "bg-tone-warning-mark",
|
|
16
|
+
destructive: "bg-destructive-mark"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
defaultVariants: {
|
|
@@ -72,7 +72,7 @@ function SelectTrigger({
|
|
|
72
72
|
className: cn(
|
|
73
73
|
`flex w-full items-center justify-between ${tier.body} text-foreground motion-interactive`,
|
|
74
74
|
mobileTier,
|
|
75
|
-
size === "xs" ? cn("h-(--control-xs) gap-1 px-1.5", FIELD_SHELL_XS) : "h-(--control-md) gap-2 rounded-sm border border-
|
|
75
|
+
size === "xs" ? cn("h-(--control-xs) gap-1 px-1.5", FIELD_SHELL_XS) : "h-(--control-md) gap-2 rounded-sm border border-field-border bg-field px-3",
|
|
76
76
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
77
77
|
"data-[popup-open]:ring-2 data-[popup-open]:ring-ring",
|
|
78
78
|
// Invalid like every other field: Base UI marks the trigger
|
|
@@ -61,11 +61,12 @@ function useKeyboardInset(enabled) {
|
|
|
61
61
|
Math.abs(viewport.scale - 1) > 0.01 ? 0 : Math.max(0, Math.round(window.innerHeight - viewport.height - viewport.offsetTop))
|
|
62
62
|
);
|
|
63
63
|
measure();
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
const listeners = new AbortController();
|
|
65
|
+
const { signal } = listeners;
|
|
66
|
+
viewport.addEventListener("resize", measure, { signal });
|
|
67
|
+
viewport.addEventListener("scroll", measure, { signal });
|
|
66
68
|
return () => {
|
|
67
|
-
|
|
68
|
-
viewport.removeEventListener("scroll", measure);
|
|
69
|
+
listeners.abort();
|
|
69
70
|
setInset(0);
|
|
70
71
|
};
|
|
71
72
|
}, [enabled]);
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { cn } from "../../lib/cn.js";
|
|
11
11
|
import { HighlightGlide } from "../../lib/highlight-glide.js";
|
|
12
12
|
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
13
|
+
import { useAppShellFrame } from "../app-shell/app-shell-frame.js";
|
|
13
14
|
import { RAIL_ROW_INLINE, sideNavItemIconVariants, sideNavItemVariants } from "../side-nav/side-nav.variants.js";
|
|
14
15
|
import { SimpleTooltip } from "../tooltip/index.js";
|
|
15
16
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -27,6 +28,8 @@ var SIDEBAR_VARIANT = {
|
|
|
27
28
|
app: "hidden lg:flex lg:sticky lg:top-0 lg:h-[calc(100vh-var(--dock-height,0px))] lg:self-start",
|
|
28
29
|
inline: "flex h-full"
|
|
29
30
|
};
|
|
31
|
+
var INSET_RAIL = "lg:h-auto lg:self-stretch";
|
|
32
|
+
var INSET_RAIL_SURFACE = "lg:border-r-0 lg:[--sidebar:var(--shell-ground)]";
|
|
30
33
|
var SIDEBAR_INK = [
|
|
31
34
|
"[--background:var(--sidebar)] [--sable-surface-bg:var(--sidebar)]",
|
|
32
35
|
"[--foreground:var(--sidebar-foreground)]",
|
|
@@ -54,6 +57,7 @@ function Sidebar({
|
|
|
54
57
|
}) {
|
|
55
58
|
const [hasCollapsed, setHasCollapsed] = useState(collapsed);
|
|
56
59
|
if (collapsed && !hasCollapsed) setHasCollapsed(true);
|
|
60
|
+
const inset = useAppShellFrame() === "inset";
|
|
57
61
|
return /* @__PURE__ */ jsx(SidebarContext.Provider, { value: { collapsed, icons, reopened: hasCollapsed && !collapsed }, children: /* @__PURE__ */ jsx(
|
|
58
62
|
"aside",
|
|
59
63
|
{
|
|
@@ -61,6 +65,7 @@ function Sidebar({
|
|
|
61
65
|
"data-collapsed": collapsed || void 0,
|
|
62
66
|
"data-variant": variant,
|
|
63
67
|
"data-surface": surface,
|
|
68
|
+
"data-frame": inset ? "inset" : void 0,
|
|
64
69
|
className: cn(
|
|
65
70
|
"shrink-0 flex-col bg-sidebar text-sidebar-foreground",
|
|
66
71
|
// The rail is its own surface. Anything placed in it that inks with
|
|
@@ -71,8 +76,11 @@ function Sidebar({
|
|
|
71
76
|
// a theme names --sidebar-muted-foreground.
|
|
72
77
|
// On the page surface the rail is separated by space and its
|
|
73
78
|
// content, not a rule and a tonal step.
|
|
74
|
-
|
|
79
|
+
// In an inset AppShell the rail is the ground itself, so from lg its border
|
|
80
|
+
// goes too: the gap around the main panel is the divider.
|
|
81
|
+
surface === "rail" ? cn("border-r border-sidebar-border", inset && INSET_RAIL_SURFACE, SIDEBAR_INK) : PAGE_INK,
|
|
75
82
|
SIDEBAR_VARIANT[variant],
|
|
83
|
+
inset && INSET_RAIL,
|
|
76
84
|
"w-[var(--sidebar-width,16rem)] data-[collapsed]:w-[var(--sidebar-width-collapsed,4rem)]",
|
|
77
85
|
// Collapsing eases the width (`motion-size`, expressive) rather than
|
|
78
86
|
// jumping 192px in one frame. It reflows the page beside the rail for
|
|
@@ -164,11 +172,18 @@ function SidebarFooter({
|
|
|
164
172
|
className,
|
|
165
173
|
...props
|
|
166
174
|
}) {
|
|
175
|
+
const inset = useAppShellFrame() === "inset";
|
|
167
176
|
return /* @__PURE__ */ jsx(
|
|
168
177
|
"div",
|
|
169
178
|
{
|
|
170
179
|
className: cn(
|
|
171
180
|
"flex flex-col gap-1 border-t border-sidebar-border px-3 py-4",
|
|
181
|
+
// In an inset shell the rail has no edge of its own (the gap is the
|
|
182
|
+
// divider), so a full-width rule ran out into that gap and stopped on
|
|
183
|
+
// nothing. Inset to the content column it spans what the rows span:
|
|
184
|
+
// the padding becomes margin, so the rule shortens and nothing inside
|
|
185
|
+
// moves.
|
|
186
|
+
inset && "lg:mx-3 lg:px-0",
|
|
172
187
|
className
|
|
173
188
|
),
|
|
174
189
|
...props
|
|
@@ -24,6 +24,10 @@ var sidebarMeta = {
|
|
|
24
24
|
guidance: "do",
|
|
25
25
|
description: `Set surface="page" when a full-width top bar sits above the rail. A theme's tinted or dark rail is for a full-height rail that frames the app.`
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
guidance: "do",
|
|
29
|
+
description: `Keep surface="rail" inside AppShell frame="inset": the rail's tint becomes the ground the main panel floats on. Do not add a border-r class back \u2014 the gap is the divider.`
|
|
30
|
+
},
|
|
27
31
|
{
|
|
28
32
|
guidance: "dont",
|
|
29
33
|
description: "Do not use Sidebar for page-local settings, filters, or temporary action groups."
|
|
@@ -76,7 +80,7 @@ var sidebarMeta = {
|
|
|
76
80
|
name: "surface",
|
|
77
81
|
type: '"rail" | "page"',
|
|
78
82
|
default: '"rail"',
|
|
79
|
-
description: `Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border \u2014 right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border \u2014 use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat.`
|
|
83
|
+
description: `Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border \u2014 right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border \u2014 use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat. In AppShell's sidebar slot with frame="inset", the rail's surface is also the shell's ground: from lg a "rail" rail drops its border (the gap around the main panel divides them) and either surface stretches to the shell's body row instead of sizing to the viewport. AppShell signals this to its sidebar slot only, so a Sidebar elsewhere in the page is unaffected.`
|
|
80
84
|
},
|
|
81
85
|
{
|
|
82
86
|
name: "motion",
|
|
@@ -146,7 +150,8 @@ var sidebarMeta = {
|
|
|
146
150
|
"sidebar-border",
|
|
147
151
|
"sidebar-accent",
|
|
148
152
|
"sidebar-accent-foreground",
|
|
149
|
-
"sidebar-ring"
|
|
153
|
+
"sidebar-ring",
|
|
154
|
+
"shell-ground"
|
|
150
155
|
],
|
|
151
156
|
variants: {
|
|
152
157
|
variant: ["app", "inline"]
|
|
@@ -106,7 +106,14 @@ var sparklineMeta = {
|
|
|
106
106
|
"tone-warning",
|
|
107
107
|
"muted-foreground",
|
|
108
108
|
"success",
|
|
109
|
-
"destructive"
|
|
109
|
+
"destructive",
|
|
110
|
+
"destructive-mark",
|
|
111
|
+
"success-mark",
|
|
112
|
+
"tone-warning-mark",
|
|
113
|
+
"tone-info-mark",
|
|
114
|
+
"tone-1-mark",
|
|
115
|
+
"tone-2-mark",
|
|
116
|
+
"tone-3-mark"
|
|
110
117
|
],
|
|
111
118
|
motion: [
|
|
112
119
|
{
|
|
@@ -83,7 +83,14 @@ var statusDotMeta = {
|
|
|
83
83
|
"tone-info",
|
|
84
84
|
"tone-1",
|
|
85
85
|
"tone-2",
|
|
86
|
-
"destructive"
|
|
86
|
+
"destructive",
|
|
87
|
+
"destructive-mark",
|
|
88
|
+
"success-mark",
|
|
89
|
+
"tone-warning-mark",
|
|
90
|
+
"tone-info-mark",
|
|
91
|
+
"tone-1-mark",
|
|
92
|
+
"tone-2-mark",
|
|
93
|
+
"tone-3-mark"
|
|
87
94
|
],
|
|
88
95
|
a11y: [
|
|
89
96
|
"Rendered aria-hidden \u2014 purely decorative. Convey status in adjacent text, not color alone.",
|
|
@@ -4,13 +4,13 @@ var statusDotVariants = cva("inline-block shrink-0 rounded-full", {
|
|
|
4
4
|
variants: {
|
|
5
5
|
tone: {
|
|
6
6
|
neutral: "bg-muted-foreground",
|
|
7
|
-
"3": "bg-tone-3",
|
|
8
|
-
success: "bg-success",
|
|
9
|
-
warning: "bg-tone-warning",
|
|
10
|
-
info: "bg-tone-info",
|
|
11
|
-
"1": "bg-tone-1",
|
|
12
|
-
"2": "bg-tone-2",
|
|
13
|
-
destructive: "bg-destructive",
|
|
7
|
+
"3": "bg-tone-3-mark",
|
|
8
|
+
success: "bg-success-mark",
|
|
9
|
+
warning: "bg-tone-warning-mark",
|
|
10
|
+
info: "bg-tone-info-mark",
|
|
11
|
+
"1": "bg-tone-1-mark",
|
|
12
|
+
"2": "bg-tone-2-mark",
|
|
13
|
+
destructive: "bg-destructive-mark",
|
|
14
14
|
// A palette family paints from the slot its data-palette scope fills.
|
|
15
15
|
red: "bg-palette-dot",
|
|
16
16
|
orange: "bg-palette-dot",
|
|
@@ -24,7 +24,7 @@ var stepperDotVariants = cva(
|
|
|
24
24
|
complete: "bg-primary",
|
|
25
25
|
current: "bg-[var(--sable-surface-bg,var(--background))] ring-[0.1875em] ring-primary ring-inset",
|
|
26
26
|
upcoming: "bg-border-strong",
|
|
27
|
-
error: "bg-destructive"
|
|
27
|
+
error: "bg-destructive-mark"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
defaultVariants: {
|
|
@@ -100,13 +100,13 @@ var tableMeta = {
|
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
name: "TableHead",
|
|
103
|
-
type: 'ComponentPropsWithRef<"th">',
|
|
104
|
-
description: "A column header cell (th) in muted label styling."
|
|
103
|
+
type: 'ComponentPropsWithRef<"th"> & { numeric?: boolean }',
|
|
104
|
+
description: "A column header cell (th) in muted label styling. `numeric` marks a number column: end-aligned, tabular figures \u2014 set it on the column's cells too."
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
name: "TableCell",
|
|
108
|
-
type: 'ComponentPropsWithRef<"td">',
|
|
109
|
-
description: "A data cell (td)."
|
|
108
|
+
type: 'ComponentPropsWithRef<"td"> & { numeric?: boolean }',
|
|
109
|
+
description: "A data cell (td). `numeric` end-aligns it with tabular figures, matching its column's header."
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
name: "TableCaption",
|
|
@@ -23,6 +23,11 @@ function Textarea({
|
|
|
23
23
|
// already lost the invalid ring Input shows on focus.
|
|
24
24
|
FIELD_SHELL,
|
|
25
25
|
FIELD_INVALID,
|
|
26
|
+
// Text being edited must not rewrap above the caret. `text-wrap-style`
|
|
27
|
+
// inherits, so a field inside a `text-pretty` or `text-balance` block
|
|
28
|
+
// would re-break every earlier line on each keystroke; `stable` is
|
|
29
|
+
// what an editing surface asks for. Longhand only: the wrap mode stays.
|
|
30
|
+
"[text-wrap-style:stable]",
|
|
26
31
|
autoResize && "field-sizing-content resize-none",
|
|
27
32
|
tier.body,
|
|
28
33
|
density === "comfortable" && MOBILE_FIELD_TIER,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/components/textarea/textarea.meta.ts
|
|
2
2
|
var textareaMeta = {
|
|
3
3
|
name: "Textarea",
|
|
4
|
-
summary: "Styled native multi-line text control. Soft-square,
|
|
4
|
+
summary: "Styled native multi-line text control. Soft-square, the theme's field surface, --ring focus, and an aria-invalid destructive error state.",
|
|
5
5
|
status: "stable",
|
|
6
6
|
keywords: [
|
|
7
7
|
"textarea",
|
|
@@ -75,8 +75,8 @@ var textareaMeta = {
|
|
|
75
75
|
}
|
|
76
76
|
],
|
|
77
77
|
tokensUsed: [
|
|
78
|
-
"
|
|
79
|
-
"
|
|
78
|
+
"field-border",
|
|
79
|
+
"field",
|
|
80
80
|
"foreground",
|
|
81
81
|
"muted-foreground",
|
|
82
82
|
"ring",
|