Files
dotfiles/home_root/.claude/skills/australis-design/ui_kits/terminal/CommandPalette.jsx
T
vh 7b16be02bf feat: fold in the portable Claude Code, zsh and git config; add a drift check
Added (all now linked from their live locations):
- home_root/.claude/settings.json. The statusLine command now uses $HOME
  instead of a Linux-only absolute path.
- home_root/.claude/bin/ratecheck and home_root/.claude/skills/australis-design.
- home_config/zsh/claude-config-dir.zsh and home_root/.local/bin/claude-config-dir-init,
  the per-repo Claude Code login helper and its seeding script.
- home_root/.gitconfig and home_config/git/ignore.
- home_root/.zshenv, with the cargo env line guarded so boxes without rust start clean.

link-dotfiles --check reports drift without changing anything: tracked files whose
live copy is no longer a link (stow dry-run conflicts) and links into this repo
that dangle. Exits 1 on drift. Verified against a clean baseline and two planted
drifts, a detached .nanorc and a dangling link.

.mailmap maps every earlier identity to Vuong Hoang: the repo-local 'Your Name'
placeholder config (now removed), host-generated emails, and aider suffixes.

CLAUDE.md: the symlink warning now lists every linked file and points at --check.
README: documents what is and isn't tracked under .claude, the edit rule, and --check.
2026-09-24 09:07:16 -07:00

133 lines
4.7 KiB
React

// CommandPalette — ⌘K overlay. Glass surface, blue focus ring.
const CommandPalette = ({ open, onClose, onRun }) => {
const [q, setQ] = React.useState('');
const [sel, setSel] = React.useState(0);
const inputRef = React.useRef(null);
const items = React.useMemo(() => [
{ id: 'build', label: 'Build project', hint: '⌘B', kind: 'Action' },
{ id: 'deploy', label: 'Deploy to prod', hint: '⌘D', kind: 'Action' },
{ id: 'theme', label: 'Theme: Australis Dark', hint: '⌘T', kind: 'Setting' },
{ id: 'tokens', label: 'Open tokens.css', hint: 'go', kind: 'File' },
{ id: 'aurora', label: 'Open aurora.ts', hint: 'go', kind: 'File' },
{ id: 'sign', label: 'Sign out', hint: '', kind: 'Action', danger: true },
], []);
const filtered = items.filter(i => i.label.toLowerCase().includes(q.toLowerCase()));
React.useEffect(() => {
if (open && inputRef.current) inputRef.current.focus();
setQ(''); setSel(0);
}, [open]);
React.useEffect(() => {
if (!open) return;
const onKey = (e) => {
if (e.key === 'Escape') onClose();
if (e.key === 'ArrowDown') { e.preventDefault(); setSel(s => Math.min(s + 1, filtered.length - 1)); }
if (e.key === 'ArrowUp') { e.preventDefault(); setSel(s => Math.max(s - 1, 0)); }
if (e.key === 'Enter' && filtered[sel]) { onRun(filtered[sel]); onClose(); }
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [open, sel, filtered, onClose, onRun]);
if (!open) return null;
return (
<div style={cpStyles.scrim} onClick={onClose}>
<div style={cpStyles.panel} onClick={e => e.stopPropagation()}>
<div style={cpStyles.head}>
<Icon name="search" size={16} style={{ color: 'var(--fg-3)' }}/>
<input
ref={inputRef}
value={q}
onChange={e => { setQ(e.target.value); setSel(0); }}
placeholder="Type a command…"
style={cpStyles.input}
/>
<span style={cpStyles.esc}>esc</span>
</div>
<div style={cpStyles.list}>
{filtered.length === 0 && (
<div style={cpStyles.empty}>No matches.</div>
)}
{filtered.map((it, i) => (
<div key={it.id}
onMouseEnter={() => setSel(i)}
onClick={() => { onRun(it); onClose(); }}
style={{
...cpStyles.item,
background: i === sel ? 'rgba(99,136,216,0.18)' : 'transparent',
color: i === sel ? '#a4c4ff' : (it.danger ? '#ff854f' : 'var(--fg-1)'),
}}>
<Icon name={it.kind === 'File' ? 'file' : it.kind === 'Setting' ? 'settings' : 'play'} size={14}
style={{ color: i === sel ? '#a4c4ff' : 'var(--fg-3)' }}/>
<span style={{ flex: 1 }}>{it.label}</span>
<span style={cpStyles.kind}>{it.kind}</span>
{it.hint && <span style={cpStyles.hint}>{it.hint}</span>}
</div>
))}
</div>
</div>
</div>
);
};
const cpStyles = {
scrim: {
position: 'absolute', inset: 0,
background: 'rgba(34,37,49,0.72)',
backdropFilter: 'blur(4px)',
display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
paddingTop: '15vh',
zIndex: 100,
},
panel: {
width: 540,
background: 'rgba(55,59,70,0.85)',
backdropFilter: 'blur(16px)',
border: '1px solid #565f69',
borderRadius: 10,
boxShadow: '0 24px 64px rgba(10,12,18,0.6), 0 8px 16px rgba(10,12,18,0.45)',
overflow: 'hidden',
},
head: {
display: 'flex', alignItems: 'center', gap: 10,
padding: '12px 14px',
borderBottom: '1px solid #414751',
},
input: {
flex: 1, background: 'transparent', border: 0, outline: 'none',
color: 'var(--fg-0)',
fontFamily: 'var(--font-sans)', fontSize: 14,
},
esc: {
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-3)', background: '#1b1e28',
padding: '2px 6px', borderRadius: 4, border: '1px solid #414751',
},
list: { padding: 6, maxHeight: 360, overflowY: 'auto' },
item: {
display: 'flex', alignItems: 'center', gap: 10,
padding: '8px 10px',
borderRadius: 5,
fontSize: 13,
cursor: 'pointer',
},
kind: {
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-3)',
textTransform: 'uppercase', letterSpacing: '0.06em',
},
hint: {
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-3)', background: '#1b1e28',
padding: '2px 6px', borderRadius: 4, border: '1px solid #414751',
},
empty: { padding: 16, fontSize: 13, color: 'var(--fg-3)' },
};
window.CommandPalette = CommandPalette;