الأصوات والاهتزازات

24 صوت واجهة تُركَّب في المتصفح لحظة تشغيلها: بلا ملفات صوتية، لا شيء تستضيفه، وحدة واحدة بحجم 7 KB. كل صوت وصفة صغيرة من جزئيتين أو ثلاث جزئيات جيبية على سلم دو الكبير الخماسي، وهجمة ناعمة ونقرة مرشَّحة، مُمزَّجة عند ‎−14 dBFS. الأصوات متوقفة حتى يشغّلها الزائر، وتُحفظ لكل متصفح، ولا تُشغَّل أبدًا قبل إيماءة من المستخدم. الحركة والصوت تفضيلان منفصلان: prefers-reduced-motion لا يغيّرهما.

الأصوات معطّلة في هذا المتصفح. أزرار التشغيل أدناه تعمل في الحالتين؛ أما المكونات وdata-sound فلا تصدر صوتًا إلا عند التفعيل.

التثبيت

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

يصل إلى lib/goodui-sounds.ts. المكونات التي لها خاصية sound تدرجه كتبعية، فيأتي معها. تُحمَّل الوحدة بكسل وفقط حين يكون 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 يقرأ حالة التحديد بعد النقر)، و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

صمّم صوتك

الوصفة قائمة أصوات: جزئيات مذبذب أو ضجيج مرشَّح، لكل منها بداية ومدة ومستوى وانزلاق اختياري. tone() يعطي نغمة مع جزئيتها الأوكتافية، وnoise() نقرة أو هبّة. أبقه قصيرًا (أقل من 350 ms)، وهادئًا (مستويات أقل من 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

حقول الصوت: wave (sine أو triangle أو square أو sawtooth أو noise)، freq وto بالهرتز، glide، at وdur وattack بالثواني، gain من 0 إلى 1، filter / filterTo / q لمرشح التمرير المنخفض (المذبذبات) أو تمرير النطاق (الضجيج)، detune بالسنت.

الاختبار

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)، مبنية من نقرات impact وnotification وselection في expo-haptics مع توقيت. cue(name) من foundation/sounds يشغّل النمط والصوت في استدعاء واحد، فالشاشة المكتوبة مرة واحدة تشعر بها صحيحة على كل منصة. الأصوات متوقفة حتى يستدعي التطبيق setSoundsEnabled(true)؛ على iOS وAndroid تُشغَّل عبر مشغّل يسجّله التطبيق بـ setSoundPlayer (expo-audio بملفاته القصيرة الخاصة)، وعلى Expo web تُركَّب كما هنا.

  • tap: impact خفيف
  • select, tick, count-tick: selection
  • toggle-on / toggle-off: impact خفيف → متوسط / متوسط → خفيف، بفاصل 60 ms
  • success, warning, error: notification من النوع نفسه
  • notify: نبضتا impact خفيفتان، بفاصل 90 ms
  • send / receive: خفيف → صلب / ناعم → خفيف
  • delete: impact ثقيل
  • undo: خفيف → متوسط
  • pop: impact صلب
  • whoosh / slide: ثلاث / اثنتان من selection
  • confirm: impact متوسط، ثم notification نجاح
  • open / close: ناعم → خفيف / خفيف → ناعم
  • drag-start / drop: impact متوسط / ثقيل → خفيف
  • celebrate: خفيف، خفيف، متوسط، ثم notification نجاح
  • unlock: صلب، صلب، ثم notification نجاح
  • hover: لا شيء (لا تحويم على اللمس)
import { cue, setSoundsEnabled } from "@/components/goodui-native/foundation"

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