glyphx 0.1.2 → 0.2.1

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/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  A fast, keyboard-first, Spotlight-style command palette for React.
4
4
 
5
- Glyph provides a clean, accessible, and native-feeling command menu inspired by macOS Spotlight and modern desktop tools.
5
+ GlyphX provides a clean, accessible, and native-feeling command menu inspired by macOS Spotlight and modern desktop tools.
6
6
 
7
7
  ## Features
8
8
 
9
- - Keyboard-first UX (`⌘ + K` / `Ctrl + K`)
9
+ - Keyboard-first UX (default `⌘ + Shift + P` / `Ctrl + Shift + P`, configurable)
10
10
  - Fuzzy search with ranked results
11
11
  - Arrow-key navigation with auto-scroll
12
- - Grouped commands
12
+ - Grouped commands with optional subtitles
13
13
  - System light / dark mode support
14
14
  - Glass-style UI
15
15
  - Accessible by default (Radix Dialog)
@@ -24,27 +24,44 @@ or
24
24
  ```bash
25
25
  pnpm add glyphx
26
26
  ```
27
+
28
+ ## Setup
29
+
30
+ Import the stylesheet once at your app's entry point — it contains the theme variables and animations GlyphX needs:
31
+
32
+ ```ts
33
+ import "glyphx/style.css";
34
+ ```
35
+
27
36
  ## Basic Usage
28
37
 
29
38
  ```tsx
30
39
  import { GlyphX } from "glyphx";
31
-
32
- const commands = [
33
- {
34
- id: "home",
35
- title: "Go to Home",
36
- group: "Navigation",
37
- run: () => {
38
- console.log("Navigate to home");
39
- },
40
- },
41
- ];
40
+ import type { Command } from "glyphx";
41
+ import { useMemo } from "react";
42
42
 
43
43
  export default function App() {
44
+ const commands: Command[] = useMemo(
45
+ () => [
46
+ {
47
+ id: "home",
48
+ title: "Go to Home",
49
+ group: "Navigation",
50
+ run: () => {
51
+ console.log("Navigate to home");
52
+ },
53
+ },
54
+ ],
55
+ []
56
+ );
57
+
44
58
  return <GlyphX commands={commands} />;
45
59
  }
46
60
  ```
47
- Press ⌘ + K on macOS or Ctrl + K on Windows/Linux to open the palette.
61
+
62
+ > **Note:** Pass a stable (memoized) `commands` array. A new array reference on every render causes unnecessary re-normalization.
63
+
64
+ Press ⌘ + Shift + P on macOS or Ctrl + Shift + P on Windows/Linux to open the palette. (The default avoids ⌘/Ctrl + K, which collides with the browser address-bar shortcut.) You can change it with the [`shortcut`](#custom-shortcut) prop.
48
65
 
49
66
  ---
50
67
 
@@ -58,17 +75,54 @@ Press ⌘ + K on macOS or Ctrl + K on Windows/Linux to open the palette.
58
75
  />
59
76
  ```
60
77
 
78
+ ## Custom Shortcut
79
+
80
+ By default the palette opens on `⌘ + Shift + P` (macOS) / `Ctrl + Shift + P` (Windows/Linux). The leading modifier is `⌘` on macOS and `Ctrl` elsewhere — detected automatically. Override the rest with the `shortcut` prop:
81
+
82
+ ```tsx
83
+ // opens on ⌘/Ctrl + J
84
+ <GlyphX commands={commands} shortcut={{ key: "j" }} />
85
+ ```
86
+
87
+ ```ts
88
+ type ShortcutConfig = {
89
+ key: string; // the non-modifier key, e.g. "k", "p", "j"
90
+ shift?: boolean; // also require Shift (default: false)
91
+ alt?: boolean; // also require Alt/Option (default: false)
92
+ };
93
+ ```
94
+
95
+ The primary modifier (`⌘` on macOS, `Ctrl` on Windows/Linux) is always required and is not configurable.
96
+
61
97
  ## Command Shape
62
98
 
63
99
  ```ts
64
100
  type Command = {
65
101
  id: string;
66
102
  title: string;
67
- group?: string;
68
- run: () => void;
103
+ subtitle?: string; // shown below the title
104
+ keywords?: string[]; // extra search terms, not displayed
105
+ shortcut?: string; // displayed on the right (e.g. "⌘ K")
106
+ group?: string; // groups commands under a heading
107
+ run: () => void | Promise<void>;
69
108
  };
70
109
  ```
71
110
 
111
+ ### `keywords`
112
+
113
+ `keywords` add invisible search aliases. They boost discoverability without cluttering the UI — useful for synonyms, abbreviations, or legacy names:
114
+
115
+ ```ts
116
+ {
117
+ id: "theme",
118
+ title: "Toggle Dark Mode",
119
+ keywords: ["appearance", "night mode", "theme", "colors"],
120
+ run: toggleTheme,
121
+ }
122
+ ```
123
+
124
+ A user typing "appearance" or "night mode" will surface this command even though neither word appears in the title.
125
+
72
126
  ## Design Goals
73
127
 
74
128
  - Minimal visual noise
@@ -79,6 +133,18 @@ type Command = {
79
133
 
80
134
  GlyphX is intended as a foundational UI primitive, not a full application.
81
135
 
136
+ ## Upgrading from 0.1.x
137
+
138
+ The default keyboard shortcut changed from `⌘/Ctrl + K` to `⌘/Ctrl + Shift + P` to avoid conflicting with the browser's address-bar shortcut. If you want to keep the old shortcut, pass it explicitly:
139
+
140
+ ```tsx
141
+ <GlyphX commands={commands} shortcut={{ key: "k" }} />
142
+ ```
143
+
144
+ ## Known Limitations
145
+
146
+ - **Single instance per page:** GlyphX uses a module-level state store. Rendering two `<GlyphX>` components on the same page will cause them to share state (commands, open/closed status, query). Use one instance per page.
147
+
82
148
  ## Status
83
149
 
84
150
  GlyphX is in early development.
@@ -0,0 +1,9 @@
1
+ export type Command = {
2
+ id: string;
3
+ title: string;
4
+ subtitle?: string;
5
+ keywords?: string[];
6
+ shortcut?: string;
7
+ group?: string;
8
+ run: () => void | Promise<void>;
9
+ };
@@ -0,0 +1,2 @@
1
+ import { Command } from './command';
2
+ export declare function executeCommand(command: Command): Promise<void>;
@@ -0,0 +1,5 @@
1
+ export type { Command } from './command';
2
+ export type { NormalizedCommand } from './normalize';
3
+ export { normalizeCommands } from './normalize';
4
+ export { searchCommands } from './search';
5
+ export { executeCommand } from './execute';
@@ -0,0 +1,7 @@
1
+ import { Command } from './command';
2
+ export type NormalizedCommand = Command & {
3
+ searchText: string;
4
+ titleLength: number;
5
+ subtitleLength: number;
6
+ };
7
+ export declare function normalizeCommands(commands: Command[]): NormalizedCommand[];
@@ -0,0 +1,7 @@
1
+ import { NormalizedCommand } from './normalize';
2
+ export type SearchResult<T> = {
3
+ item: T;
4
+ titleIndexes: readonly number[] | null;
5
+ subtitleIndexes: readonly number[] | null;
6
+ };
7
+ export declare function searchCommands(commands: NormalizedCommand[], query: string): SearchResult<NormalizedCommand>[];
@@ -0,0 +1,2 @@
1
+ import { RefObject } from 'react';
2
+ export declare function useAutoScroll<T extends HTMLElement>(ref: RefObject<T | null>, active: boolean): void;
@@ -0,0 +1,2 @@
1
+ import { ShortcutConfig } from '../palette/types';
2
+ export declare function useCommandShortcut(onTrigger: () => void, shortcut?: ShortcutConfig): void;
@@ -0,0 +1 @@
1
+ export declare function usePaletteNavigation(): void;
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ @layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-blue-400:oklch(70.7% .165 254.624);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-800:oklch(27.4% .006 286.033);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-medium:500;--font-weight-semibold:600;--radius-md:.375rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-3{top:calc(var(--spacing)*3)}.top-\[20\%\]{top:20%}.right-3{right:calc(var(--spacing)*3)}.left-1\/2{left:50%}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.max-h-64{max-height:calc(var(--spacing)*64)}.min-h-screen{min-height:100vh}.w-\[520px\]{width:520px}.w-full{width:100%}.max-w-\[90vw\]{max-width:90vw}.max-w-md{max-width:var(--container-md)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:.25rem}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-\[rgb\(var\(--border\)\)\]{border-color:rgb(var(--border))}.bg-\[rgb\(var\(--bg\)\)\]{background-color:rgb(var(--bg))}.bg-\[rgb\(var\(--glass\)\)\]{background-color:rgb(var(--glass))}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab,red,red)){.bg-black\/40{background-color:color-mix(in oklab,var(--color-black)40%,transparent)}}.bg-transparent{background-color:#0000}.bg-zinc-800{background-color:var(--color-zinc-800)}.p-2{padding:calc(var(--spacing)*2)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-8{padding-block:calc(var(--spacing)*8)}.text-center{text-align:center}.text-left{text-align:left}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-\[rgb\(var\(--fg\)\)\]{color:rgb(var(--fg))}.text-\[rgb\(var\(--muted\)\)\]{color:rgb(var(--muted))}.text-blue-400{color:var(--color-blue-400)}.text-white{color:var(--color-white)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-500{color:var(--color-zinc-500)}.opacity-100{opacity:1}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.will-change-transform{will-change:transform}.\[contain\:layout_paint\]{contain:layout paint}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.placeholder\:text-\[rgb\(var\(--muted\)\)\]::-moz-placeholder{color:rgb(var(--muted))}.placeholder\:text-\[rgb\(var\(--muted\)\)\]::placeholder{color:rgb(var(--muted))}@media(hover:hover){.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.data-\[state\=closed\]\:animate-\[spotlight-out_120ms_ease-out\][data-state=closed]{animation:.12s ease-out spotlight-out}.data-\[state\=open\]\:animate-\[spotlight-in_180ms_cubic-bezier\(0\.16\,1\,0\.3\,1\)\][data-state=open]{animation:.18s cubic-bezier(.16,1,.3,1) spotlight-in}}:root{--bg:255 255 255;--fg:24 24 27;--muted:113 113 122;--border:228 228 231;--glass:255 255 255/.92}@media(prefers-color-scheme:dark){:root{--bg:9 9 11;--fg:244 244 245;--muted:161 161 170;--border:39 39 42;--glass:24 24 27/.92}}@keyframes spotlight-in{0%{opacity:0;transform:scale(.985)}to{opacity:1;transform:scale(1)}}@keyframes spotlight-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.985)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}
@@ -0,0 +1,3 @@
1
+ export { GlyphX } from './palette/CommandPaletteProvider';
2
+ export type { GlyphXProps, ShortcutConfig } from './palette/types';
3
+ export type { Command } from './engine/command';