Snippet
The small copyable command the assistant hands over, composed on the registered input-group exactly as the web composes its own — there was never a CodeBlock in the web Snippet's tree to reuse. Code lives in one context; the input reads it readOnly and selectable; copy flips for 2000ms and reports failure. No SnippetHeader/SnippetTabs ship — neither exists upstream. Monospace rides lib/mono.
- v0.2.1
- component
- nativewind
- uniwind
- runs in Expo Go
Install
npx @react-native-reusables/cli@latest add https://rnraielements.com/r/nativewind/snippet.jsonnpx @react-native-reusables/cli@latest add https://rnraielements.com/r/uniwind/snippet.jsonDependencies
expo-clipboard
https://reactnativereusables.com/r/nativewind/button.json |
https://reactnativereusables.com/r/nativewind/icon.json |
https://rnraielements.com/r/nativewind/input-group.json |
https://reactnativereusables.com/r/uniwind/button.json |
https://reactnativereusables.com/r/uniwind/icon.json |
https://rnraielements.com/r/uniwind/input-group.json |
AI SDK peers:ai@^7.0.89@ai-sdk/react@^4.0.92
Source
import { Button } from '@/registry/nativewind/components/ui/button';
import { Icon } from '@/registry/nativewind/components/ui/icon';
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from '@/registry/nativewind/components/ui/input-group';
import { monoStyle } from '@/registry/nativewind/lib/mono';
import { cn } from '@/registry/nativewind/lib/utils';
import { CheckIcon, CopyIcon } from 'lucide-react-native';
import * as Clipboard from 'expo-clipboard';
import * as React from 'react';
import { type ViewProps } from 'react-native';
/**
* Snippet — the small copyable command the assistant hands over ("run this"), the web
* original composed on its InputGroup. OUR InputGroup is the registered gap atom, so
* the composition carries over one-for-one — there was never a CodeBlock in the web
* Snippet's tree to reuse, and its filename chrome would have been duplication anyway:
* the web Snippet is a compact InputGroup row, not a fenced block.
*
* BRIEF CORRECTION, ON THE RECORD: the task's parenthetical named
* SnippetHeader/SnippetTabs. Neither exists upstream — the current web part set is
* Snippet/SnippetAddon/SnippetText/SnippetInput/SnippetCopyButton (verified against
* upstream main and the KB alike). No tabs ship.
*
* BEHAVIOR PRESERVED FROM THE WEB ORIGINAL:
* - the code lives in one context; SnippetInput reads it and is readOnly — the
* command is display text in a selectable field, not editable state
* - copy flips to a check for ~2000ms then reverts (`timeout`)
* - copy failure calls `onError` rather than silently doing nothing
* - the `$` prefix is SnippetAddon/SnippetText composition, not a prop
*
* REACT NATIVE NOTES: the monospace family rides lib/mono (the house decision —
* Tailwind's font-mono stack has no resolvable RN family). A long command neither wraps
* nor grows the row: the input is single-line and scrolls within its own field, which
* is the web's overflow behavior with no new machinery.
*/
type SnippetContextValue = { code: string };
const SnippetContext = React.createContext<SnippetContextValue | null>(null);
function useSnippet() {
const ctx = React.useContext(SnippetContext);
if (!ctx) throw new Error('Snippet sub-components must be used within <Snippet>');
return ctx;
}
type SnippetProps = ViewProps & {
/** The command. The one prop everything else reads. */
code: string;
children?: React.ReactNode;
};
function Snippet({ code, className, children, ...props }: SnippetProps) {
const contextValue = React.useMemo(() => ({ code }), [code]);
return (
<SnippetContext.Provider value={contextValue}>
<InputGroup className={className} {...props}>
{children}
</InputGroup>
</SnippetContext.Provider>
);
}
/** Leading/trailing addon slot — a `$` prefix, a label. The web passthrough, kept. */
function SnippetAddon({ className, ...props }: ViewProps) {
return <InputGroupAddon className={className} {...props} />;
}
/** Static text inside the row — the `$` prefix's type. Web's classes, house mono. */
function SnippetText({ className, style, ...props }: React.ComponentProps<typeof InputGroupText>) {
return <InputGroupText className={cn('font-normal', className)} style={[monoStyle, style]} {...props} />;
}
/** The command, readOnly and selectable. Value comes from the context, never the caller. */
function SnippetInput({ className, style, ...props }: Omit<React.ComponentProps<typeof InputGroupInput>, 'value' | 'readOnly'>) {
const { code } = useSnippet();
return (
<InputGroupInput
// Keyboard-proofing: the field is display text — no autocorrect crawl over a
// command, no soft-key suggestions over a copyable string.
autoCapitalize="none"
autoCorrect={false}
readOnly
value={code}
className={cn('text-sm text-foreground', className)}
style={[monoStyle, style]}
{...props}
/>
);
}
type SnippetCopyButtonProps = {
timeout?: number;
onCopy?: () => void;
onError?: (error: unknown) => void;
className?: string;
};
function SnippetCopyButton({ timeout = 2000, onCopy, onError, className }: SnippetCopyButtonProps) {
const { code } = useSnippet();
const [copied, setCopied] = React.useState(false);
const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
React.useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);
async function copy() {
try {
await Clipboard.setStringAsync(code);
setCopied(true);
onCopy?.();
timer.current = setTimeout(() => setCopied(false), timeout);
} catch (error) {
// Never silent — the web original calls onError and so do we.
onError?.(error);
}
}
return (
<Button
variant="ghost"
size="icon"
onPress={copy}
accessibilityLabel={copied ? 'Copied' : 'Copy'}
// House formula (confirmation.tsx): RNR's h-10 (40pt) + hitSlop 2/side = the 44pt
// platform minimum, with no pixel of extra chrome.
hitSlop={{ top: 2, bottom: 2, left: 2, right: 2 }}
className={className}
>
<Icon as={copied ? CheckIcon : CopyIcon} size={14} className="text-muted-foreground" />
</Button>
);
}
export { Snippet, SnippetAddon, SnippetCopyButton, SnippetInput, SnippetText, useSnippet };
Props
| Name | Type | Default | Description |
|---|---|---|---|
| code | string | — | The command. The one prop everything else reads. |
| children | React.ReactNode | — | — |
Theming
Themed via text-foreground, text-muted-foreground.
Usage
() => (
<Snippet code="pnpm dlx @react-native-reusables/cli add https://raw.githubusercontent.com/hackerpug-ai/rnr-ai-elements/v0.2.0/public/r/uniwind/conversation.json">
<SnippetText />
<SnippetCopyButton />
</Snippet>
)