साउंड और हैप्टिक्स

24 UI साउंड, जो बजते वक़्त ब्राउज़र में ही सिंथेसाइज़ होते हैं: कोई ऑडियो फ़ाइल नहीं, होस्ट करने को कुछ नहीं, एक 7 KB मॉड्यूल। हर साउंड C मेजर पेंटाटोनिक पर दो-तीन साइन पार्शियल, एक नरम अटैक और एक फ़िल्टर्ड क्लिक की छोटी-सी रेसिपी है, −14 dBFS पर मिक्स। साउंड तब तक बंद रहते हैं जब तक विज़िटर उन्हें चालू न करे, हर ब्राउज़र में याद रहते हैं, और यूज़र के इशारे से पहले कभी नहीं बजते। मोशन और साउंड अलग-अलग प्राथमिकताएँ हैं: prefers-reduced-motion इन्हें नहीं बदलता।

इस ब्राउज़र में साउंड बंद हैं। नीचे के प्ले बटन दोनों स्थिति में काम करते हैं; कंपोनेंट और data-sound तभी बजते हैं जब यह चालू हो।

इंस्टॉल करें

npx shadcn@latest add https://goodui.dev/r/sounds.json

यह lib/goodui-sounds.ts में आता है। जिन कंपोनेंट में sound prop है, वे इसे डिपेंडेंसी में रखते हैं, इसलिए यह उनके साथ आ जाता है। मॉड्यूल lazy लोड होता है और सिर्फ़ तब, जब किसी कंपोनेंट पर sound चालू हो, इसलिए जो ऐप इसे कभी इस्तेमाल नहीं करता, वह इसका एक बाइट भी शिप नहीं करता।

हर साउंड

सुनने के लिए कार्ड पर क्लिक करें। वेवफ़ॉर्म ऑफ़लाइन रेंडर किया गया साउंड है, नॉर्मलाइज़्ड, ताकि धीमे साउंड भी साफ़ दिखें।

कंपोनेंट

कंपोनेंट का डिफ़ॉल्ट साउंड बजाने के लिए sound पास करें, या कोई दूसरा चुनने के लिए साउंड का नाम। जब तक आप पास न करें, सब बंद रहता है।

  • button: क्लिक पर tap
  • stateful-button: tap, फिर नतीजे के साथ success या error
  • switch: toggle-on / toggle-off
  • checkbox: चेक होने पर pop, अनचेक होने पर tick
  • tabs: चुनने पर tick
  • rating: पॉइंटर के नीचे हर स्टार पर एक tick, पंक्ति में आगे बढ़ते हुए ऊँचा स्वर, चुनने पर pop
  • number-input: हर स्टेप पर tick, + के लिए ऊँचा, − के लिए नीचा
  • dialog: open / close
  • toast-stack (Toaster): हर नए टोस्ट पर success, error, warning या notify, लोडिंग टोस्ट पूरा होने पर फिर से
  • confetti: बर्स्ट के साथ celebrate (fireConfetti({ sound: true }) या <Confetti sound />)
  • hold-to-confirm: होल्ड शुरू होने पर tick, पूरा होने पर confirm
  • copy-button: कॉपी के बाद pop, फ़ेल होने पर error
<Button sound onClick={save}>Save</Button>
<Switch sound label="SMS reminders" />
<Rating sound defaultValue={4} />
<Dialog sound title="Cancel this booking?" onConfirm={cancel} trigger={<Button variant="outline">Cancel</Button>} />
<Toaster sound />                   {/* success, error, warning or notify per toast */}
<HoldToConfirm sound="unlock" onConfirm={deleteAccount}>Delete account</HoldToConfirm>

लोगों को स्विच दें

SoundProvider को एक बार माउंट करें और अपनी सेटिंग्स या हेडर में एक टॉगल रखें। विज़िटर की पसंद localStorage में सेव होती है और ऐप के डिफ़ॉल्ट पर भारी पड़ती है।

app/layout.tsx
import { SoundProvider } from "@/lib/goodui-sounds"

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <SoundProvider>{children}</SoundProvider>   {/* enabled: app default, persist, volume, attributes */}
      </body>
    </html>
  )
}
components/sound-toggle.tsx
"use client"
import { useSound } from "@/lib/goodui-sounds"

export function SoundToggle() {
  const { enabled, toggle, play } = useSound()
  return (
    <button type="button" role="switch" aria-checked={enabled} onClick={() => { toggle(); play(enabled ? "toggle-off" : "toggle-on", { force: true }) }}>
      Sounds {enabled ? "on" : "off"}
    </button>
  )
}

कोई भी एलिमेंट

provider माउंट होने के बाद कोई भी एलिमेंट बिना कोड के साउंड बजा सकता है। data-sound क्लिक पर बजता है (toggle क्लिक के बाद checked स्थिति पढ़ता है), data-sound-hover पॉइंटर के आने पर।

<button data-sound="tap">Book</button>
<input type="checkbox" data-sound="toggle" />
<a href="/pricing" data-sound-hover>Pricing</a>   {/* "hover", very subtle */}

API

import { play, useSound, setSoundEnabled, isSoundEnabled, setSoundVolume, SOUND_NAMES, type SoundName } from "@/lib/goodui-sounds"

play("success")                          // false when sounds are off, before the first gesture, or without Web Audio
play("tick", { rate: 1.1, volume: 0.6 }) // rate: pitch and speed, volume: 0..1 on top of the -14 dBFS bus
play("notify", { force: true })          // previews and settings: ignores the toggle
setSoundEnabled(true)                    // saved in localStorage (unless <SoundProvider persist={false} />)
setSoundVolume(0.5)

const { enabled, toggle, setEnabled, play } = useSound()   // reads "off" during SSR

अपना साउंड बनाएँ

रेसिपी voices की सूची है: ऑसिलेटर पार्शियल या फ़िल्टर्ड नॉइज़, हर एक का अपना start, length, level और वैकल्पिक glide। tone() अपने ऑक्टेव पार्शियल के साथ एक नोट देता है, noise() एक क्लिक या whoosh। इसे छोटा (350 ms से कम), धीमा (level 0.5 से कम) और स्केल पर रखें, तो यह बिल्ट-इन साउंड के बीच सहज बैठेगा।

import { defineSound, tone, noise, play } from "@/lib/goodui-sounds"

// A cash-register "cha-ching": a click, then two notes a fifth apart with a bright octave.
defineSound("cha-ching", [
  noise(0.02, { gain: 0.2, filter: 3000 }),
  ...tone(1046.5, 0.12, { at: 0.03, gain: 0.4, shine: 0.3 }),   // C6
  ...tone(1568, 0.26, { at: 0.11, gain: 0.42, shine: 0.3 }),    // G6
])

play("cha-ching")                 // or <button data-sound="cha-ching">
defineSound("tap", [...])         // built-ins can be replaced the same way

Voice फ़ील्ड: wave (sine, triangle, square, sawtooth या noise), freq और to Hz में, glide, at, dur और attack सेकंड में, gain 0..1, लोपास (ऑसिलेटर) या बैंडपास (noise) के लिए filter / filterTo / q, detune cents में।

टेस्टिंग

renderSound(name) रेसिपी को OfflineAudioContext से रेंडर करके बफ़र लौटाता है, इसलिए टेस्ट कुछ बजाए बिना जाँच सकता है कि साउंड न तो मौन है और न क्लिप हो रहा है।

const buffer = await renderSound("success")
const data = buffer.getChannelData(0)
const peak = Math.max(...data.map(Math.abs))   // the built-ins stay under 0.3 at volume 1

goodui Native: हैप्टिक्स

goodui Native (foundation/haptics.ts) में वही नाम हैप्टिक पैटर्न हैं, जो expo-haptics के impact, notification और selection टैप से टाइमिंग के साथ बने हैं। foundation/sounds का cue(name) एक ही कॉल में पैटर्न और साउंड दोनों बजाता है, इसलिए एक बार लिखी स्क्रीन हर प्लेटफ़ॉर्म पर सही महसूस होती है। साउंड तब तक बंद रहते हैं जब तक ऐप setSoundsEnabled(true) न बुलाए; iOS और Android पर वे उस प्लेयर से बजते हैं जिसे ऐप setSoundPlayer से रजिस्टर करता है (expo-audio अपनी छोटी फ़ाइलों के साथ), Expo web पर वे यहाँ की तरह सिंथेसाइज़ होते हैं।

  • tap: impact light
  • select, tick, count-tick: selection
  • toggle-on / toggle-off: impact light → medium / medium → light, 60 ms के अंतर पर
  • success, warning, error: उसी प्रकार का notification
  • notify: दो light impact, 90 ms के अंतर पर
  • send / receive: light → rigid / soft → light
  • delete: impact heavy
  • undo: light → medium
  • pop: impact rigid
  • whoosh / slide: तीन / दो selection
  • confirm: impact medium, फिर notification success
  • open / close: soft → light / light → soft
  • drag-start / drop: impact medium / heavy → light
  • celebrate: light, light, medium, फिर notification success
  • unlock: rigid, rigid, फिर notification success
  • hover: कुछ नहीं (टच पर hover नहीं होता)
import { cue, setSoundsEnabled } from "@/components/goodui-native/foundation"

setSoundsEnabled(settings.sounds)        // from your settings screen
cue("confirm")                           // haptic pattern + sound, one vocabulary