@refraction-ui/astro 0.16.0 → 0.17.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.
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `createEmojiTrigger` — a ready-made `:` shortcode trigger backed by the shared
3
+ * emoji dataset (`@refraction-ui/emoji-picker`, ~1900 base emoji), so the
4
+ * composer's `:` menu is complete and matches the picker exactly (one source of
5
+ * truth, no divergence). The committed token is the native glyph.
6
+ *
7
+ * This module is side-effect free and imports the dataset lazily via the emoji
8
+ * search API, so it is only pulled into a bundle when `createEmojiTrigger` is
9
+ * actually referenced.
10
+ */
11
+ import { searchEmojis } from '../emoji-picker/index.ts'
12
+ import type { ComposerCandidate, ComposerTrigger } from './types.js'
13
+
14
+ export interface EmojiTriggerOptions {
15
+ /** Trigger id. Default 'emoji'. */
16
+ id?: string
17
+ /** Symbol. Default ':'. */
18
+ symbol?: string
19
+ /** Max results surfaced to the menu. Default 8. */
20
+ maxResults?: number
21
+ /**
22
+ * Minimum query length before resolving (avoids flooding the menu with the
23
+ * whole set on a lone ':'). Default 1.
24
+ */
25
+ minQueryLength?: number
26
+ }
27
+
28
+ /**
29
+ * Build a `:` emoji trigger. Typing `:fir` filters the full set; committing
30
+ * inserts the glyph (🔥). Pair it with the picker for the same coverage in both
31
+ * surfaces.
32
+ */
33
+ export function createEmojiTrigger(options: EmojiTriggerOptions = {}): ComposerTrigger {
34
+ const { id = 'emoji', symbol = ':', maxResults = 8, minQueryLength = 1 } = options
35
+ return {
36
+ id,
37
+ symbol,
38
+ // The committed token is the glyph itself, not `:name:`.
39
+ toDisplay: (candidate: ComposerCandidate) =>
40
+ (candidate.metadata?.emoji as string | undefined) ?? candidate.display,
41
+ resolve: (query: string): ComposerCandidate[] => {
42
+ if (query.length < minQueryLength) return []
43
+ return searchEmojis(query, maxResults).map((entry) => ({
44
+ id: `${entry.shortcode}`,
45
+ display: `${entry.emoji} :${entry.shortcode}:`,
46
+ subtitle: entry.name,
47
+ metadata: { emoji: entry.emoji, shortcode: entry.shortcode },
48
+ }))
49
+ },
50
+ }
51
+ }
@@ -6,3 +6,4 @@ export * from './suggestion.ts'
6
6
  export * from './tokens.ts'
7
7
  export * from './composer.ts'
8
8
  export * from './styles.ts'
9
+ export { createEmojiTrigger, type EmojiTriggerOptions } from './emoji-trigger.js'
@@ -6,13 +6,25 @@
6
6
 
7
7
  import { cva } from '../shared/index.ts'
8
8
 
9
- /** Outer surface (the rounded card wrapping tray + field + action row). */
9
+ /**
10
+ * Outer surface (the rounded card wrapping tray + field + action row).
11
+ * `surface` picks the resting fill: `outlined` (transparent-on-page card) or
12
+ * `filled` (a calm muted fill distinct from the page). Both keep a tasteful
13
+ * focus-visible ring on the pill — a hairline border plus a single-px ring, not
14
+ * a slammed saturated full-perimeter brand border.
15
+ */
10
16
  export const composerSurfaceVariants = cva({
11
17
  base: [
12
- 'overflow-hidden rounded-2xl border border-border bg-background shadow-sm',
13
- 'transition-shadow focus-within:border-foreground/25 focus-within:shadow-md',
18
+ 'overflow-hidden rounded-2xl border shadow-sm',
19
+ 'transition-[box-shadow,border-color,background-color] duration-150 ease-out',
20
+ 'focus-within:ring-1 focus-within:ring-ring focus-within:shadow-md',
21
+ 'motion-reduce:transition-none',
14
22
  ].join(' '),
15
23
  variants: {
24
+ surface: {
25
+ outlined: 'border-border bg-background focus-within:border-ring',
26
+ filled: 'border-border/50 bg-muted/50 focus-within:border-ring/70 focus-within:bg-background',
27
+ },
16
28
  disabled: {
17
29
  true: 'pointer-events-none opacity-60',
18
30
  false: '',
@@ -23,6 +35,7 @@ export const composerSurfaceVariants = cva({
23
35
  },
24
36
  },
25
37
  defaultVariants: {
38
+ surface: 'outlined',
26
39
  disabled: 'false',
27
40
  error: 'false',
28
41
  },
@@ -35,9 +48,16 @@ export const composerFieldClass =
35
48
  /** Attachment tray above the field, inside the dock. */
36
49
  export const composerTrayClass = 'flex flex-wrap gap-2 px-3 pt-3'
37
50
 
38
- /** A single attachment chip. */
51
+ /**
52
+ * A single attachment chip. Chips animate in on add (scale + fade) and, when
53
+ * the adapter marks one `exiting`, animate out faster before unmounting. Both
54
+ * degrade to opacity-only / instant under reduced motion.
55
+ */
39
56
  export const composerAttachmentChipVariants = cva({
40
- base: 'inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs',
57
+ base: [
58
+ 'inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs',
59
+ 'origin-left transition-all duration-150 ease-out motion-reduce:transition-none',
60
+ ].join(' '),
41
61
  variants: {
42
62
  status: {
43
63
  pending: 'opacity-70',
@@ -45,9 +65,14 @@ export const composerAttachmentChipVariants = cva({
45
65
  ready: '',
46
66
  error: 'text-destructive',
47
67
  },
68
+ exiting: {
69
+ true: 'scale-90 opacity-0 motion-reduce:scale-100',
70
+ false: 'motion-safe:animate-fade-in-scale',
71
+ },
48
72
  },
49
73
  defaultVariants: {
50
74
  status: 'pending',
75
+ exiting: 'false',
51
76
  },
52
77
  })
53
78
 
@@ -55,9 +80,35 @@ export const composerAttachmentChipVariants = cva({
55
80
  export const composerTokenPillClass =
56
81
  'rounded bg-accent/40 px-0.5 text-accent-foreground'
57
82
 
58
- /** The caret-anchored suggestion menu. */
83
+ /**
84
+ * The caret-anchored suggestion menu. Enters with an eased fade+scale from its
85
+ * bottom edge (it sits above the field); reduced motion drops the movement.
86
+ */
59
87
  export const composerMenuClass =
60
- 'z-20 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg'
88
+ 'z-20 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg origin-bottom motion-safe:animate-fade-in-scale'
89
+
90
+ /**
91
+ * The inline expression/accessory panel docked below the field. It is part of
92
+ * the composer's own stack (never a floating portal), so the textarea and the
93
+ * message in progress stay fully visible while the panel is open. Opens with an
94
+ * eased fade; reduced motion shows it instantly.
95
+ */
96
+ export const composerAccessoryPanelClass =
97
+ 'border-t border-border motion-safe:animate-fade-in'
98
+
99
+ /** The built-in expression-panel toggle button. */
100
+ export const composerAccessoryToggleVariants = cva({
101
+ base: 'flex h-8 w-8 items-center justify-center rounded-lg transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
102
+ variants: {
103
+ active: {
104
+ true: 'bg-accent text-foreground',
105
+ false: 'text-muted-foreground',
106
+ },
107
+ },
108
+ defaultVariants: {
109
+ active: 'false',
110
+ },
111
+ })
61
112
 
62
113
  /** A suggestion row; `active` follows keyboard/pointer last-input-wins. */
63
114
  export const composerMenuItemVariants = cva({
@@ -87,9 +138,13 @@ export const composerCounterVariants = cva({
87
138
  },
88
139
  })
89
140
 
90
- /** Primary action (send ⇄ stop swap driven by `{hasText, canSend, isBusy}`). */
141
+ /**
142
+ * Primary action (send ⇄ stop swap driven by `{hasText, canSend, isBusy}`).
143
+ * The colour/affordance change eases; the adapter keys the button on busy so
144
+ * the swap pops in (scale) rather than snapping. Reduced motion keeps it calm.
145
+ */
91
146
  export const composerPrimaryActionVariants = cva({
92
- base: 'flex h-9 w-9 items-center justify-center rounded-full transition',
147
+ base: 'flex h-9 w-9 items-center justify-center rounded-full transition-all duration-150 ease-out active:scale-95 motion-reduce:transition-none motion-reduce:active:scale-100 motion-safe:animate-scale-in',
93
148
  variants: {
94
149
  enabled: {
95
150
  true: 'bg-primary text-primary-foreground hover:opacity-90',