@nationaldesignstudio/react 0.5.4 → 0.5.6

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.
@@ -1,7 +1,7 @@
1
1
  # Component Registry
2
2
 
3
3
  > Auto-generated component metadata for AI/agent consumption.
4
- > Generated: 2026-01-16T16:52:21.427Z
4
+ > Generated: 2026-01-20T19:14:31.863Z
5
5
 
6
6
  ---
7
7
 
@@ -346,7 +346,7 @@ className="font-semibold" // Font weight 600
346
346
  <Button size="md" variant="primary">
347
347
  Primary
348
348
  </Button>
349
- <Button size="md" variant="primary-outline">
349
+ <Button size="md" variant="outline">
350
350
  Secondary
351
351
  </Button>
352
352
  </CardActions>
@@ -374,7 +374,7 @@ className="font-semibold" // Font weight 600
374
374
  <Button size="md" variant="primary">
375
375
  Primary
376
376
  </Button>
377
- <Button size="md" variant="primary-outline">
377
+ <Button size="md" variant="outline">
378
378
  Secondary
379
379
  </Button>
380
380
  </CardActions>
@@ -404,7 +404,7 @@ className="font-semibold" // Font weight 600
404
404
  <Button size="sm" variant="primary">
405
405
  Primary
406
406
  </Button>
407
- <Button size="sm" variant="primary-outline">
407
+ <Button size="sm" variant="outline">
408
408
  Secondary
409
409
  </Button>
410
410
  </CardActions>
@@ -538,7 +538,7 @@ className="font-semibold" // Font weight 600
538
538
  <Button size="md" variant="primary">
539
539
  Primary
540
540
  </Button>
541
- <Button size="md" variant="primary-outline">
541
+ <Button size="md" variant="outline">
542
542
  Secondary
543
543
  </Button>
544
544
  </CardActions>
@@ -1288,6 +1288,248 @@ className="font-semibold" // Font weight 600
1288
1288
  ```
1289
1289
 
1290
1290
 
1291
+ ### BlurredVideoBackdrop
1292
+
1293
+ **Import:** `import { BlurredVideoBackdrop } from "@nationaldesignstudio/react";`
1294
+
1295
+ **Category:** Other
1296
+
1297
+ **Props:**
1298
+
1299
+ - `videoRef`: React.RefObject<HTMLVideoElement | null> (required)
1300
+ Ref to the primary video element to create backdrop from (required) */
1301
+ - `opacity`: number
1302
+ Opacity of the backdrop (0-1, default: 0.6) */
1303
+ - `extension`: number
1304
+ Extension amount in pixels to cover blur artifacts (default: 120) */
1305
+ - `targetFps`: number
1306
+ Target FPS for canvas rendering (default: 30) */
1307
+ - `scale`: number
1308
+ Canvas scale factor - lower = better performance (default: 0.5) */
1309
+ - `showMetrics`: boolean
1310
+ Whether to show debug metrics */
1311
+
1312
+ **Examples:**
1313
+
1314
+ ```tsx
1315
+ <div className="relative w-full h-[600px] bg-black overflow-hidden">
1316
+ <BlurredVideoBackdrop {...args} videoRef={videoRef} />
1317
+ <div className="relative z-10 flex items-center justify-center h-full p-48">
1318
+ <div className="w-full max-w-[800px]">
1319
+ <VideoPlayer
1320
+ cloudflare={DGA_CLOUDFLARE}
1321
+ videoRef={videoRef}
1322
+ rounded="lg"
1323
+ />
1324
+ </div>
1325
+ </div>
1326
+ </div>
1327
+ ```
1328
+
1329
+ ```tsx
1330
+ <div className="relative w-full h-[500px] bg-black overflow-hidden">
1331
+ <BlurredVideoBackdrop videoRef={videoRef} blur="high" />
1332
+ <div className="relative z-10 flex items-center justify-center h-full p-48">
1333
+ <div className="w-full max-w-[640px]">
1334
+ <VideoPlayer
1335
+ cloudflare={DGA_CLOUDFLARE}
1336
+ videoRef={videoRef}
1337
+ rounded="md"
1338
+ />
1339
+ </div>
1340
+ </div>
1341
+ </div>
1342
+ ```
1343
+
1344
+ ```tsx
1345
+ <div className="flex flex-col gap-24 p-24">
1346
+ <h2 className="typography-h3-md text-white">Blur Intensity Levels</h2>
1347
+ <div className="grid grid-cols-2 gap-24">
1348
+ {configs.map(({ blur, ref }) => (
1349
+ <div key={blur} className="flex flex-col gap-8">
1350
+ <p className="text-white/80 text-14 capitalize">{blur}</p>
1351
+ <div className="relative h-[200px] bg-black rounded-8 overflow-hidden">
1352
+ <BlurredVideoBackdrop
1353
+ videoRef={ref}
1354
+ blur={blur}
1355
+ opacity={0.8}
1356
+ showMetrics
1357
+ />
1358
+ <div className="relative z-10 flex items-center justify-center h-full p-8">
1359
+ <div className="w-full max-w-[240px]">
1360
+ <VideoPlayer
1361
+ cloudflare={DGA_CLOUDFLARE}
1362
+ videoRef={ref}
1363
+ rounded="sm"
1364
+ />
1365
+ </div>
1366
+ </div>
1367
+ </div>
1368
+ </div>
1369
+ ))}
1370
+ </div>
1371
+ </div>
1372
+ ```
1373
+
1374
+ ```tsx
1375
+ <div className="flex flex-col gap-24 p-24">
1376
+ <h2 className="typography-h3-md text-white">Gradient Overlays</h2>
1377
+ <div className="grid grid-cols-3 gap-24">
1378
+ {configs.map(({ overlay, ref }) => (
1379
+ <div key={overlay} className="flex flex-col gap-8">
1380
+ <p className="text-white/80 text-14 capitalize">{overlay}</p>
1381
+ <div className="relative h-[200px] bg-black rounded-8 overflow-hidden">
1382
+ <BlurredVideoBackdrop
1383
+ videoRef={ref}
1384
+ blur="high"
1385
+ overlay={overlay}
1386
+ opacity={0.8}
1387
+ />
1388
+ <div className="relative z-10 flex items-center justify-center h-full p-8">
1389
+ <div className="w-full max-w-[200px]">
1390
+ <VideoPlayer
1391
+ cloudflare={DGA_CLOUDFLARE}
1392
+ videoRef={ref}
1393
+ rounded="sm"
1394
+ />
1395
+ </div>
1396
+ </div>
1397
+ </div>
1398
+ </div>
1399
+ ))}
1400
+ </div>
1401
+ </div>
1402
+ ```
1403
+
1404
+ ```tsx
1405
+ <div className="flex flex-col gap-24 p-24">
1406
+ <div>
1407
+ <h2 className="typography-h3-md text-white mb-8">
1408
+ Scale Factor Impact
1409
+ </h2>
1410
+ <p className="text-gray-400 text-14">
1411
+ Lower scale factors render at reduced resolution for better
1412
+ performance. The blur effect hides most quality loss.
1413
+ </p>
1414
+ </div>
1415
+ <div className="grid grid-cols-2 gap-16">
1416
+ {scales.map(({ scale, ref, label }) => (
1417
+ <div key={scale} className="flex flex-col gap-8">
1418
+ <p className="text-white/80 text-14">{label}</p>
1419
+ <div className="relative h-[250px] bg-black rounded-8 overflow-hidden">
1420
+ <BlurredVideoBackdrop
1421
+ videoRef={ref}
1422
+ blur="high"
1423
+ scale={scale}
1424
+ targetFps={30}
1425
+ showMetrics
1426
+ opacity={0.8}
1427
+ />
1428
+ <div className="relative z-10 flex items-center justify-center h-full p-16">
1429
+ <div className="w-full max-w-[300px]">
1430
+ <VideoPlayer
1431
+ cloudflare={DGA_CLOUDFLARE}
1432
+ videoRef={ref}
1433
+ rounded="sm"
1434
+ />
1435
+ </div>
1436
+ </div>
1437
+ </div>
1438
+ </div>
1439
+ ))}
1440
+ </div>
1441
+ </div>
1442
+ ```
1443
+
1444
+ ```tsx
1445
+ <div className="flex flex-col gap-16 p-24">
1446
+ <p className="text-14 text-gray-600 max-w-md">
1447
+ Click the button to open a video dialog with blurred backdrop. The
1448
+ backdrop renders from the same video element - no sync needed.
1449
+ </p>
1450
+ <Dialog
1451
+ trigger={<Button>Watch Video with Blur Backdrop</Button>}
1452
+ size="full"
1453
+ variant="minimal"
1454
+ showClose={true}
1455
+ open={open}
1456
+ onOpenChange={setOpen}
1457
+ >
1458
+ <div className="relative w-full h-full overflow-hidden">
1459
+ <BlurredVideoBackdrop
1460
+ videoRef={videoRef}
1461
+ blur="high"
1462
+ overlay="vignette"
1463
+ opacity={0.6}
1464
+ />
1465
+ <div className="relative z-10 flex items-center justify-center h-full p-24">
1466
+ <div className="w-full max-w-[960px]">
1467
+ <VideoPlayer
1468
+ cloudflare={DGA_CLOUDFLARE}
1469
+ videoRef={videoRef}
1470
+ autoPlay={open}
1471
+ rounded="lg"
1472
+ />
1473
+ </div>
1474
+ </div>
1475
+ </div>
1476
+ </Dialog>
1477
+ <pre className="text-12 bg-gray-100 p-12 rounded-4 overflow-x-auto">
1478
+ {`const videoRef = useRef<HTMLVideoElement>(null);
1479
+ <Dialog size="full" variant="minimal">
1480
+ <div className="relative w-full h-full overflow-hidden">
1481
+ <BlurredVideoBackdrop
1482
+ videoRef={videoRef}
1483
+ blur="high"
1484
+ overlay="vignette"
1485
+ />
1486
+ <div className="relative z-10 flex items-center justify-center h-full">
1487
+ <VideoPlayer
1488
+ cloudflare={cloudflare}
1489
+ videoRef={videoRef}
1490
+ autoPlay
1491
+ />
1492
+ </div>
1493
+ </div>
1494
+ </Dialog>`}
1495
+ </pre>
1496
+ </div>
1497
+ ```
1498
+
1499
+ ```tsx
1500
+ <div className="relative w-full h-[600px] bg-black overflow-hidden">
1501
+ <BlurredVideoBackdrop
1502
+ videoRef={videoRef}
1503
+ blur="extreme"
1504
+ overlay="top-bottom"
1505
+ opacity={0.5}
1506
+ />
1507
+ {/* Hidden video that drives the backdrop */}
1508
+ <video
1509
+ ref={videoRef}
1510
+ className="hidden"
1511
+ src={`https://customer-${DGA_CLOUDFLARE.customerCode}.cloudflarestream.com/${DGA_CLOUDFLARE.videoId}/manifest/video.m3u8`}
1512
+ autoPlay
1513
+ muted
1514
+ loop
1515
+ playsInline
1516
+ />
1517
+ <div className="relative z-10 flex flex-col items-center justify-center h-full text-center px-24">
1518
+ <h1 className="typography-h1-lg text-white mb-16">
1519
+ Welcome to the Future
1520
+ </h1>
1521
+ <p className="typography-body-lg-md text-white/80 max-w-xl mb-32">
1522
+ Experience ambient video backgrounds with our BlurredVideoBackdrop
1523
+ component. Perfect for hero sections and video modals.
1524
+ </p>
1525
+ <Button variant="primary" size="lg">
1526
+ Get Started
1527
+ </Button>
1528
+ </div>
1529
+ </div>
1530
+ ```
1531
+
1532
+
1291
1533
  ### CardGrid
1292
1534
 
1293
1535
  **Import:** `import { CardGrid } from "@nationaldesignstudio/react";`
@@ -1501,6 +1743,360 @@ className="font-semibold" // Font weight 600
1501
1743
  ```
1502
1744
 
1503
1745
 
1746
+ ### Dialog
1747
+
1748
+ **Import:** `import { Dialog } from "@nationaldesignstudio/react";`
1749
+
1750
+ **Category:** Other
1751
+
1752
+ **Props:**
1753
+
1754
+ - `children`: React.ReactNode (required)
1755
+ The content to show in the dialog */
1756
+ - `trigger`: React.ReactNode
1757
+ The element that triggers the dialog (optional for controlled mode) */
1758
+ - `title`: React.ReactNode
1759
+ Title for the dialog */
1760
+ - `description`: React.ReactNode
1761
+ Description for the dialog */
1762
+ - `size`: "sm" | "md" | "lg" | "xl" | "full"
1763
+ Size of the dialog */
1764
+ Values: `sm`, `md`, `lg`, `xl`, `full`
1765
+ - `variant`: "default" | "minimal"
1766
+ Visual variant: "default" for card style, "minimal" for borderless (video modals) */
1767
+ Values: `default`, `minimal`
1768
+ - `showClose`: boolean
1769
+ Whether to show a close button */
1770
+ - `open`: boolean
1771
+ Controlled open state */
1772
+ - `defaultOpen`: boolean
1773
+ Default open state */
1774
+ - `onOpenChange`: (open: boolean) => void
1775
+ Callback when open state changes */
1776
+ - `className`: string
1777
+ Additional className for the popup */
1778
+
1779
+ **Examples:**
1780
+
1781
+ ```tsx
1782
+ <Dialog
1783
+ {...args}
1784
+ trigger={<Button>Open Dialog</Button>}
1785
+ title="Dialog Title"
1786
+ description="This is a description of the dialog content."
1787
+ >
1788
+ <p className="typography-body-md-md mt-16">
1789
+ This is the dialog body content. You can put any content here including
1790
+ text, forms, or other components.
1791
+ </p>
1792
+ </Dialog>
1793
+ ```
1794
+
1795
+ ```tsx
1796
+ <Dialog
1797
+ trigger={<Button>Open Dialog</Button>}
1798
+ title="Welcome"
1799
+ description="This is a simple dialog."
1800
+ >
1801
+ <p className="typography-body-md-md mt-16">
1802
+ Dialog content goes here. The dialog automatically handles focus
1803
+ trapping, scroll locking, and keyboard navigation.
1804
+ </p>
1805
+ </Dialog>
1806
+ ```
1807
+
1808
+ ```tsx
1809
+ <Dialog
1810
+ trigger={<Button variant="outline">Small Dialog</Button>}
1811
+ title="Delete Item?"
1812
+ description="This action cannot be undone."
1813
+ size="sm"
1814
+ >
1815
+ <div className="mt-24 flex justify-end gap-12">
1816
+ <Button variant="secondary" size="sm">
1817
+ Cancel
1818
+ </Button>
1819
+ <Button variant="destructive" size="sm">
1820
+ Delete
1821
+ </Button>
1822
+ </div>
1823
+ </Dialog>
1824
+ ```
1825
+
1826
+ ```tsx
1827
+ <Dialog
1828
+ trigger={<Button variant="outline">Medium Dialog</Button>}
1829
+ title="Edit Profile"
1830
+ description="Make changes to your profile here."
1831
+ size="md"
1832
+ >
1833
+ <div className="mt-24 flex flex-col gap-16">
1834
+ <Input placeholder="Display name" />
1835
+ <Input placeholder="Email address" type="email" />
1836
+ </div>
1837
+ <div className="mt-24 flex justify-end gap-12">
1838
+ <Button variant="secondary">Cancel</Button>
1839
+ <Button>Save Changes</Button>
1840
+ </div>
1841
+ </Dialog>
1842
+ ```
1843
+
1844
+ ```tsx
1845
+ <Dialog
1846
+ trigger={<Button variant="outline">Large Dialog</Button>}
1847
+ title="Terms of Service"
1848
+ size="lg"
1849
+ >
1850
+ <div className="mt-16 max-h-[400px] overflow-y-auto rounded-surface-ui-small border border-overlay-border bg-bg-section p-16">
1851
+ <p className="typography-body-sm-md text-text-secondary">
1852
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
1853
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
1854
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut
1855
+ aliquip ex ea commodo consequat.
1856
+ </p>
1857
+ <p className="typography-body-sm-md mt-16 text-text-secondary">
1858
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
1859
+ dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
1860
+ proident, sunt in culpa qui officia deserunt mollit anim id est
1861
+ laborum.
1862
+ </p>
1863
+ <p className="typography-body-sm-md mt-16 text-text-secondary">
1864
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem
1865
+ accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae
1866
+ ab illo inventore veritatis et quasi architecto beatae vitae dicta
1867
+ sunt explicabo.
1868
+ </p>
1869
+ </div>
1870
+ <div className="mt-24 flex justify-end gap-12">
1871
+ <Button variant="secondary">Decline</Button>
1872
+ <Button>Accept</Button>
1873
+ </div>
1874
+ </Dialog>
1875
+ ```
1876
+
1877
+ ```tsx
1878
+ <Dialog
1879
+ trigger={<Button variant="outline">XL Dialog</Button>}
1880
+ title="Dashboard Settings"
1881
+ description="Configure your dashboard preferences."
1882
+ size="xl"
1883
+ >
1884
+ <div className="mt-24 grid grid-cols-2 gap-24">
1885
+ <div className="flex flex-col gap-16">
1886
+ <h3 className="typography-body-md-md font-semibold">
1887
+ Display Settings
1888
+ </h3>
1889
+ <Input placeholder="Dashboard name" />
1890
+ <Input placeholder="Refresh interval (seconds)" type="number" />
1891
+ </div>
1892
+ <div className="flex flex-col gap-16">
1893
+ <h3 className="typography-body-md-md font-semibold">Data Sources</h3>
1894
+ <Input placeholder="API endpoint" />
1895
+ <Input placeholder="API key" type="password" />
1896
+ </div>
1897
+ </div>
1898
+ <div className="mt-24 flex justify-end gap-12">
1899
+ <Button variant="secondary">Cancel</Button>
1900
+ <Button>Save Settings</Button>
1901
+ </div>
1902
+ </Dialog>
1903
+ ```
1904
+
1905
+ ```tsx
1906
+ <Dialog
1907
+ trigger={<Button variant="outline">Full Dialog</Button>}
1908
+ title="Image Gallery"
1909
+ size="full"
1910
+ >
1911
+ <div className="mt-24 grid flex-1 grid-cols-3 gap-16 overflow-y-auto">
1912
+ {Array.from({ length: 9 }).map((_, i) => (
1913
+ <div
1914
+ key={`image-${i + 1}`}
1915
+ className="aspect-video rounded-surface-card bg-bg-section-secondary"
1916
+ />
1917
+ ))}
1918
+ </div>
1919
+ </Dialog>
1920
+ ```
1921
+
1922
+ ```tsx
1923
+ <div className="flex flex-wrap items-center gap-12">
1924
+ <Dialog
1925
+ trigger={<Button variant="outline">Small</Button>}
1926
+ title="Small Dialog"
1927
+ size="sm"
1928
+ >
1929
+ <p className="typography-body-md-md mt-16">
1930
+ This is a small dialog for simple confirmations.
1931
+ </p>
1932
+ </Dialog>
1933
+ <Dialog
1934
+ trigger={<Button variant="outline">Medium</Button>}
1935
+ title="Medium Dialog"
1936
+ size="md"
1937
+ >
1938
+ <p className="typography-body-md-md mt-16">
1939
+ This is the default medium size dialog.
1940
+ </p>
1941
+ </Dialog>
1942
+ <Dialog
1943
+ trigger={<Button variant="outline">Large</Button>}
1944
+ title="Large Dialog"
1945
+ size="lg"
1946
+ >
1947
+ <p className="typography-body-md-md mt-16">
1948
+ This is a large dialog for more content.
1949
+ </p>
1950
+ </Dialog>
1951
+ <Dialog
1952
+ trigger={<Button variant="outline">XL</Button>}
1953
+ title="Extra Large Dialog"
1954
+ size="xl"
1955
+ >
1956
+ <p className="typography-body-md-md mt-16">
1957
+ This is an extra large dialog for complex layouts.
1958
+ </p>
1959
+ </Dialog>
1960
+ <Dialog
1961
+ trigger={<Button variant="outline">Full</Button>}
1962
+ title="Full Dialog"
1963
+ size="full"
1964
+ >
1965
+ <p className="typography-body-md-md mt-16">
1966
+ This is a full-size dialog that takes up most of the viewport.
1967
+ </p>
1968
+ </Dialog>
1969
+ </div>
1970
+ ```
1971
+
1972
+ ```tsx
1973
+ <Dialog
1974
+ trigger={<Button variant="destructive">Delete Account</Button>}
1975
+ title="Are you absolutely sure?"
1976
+ description="This action cannot be undone. This will permanently delete your account and remove your data from our servers."
1977
+ size="sm"
1978
+ >
1979
+ <div className="mt-24 flex justify-end gap-12">
1980
+ <Button variant="secondary">Cancel</Button>
1981
+ <Button variant="destructive">Yes, delete my account</Button>
1982
+ </div>
1983
+ </Dialog>
1984
+ ```
1985
+
1986
+ ```tsx
1987
+ <Dialog
1988
+ trigger={<Button>Add New User</Button>}
1989
+ title="Create User"
1990
+ description="Add a new user to your organization."
1991
+ >
1992
+ <form className="mt-24 flex flex-col gap-16">
1993
+ <Input placeholder="Full name" />
1994
+ <Input placeholder="Email address" type="email" />
1995
+ <Input placeholder="Role" />
1996
+ <div className="mt-8 flex justify-end gap-12">
1997
+ <Button variant="secondary" type="button">
1998
+ Cancel
1999
+ </Button>
2000
+ <Button type="submit">Create User</Button>
2001
+ </div>
2002
+ </form>
2003
+ </Dialog>
2004
+ ```
2005
+
2006
+ ```tsx
2007
+ <div className="flex flex-col items-center gap-16">
2008
+ <Dialog
2009
+ trigger={<Button>Controlled Dialog</Button>}
2010
+ title="Controlled"
2011
+ description="This dialog's state is controlled externally."
2012
+ open={open}
2013
+ onOpenChange={setOpen}
2014
+ >
2015
+ <p className="typography-body-md-md mt-16">
2016
+ The dialog is {open ? "open" : "closed"}.
2017
+ </p>
2018
+ </Dialog>
2019
+ <div className="flex gap-12">
2020
+ <Button variant="outline" onClick={() => setOpen(true)}>
2021
+ Open from outside
2022
+ </Button>
2023
+ <Button variant="outline" onClick={() => setOpen(false)}>
2024
+ Close from outside
2025
+ </Button>
2026
+ </div>
2027
+ </div>
2028
+ ```
2029
+
2030
+ ```tsx
2031
+ <DialogParts>
2032
+ <DialogTrigger>
2033
+ <Button variant="primary">Custom Dialog</Button>
2034
+ </DialogTrigger>
2035
+ <DialogPortal>
2036
+ <DialogBackdrop />
2037
+ <DialogPopup size="md">
2038
+ <DialogClose>
2039
+ <svg
2040
+ width="16"
2041
+ height="16"
2042
+ viewBox="0 0 16 16"
2043
+ fill="none"
2044
+ aria-hidden="true"
2045
+ >
2046
+ <path
2047
+ d="M2 2L14 14M2 14L14 2"
2048
+ stroke="currentColor"
2049
+ strokeWidth="2"
2050
+ strokeLinecap="round"
2051
+ />
2052
+ </svg>
2053
+ <span className="sr-only">Close</span>
2054
+ </DialogClose>
2055
+ <DialogTitle>Custom Dialog Title</DialogTitle>
2056
+ <DialogDescription>
2057
+ Built with compound components for maximum flexibility.
2058
+ </DialogDescription>
2059
+ <DialogBody>
2060
+ <p className="typography-body-md-md mt-16">
2061
+ This dialog is built using individual compound components, giving
2062
+ you complete control over the structure and styling.
2063
+ </p>
2064
+ </DialogBody>
2065
+ <DialogFooter>
2066
+ <Button variant="secondary">Cancel</Button>
2067
+ <Button>Confirm</Button>
2068
+ </DialogFooter>
2069
+ </DialogPopup>
2070
+ </DialogPortal>
2071
+ </DialogParts>
2072
+ ```
2073
+
2074
+ ```tsx
2075
+ <Dialog
2076
+ trigger={<Button>Open Parent Dialog</Button>}
2077
+ title="Parent Dialog"
2078
+ description="This dialog contains another dialog."
2079
+ size="lg"
2080
+ >
2081
+ <div className="mt-24 flex flex-col gap-16">
2082
+ <p className="typography-body-md-md">
2083
+ Click the button below to open a nested dialog.
2084
+ </p>
2085
+ <Dialog
2086
+ trigger={<Button variant="secondary">Open Nested Dialog</Button>}
2087
+ title="Nested Dialog"
2088
+ description="This is a nested dialog."
2089
+ size="sm"
2090
+ >
2091
+ <p className="typography-body-md-md mt-16">
2092
+ Nested dialog content. Focus is properly trapped here.
2093
+ </p>
2094
+ </Dialog>
2095
+ </div>
2096
+ </Dialog>
2097
+ ```
2098
+
2099
+
1504
2100
  ### FaqSection
1505
2101
 
1506
2102
  **Import:** `import { FaqSection } from "@nationaldesignstudio/react";`
@@ -2852,7 +3448,7 @@ className="font-semibold" // Font weight 600
2852
3448
  ```tsx
2853
3449
  <div className="flex flex-col gap-16 max-w-[320px]">
2854
3450
  <div>
2855
- <p className="mb-8 text-12 text-text-muted">Default</p>
3451
+ <p className="mb-8 typography-body-sm-sm text-text-muted">Default</p>
2856
3452
  <Select>
2857
3453
  <SelectTrigger placeholder="Select option..." />
2858
3454
  <SelectPopup>
@@ -2863,7 +3459,7 @@ className="font-semibold" // Font weight 600
2863
3459
  </Select>
2864
3460
  </div>
2865
3461
  <div>
2866
- <p className="mb-8 text-12 text-text-muted">With Value</p>
3462
+ <p className="mb-8 typography-body-sm-sm text-text-muted">With Value</p>
2867
3463
  <Select defaultValue="option2">
2868
3464
  <SelectTrigger placeholder="Select option..." />
2869
3465
  <SelectPopup>
@@ -2874,7 +3470,7 @@ className="font-semibold" // Font weight 600
2874
3470
  </Select>
2875
3471
  </div>
2876
3472
  <div>
2877
- <p className="mb-8 text-12 text-text-muted">Error</p>
3473
+ <p className="mb-8 typography-body-sm-sm text-text-muted">Invalid</p>
2878
3474
  <Select>
2879
3475
  <SelectTrigger placeholder="Select option..." error />
2880
3476
  <SelectPopup>
@@ -2885,7 +3481,7 @@ className="font-semibold" // Font weight 600
2885
3481
  </Select>
2886
3482
  </div>
2887
3483
  <div>
2888
- <p className="mb-8 text-12 text-text-muted">Disabled</p>
3484
+ <p className="mb-8 typography-body-sm-sm text-text-muted">Disabled</p>
2889
3485
  <Select disabled>
2890
3486
  <SelectTrigger placeholder="Select option..." />
2891
3487
  <SelectPopup>
@@ -2940,7 +3536,7 @@ className="font-semibold" // Font weight 600
2940
3536
  ```tsx
2941
3537
  <div className="flex flex-col gap-16 max-w-[320px]">
2942
3538
  <div>
2943
- <p className="mb-8 text-12 text-text-muted">Small (36px)</p>
3539
+ <p className="mb-8 typography-body-sm-sm text-text-muted">Small (36px)</p>
2944
3540
  <Select>
2945
3541
  <SelectTrigger size="sm" placeholder="Select option..." />
2946
3542
  <SelectPopup>
@@ -2951,7 +3547,9 @@ className="font-semibold" // Font weight 600
2951
3547
  </Select>
2952
3548
  </div>
2953
3549
  <div>
2954
- <p className="mb-8 text-12 text-text-muted">Default (48px)</p>
3550
+ <p className="mb-8 typography-body-sm-sm text-text-muted">
3551
+ Default (48px)
3552
+ </p>
2955
3553
  <Select>
2956
3554
  <SelectTrigger size="default" placeholder="Select option..." />
2957
3555
  <SelectPopup>
@@ -2962,7 +3560,7 @@ className="font-semibold" // Font weight 600
2962
3560
  </Select>
2963
3561
  </div>
2964
3562
  <div>
2965
- <p className="mb-8 text-12 text-text-muted">Large (56px)</p>
3563
+ <p className="mb-8 typography-body-sm-sm text-text-muted">Large (56px)</p>
2966
3564
  <Select>
2967
3565
  <SelectTrigger size="lg" placeholder="Select option..." />
2968
3566
  <SelectPopup>
@@ -2975,11 +3573,89 @@ className="font-semibold" // Font weight 600
2975
3573
  </div>
2976
3574
  ```
2977
3575
 
3576
+ ```tsx
3577
+ <div className="flex flex-col gap-24 max-w-[320px]">
3578
+ <div>
3579
+ <p className="mb-8 typography-body-sm-sm text-text-muted">
3580
+ alignItemWithTrigger=true (default)
3581
+ </p>
3582
+ <Select defaultValue="option2">
3583
+ <SelectTrigger placeholder="Select option..." />
3584
+ <SelectPopup alignItemWithTrigger={true}>
3585
+ <SelectOption value="option1">Option 1</SelectOption>
3586
+ <SelectOption value="option2">Option 2</SelectOption>
3587
+ <SelectOption value="option3">Option 3</SelectOption>
3588
+ </SelectPopup>
3589
+ </Select>
3590
+ </div>
3591
+ <div>
3592
+ <p className="mb-8 typography-body-sm-sm text-text-muted">
3593
+ alignItemWithTrigger=false
3594
+ </p>
3595
+ <Select defaultValue="option2">
3596
+ <SelectTrigger placeholder="Select option..." />
3597
+ <SelectPopup alignItemWithTrigger={false}>
3598
+ <SelectOption value="option1">Option 1</SelectOption>
3599
+ <SelectOption value="option2">Option 2</SelectOption>
3600
+ <SelectOption value="option3">Option 3</SelectOption>
3601
+ </SelectPopup>
3602
+ </Select>
3603
+ </div>
3604
+ </div>
3605
+ ```
3606
+
3607
+ ```tsx
3608
+ <div className="w-[320px]">
3609
+ <Select>
3610
+ <SelectTrigger placeholder="Select a fruit..." />
3611
+ <SelectPopup>
3612
+ <SelectGroup>
3613
+ <SelectGroupLabel>Fruits</SelectGroupLabel>
3614
+ <SelectOption value="apple">Apple</SelectOption>
3615
+ <SelectOption value="banana">Banana</SelectOption>
3616
+ <SelectOption value="orange">Orange</SelectOption>
3617
+ </SelectGroup>
3618
+ <SelectSeparator />
3619
+ <SelectGroup>
3620
+ <SelectGroupLabel>Vegetables</SelectGroupLabel>
3621
+ <SelectOption value="carrot">Carrot</SelectOption>
3622
+ <SelectOption value="broccoli">Broccoli</SelectOption>
3623
+ <SelectOption value="spinach">Spinach</SelectOption>
3624
+ </SelectGroup>
3625
+ <SelectSeparator />
3626
+ <SelectGroup>
3627
+ <SelectGroupLabel>Proteins</SelectGroupLabel>
3628
+ <SelectOption value="chicken">Chicken</SelectOption>
3629
+ <SelectOption value="beef">Beef</SelectOption>
3630
+ <SelectOption value="tofu">Tofu</SelectOption>
3631
+ </SelectGroup>
3632
+ </SelectPopup>
3633
+ </Select>
3634
+ </div>
3635
+ ```
3636
+
3637
+ ```tsx
3638
+ <div className="w-[320px]">
3639
+ <Select>
3640
+ <SelectTrigger placeholder="Select a state..." />
3641
+ <SelectPopup className="max-h-[200px]">
3642
+ <SelectScrollUpArrow />
3643
+ {allStates.map((state) => (
3644
+ <SelectOption key={state.value} value={state.value}>
3645
+ {state.label}
3646
+ </SelectOption>
3647
+ ))}
3648
+ <SelectScrollDownArrow />
3649
+ </SelectPopup>
3650
+ </Select>
3651
+ </div>
3652
+ ```
3653
+
2978
3654
  ```tsx
2979
3655
  <div className="flex flex-col gap-8 max-w-[320px]">
2980
3656
  <label
2981
3657
  htmlFor="state-select"
2982
- className="text-14 font-medium text-text-primary"
3658
+ className="typography-body-md-md font-medium text-text-primary"
2983
3659
  >
2984
3660
  State / Territory
2985
3661
  </label>
@@ -2997,10 +3673,10 @@ className="font-semibold" // Font weight 600
2997
3673
  ```
2998
3674
 
2999
3675
  ```tsx
3000
- <div className="flex flex-col gap-8 max-w-[320px]">
3676
+ <div className="flex flex-col gap-8 max-w-[320px]" data-invalid>
3001
3677
  <label
3002
3678
  htmlFor="state-error"
3003
- className="text-14 font-medium text-text-primary"
3679
+ className="typography-body-md-md font-medium text-text-primary"
3004
3680
  >
3005
3681
  State / Territory
3006
3682
  </label>
@@ -3014,7 +3690,9 @@ className="font-semibold" // Font weight 600
3014
3690
  ))}
3015
3691
  </SelectPopup>
3016
3692
  </Select>
3017
- <p className="text-12 text-ui-error-color">Please select a state</p>
3693
+ <p className="typography-body-sm-sm text-ui-error-color">
3694
+ Please select a state
3695
+ </p>
3018
3696
  </div>
3019
3697
  ```
3020
3698
 
@@ -3022,7 +3700,7 @@ className="font-semibold" // Font weight 600
3022
3700
  <div className="flex flex-col gap-8 max-w-[320px]">
3023
3701
  <label
3024
3702
  htmlFor="required-select"
3025
- className="text-14 font-medium text-text-primary"
3703
+ className="typography-body-md-md font-medium text-text-primary"
3026
3704
  >
3027
3705
  State / Territory <span className="text-ui-error-color">*</span>
3028
3706
  </label>
@@ -3058,33 +3736,22 @@ className="font-semibold" // Font weight 600
3058
3736
  </div>
3059
3737
  ```
3060
3738
 
3061
- ```tsx
3062
- <div className="w-[320px]">
3063
- <Select>
3064
- <SelectTrigger placeholder="Select a state..." />
3065
- <SelectPopup>
3066
- {states.map((state) => (
3067
- <SelectOption key={state.value} value={state.value}>
3068
- {state.label}
3069
- </SelectOption>
3070
- ))}
3071
- </SelectPopup>
3072
- </Select>
3073
- </div>
3074
- ```
3075
-
3076
3739
  ```tsx
3077
3740
  <div className="flex flex-col gap-16 max-w-[500px]">
3078
- <p className="text-14 text-text-muted">
3741
+ <p className="typography-body-md-md text-text-muted">
3079
3742
  Select and Input share the same base styling for consistent forms
3080
3743
  </p>
3081
3744
  <div className="flex gap-12">
3082
3745
  <div className="flex-1">
3083
- <p className="block mb-8 text-12 text-text-muted">Input</p>
3746
+ <p className="block mb-8 typography-body-sm-sm text-text-muted">
3747
+ Input
3748
+ </p>
3084
3749
  <Input placeholder="Enter text..." />
3085
3750
  </div>
3086
3751
  <div className="flex-1">
3087
- <p className="block mb-8 text-12 text-text-muted">Select</p>
3752
+ <p className="block mb-8 typography-body-sm-sm text-text-muted">
3753
+ Select
3754
+ </p>
3088
3755
  <Select>
3089
3756
  <SelectTrigger placeholder="Select option..." />
3090
3757
  <SelectPopup>
@@ -3096,11 +3763,15 @@ className="font-semibold" // Font weight 600
3096
3763
  </div>
3097
3764
  <div className="flex gap-12">
3098
3765
  <div className="flex-1">
3099
- <p className="block mb-8 text-12 text-text-muted">Input (Error)</p>
3766
+ <p className="block mb-8 typography-body-sm-sm text-text-muted">
3767
+ Input (Invalid)
3768
+ </p>
3100
3769
  <Input error placeholder="Enter text..." />
3101
3770
  </div>
3102
3771
  <div className="flex-1">
3103
- <p className="block mb-8 text-12 text-text-muted">Select (Error)</p>
3772
+ <p className="block mb-8 typography-body-sm-sm text-text-muted">
3773
+ Select (Invalid)
3774
+ </p>
3104
3775
  <Select>
3105
3776
  <SelectTrigger error placeholder="Select option..." />
3106
3777
  <SelectPopup>
@@ -3112,11 +3783,13 @@ className="font-semibold" // Font weight 600
3112
3783
  </div>
3113
3784
  <div className="flex gap-12">
3114
3785
  <div className="flex-1">
3115
- <p className="block mb-8 text-12 text-text-muted">Input (Disabled)</p>
3786
+ <p className="block mb-8 typography-body-sm-sm text-text-muted">
3787
+ Input (Disabled)
3788
+ </p>
3116
3789
  <Input disabled placeholder="Enter text..." />
3117
3790
  </div>
3118
3791
  <div className="flex-1">
3119
- <p className="block mb-8 text-12 text-text-muted">
3792
+ <p className="block mb-8 typography-body-sm-sm text-text-muted">
3120
3793
  Select (Disabled)
3121
3794
  </p>
3122
3795
  <Select disabled>
@@ -3657,6 +4330,579 @@ className="font-semibold" // Font weight 600
3657
4330
  ```
3658
4331
 
3659
4332
 
4333
+ ### VideoDialog
4334
+
4335
+ **Import:** `import { VideoDialog } from "@nationaldesignstudio/react";`
4336
+
4337
+ **Category:** Other
4338
+
4339
+ **Props:**
4340
+
4341
+ - `trigger`: React.ReactNode (required)
4342
+ Trigger element that opens the dialog */
4343
+ - `blur`: BlurredVideoBackdropProps["blur"]
4344
+ Blur intensity for backdrop (default: high) */
4345
+ - `overlay`: BlurredVideoBackdropProps["overlay"]
4346
+ Gradient overlay for backdrop (default: vignette) */
4347
+ - `backdropOpacity`: number
4348
+ Backdrop opacity (default: 0.6) */
4349
+ - `showClose`: boolean
4350
+ Whether to show close button (default: true) */
4351
+ - `closePosition`: "top-right" | "top-left"
4352
+ Close button position (default: top-right) */
4353
+ Values: `top-right`, `top-left`
4354
+ - `rounded`: VideoPlayerProps["rounded"]
4355
+ Video player rounded corners (default: lg) */
4356
+ - `open`: boolean
4357
+ Controlled open state */
4358
+ - `defaultOpen`: boolean
4359
+ Default open state */
4360
+ - `onOpenChange`: (open: boolean) => void
4361
+ Callback when open state changes */
4362
+ - `className`: string
4363
+ Additional className for the dialog container */
4364
+
4365
+ **Examples:**
4366
+
4367
+ ```tsx
4368
+ <VideoDialog {...args} trigger={<Button size="lg">Watch Video</Button>} />
4369
+ ```
4370
+
4371
+ ```tsx
4372
+ <div className="flex flex-col gap-16 items-center">
4373
+ <p className="text-14 text-gray-600 max-w-md text-center">
4374
+ Click the button to open a fullscreen video modal with the blurred
4375
+ backdrop effect. The blur video stays synchronized with the main video.
4376
+ </p>
4377
+ <VideoDialog
4378
+ trigger={<Button size="lg">Watch Video</Button>}
4379
+ cloudflare={DGA_CLOUDFLARE}
4380
+ blur="high"
4381
+ overlay="vignette"
4382
+ />
4383
+ </div>
4384
+ ```
4385
+
4386
+ ```tsx
4387
+ <div className="flex flex-wrap gap-12 justify-center">
4388
+ {(["low", "medium", "high", "extreme"] as const).map((blur) => (
4389
+ <VideoDialog
4390
+ key={blur}
4391
+ trigger={
4392
+ <Button variant="secondary" size="md">
4393
+ blur=&quot;{blur}&quot;
4394
+ </Button>
4395
+ }
4396
+ cloudflare={DGA_CLOUDFLARE}
4397
+ blur={blur}
4398
+ overlay="vignette"
4399
+ />
4400
+ ))}
4401
+ </div>
4402
+ ```
4403
+
4404
+ ```tsx
4405
+ <div className="flex flex-wrap gap-12 justify-center">
4406
+ {(["none", "vignette", "top-bottom"] as const).map((overlay) => (
4407
+ <VideoDialog
4408
+ key={overlay}
4409
+ trigger={
4410
+ <Button variant="secondary" size="md">
4411
+ overlay=&quot;{overlay}&quot;
4412
+ </Button>
4413
+ }
4414
+ cloudflare={DGA_CLOUDFLARE}
4415
+ blur="high"
4416
+ overlay={overlay}
4417
+ />
4418
+ ))}
4419
+ </div>
4420
+ ```
4421
+
4422
+ ```tsx
4423
+ <div className="flex flex-col gap-16 items-center">
4424
+ <p className="text-14 text-gray-600 max-w-md text-center">
4425
+ Controlled state example. The dialog can be opened/closed
4426
+ programmatically.
4427
+ </p>
4428
+ <div className="flex gap-12">
4429
+ <Button onClick={() => setOpen(true)}>Open Dialog</Button>
4430
+ <Button variant="secondary" onClick={() => setOpen(false)}>
4431
+ Close Dialog (No-op when closed)
4432
+ </Button>
4433
+ </div>
4434
+ <VideoDialog
4435
+ trigger={<Button variant="secondary">Using Trigger</Button>}
4436
+ cloudflare={DGA_CLOUDFLARE}
4437
+ open={open}
4438
+ onOpenChange={setOpen}
4439
+ />
4440
+ </div>
4441
+ ```
4442
+
4443
+ ```tsx
4444
+ <div className="flex gap-12 justify-center">
4445
+ <VideoDialog
4446
+ trigger={
4447
+ <Button variant="secondary" size="md">
4448
+ Close Top-Right
4449
+ </Button>
4450
+ }
4451
+ cloudflare={DGA_CLOUDFLARE}
4452
+ closePosition="top-right"
4453
+ />
4454
+ <VideoDialog
4455
+ trigger={
4456
+ <Button variant="secondary" size="md">
4457
+ Close Top-Left
4458
+ </Button>
4459
+ }
4460
+ cloudflare={DGA_CLOUDFLARE}
4461
+ closePosition="top-left"
4462
+ />
4463
+ </div>
4464
+ ```
4465
+
4466
+ ```tsx
4467
+ <VideoDialog
4468
+ trigger={<Button size="lg">Watch with Captions</Button>}
4469
+ cloudflare={DGA_CLOUDFLARE}
4470
+ captionsSrc="https://customer-29f0fiy60aiz1fqm.cloudflarestream.com/e978a151c0700e12277e66802746e204/captions/en"
4471
+ />
4472
+ ```
4473
+
4474
+ ```tsx
4475
+ <div className="flex flex-col gap-16 items-center">
4476
+ <p className="text-14 text-gray-600 max-w-md text-center">
4477
+ Extreme blur creates a very diffused, ambient background that&apos;s
4478
+ less distracting while still maintaining visual connection to the video.
4479
+ </p>
4480
+ <VideoDialog
4481
+ trigger={<Button size="lg">Extreme Blur</Button>}
4482
+ cloudflare={DGA_CLOUDFLARE}
4483
+ blur="extreme"
4484
+ overlay="vignette"
4485
+ backdropOpacity={0.5}
4486
+ />
4487
+ </div>
4488
+ ```
4489
+
4490
+ ```tsx
4491
+ <div className="flex flex-col gap-16 items-center">
4492
+ <p className="text-14 text-gray-600 max-w-md text-center">
4493
+ No visible close button. Use Escape key or click outside to close.
4494
+ </p>
4495
+ <VideoDialog
4496
+ trigger={<Button size="lg">Watch Video</Button>}
4497
+ cloudflare={DGA_CLOUDFLARE}
4498
+ showClose={false}
4499
+ />
4500
+ </div>
4501
+ ```
4502
+
4503
+
4504
+ ### VideoPlayer
4505
+
4506
+ **Import:** `import { VideoPlayer } from "@nationaldesignstudio/react";`
4507
+
4508
+ **Category:** Other
4509
+
4510
+ **Props:**
4511
+
4512
+ - `src`: string
4513
+ Video source URL (HLS .m3u8 or regular video file) */
4514
+ - `cloudflare`: CloudflareConfig
4515
+ Cloudflare Stream configuration (takes precedence over src) */
4516
+ - `poster`: string
4517
+ Poster image URL */
4518
+ - `captionsSrc`: string
4519
+ VTT captions URL */
4520
+ - `autoPlay`: boolean
4521
+ Whether to autoplay (default: false) */
4522
+ - `loop`: boolean
4523
+ Whether to loop the video (default: false) */
4524
+ - `muted`: boolean
4525
+ Whether to mute initially (default: false) */
4526
+ - `controls`: boolean
4527
+ Whether to show controls (default: true) */
4528
+ - `autoHideControls`: boolean
4529
+ Whether to auto-hide controls when not interacting (default: true) */
4530
+ - `autoHideDelay`: number
4531
+ Control auto-hide delay in ms (default: 3000) */
4532
+ - `captionsEnabled`: boolean
4533
+ Whether captions are enabled by default (default: false) */
4534
+ - `onPlay`: () => void
4535
+ Callback when video starts playing */
4536
+ - `onPause`: () => void
4537
+ Callback when video pauses */
4538
+ - `onEnded`: () => void
4539
+ Callback when video ends */
4540
+ - `onTimeUpdate`: (time: number) => void
4541
+ Callback on time update */
4542
+ - `onError`: (error: Error) => void
4543
+ Callback on error */
4544
+ - `videoRef`: React.RefObject<HTMLVideoElement | null>
4545
+ Ref to the video element */
4546
+
4547
+ **Examples:**
4548
+
4549
+ ```tsx
4550
+ <div className="w-640">
4551
+ <VideoPlayer {...args} />
4552
+ </div>
4553
+ ```
4554
+
4555
+ ```tsx
4556
+ <div className="w-640">
4557
+ <VideoPlayer cloudflare={DGA_CLOUDFLARE} />
4558
+ </div>
4559
+ ```
4560
+
4561
+ ```tsx
4562
+ <div className="flex flex-col gap-16">
4563
+ <p className="text-14 text-gray-600 max-w-md">
4564
+ VideoPlayer supports Cloudflare Stream out of the box. Provide your
4565
+ video ID and customer code.
4566
+ </p>
4567
+ <div className="w-640">
4568
+ <VideoPlayer cloudflare={DGA_CLOUDFLARE} rounded="md" />
4569
+ </div>
4570
+ <pre className="text-12 bg-gray-100 p-12 rounded-4 overflow-x-auto">
4571
+ {`<VideoPlayer
4572
+ cloudflare={{
4573
+ videoId: "${DGA_CLOUDFLARE.videoId}",
4574
+ customerCode: "${DGA_CLOUDFLARE.customerCode}"
4575
+ }}
4576
+ rounded="md"
4577
+ />`}
4578
+ </pre>
4579
+ </div>
4580
+ ```
4581
+
4582
+ ```tsx
4583
+ <div className="flex flex-col items-center gap-16">
4584
+ <div className="w-640">
4585
+ <VideoPlayer
4586
+ cloudflare={DGA_CLOUDFLARE}
4587
+ onPlay={() => addEvent("Video playing")}
4588
+ onPause={() => addEvent("Video paused")}
4589
+ onEnded={() => addEvent("Video ended")}
4590
+ />
4591
+ </div>
4592
+ <div className="w-256 rounded-4 bg-gray-100 p-16">
4593
+ <p className="mb-8 font-medium text-gray-900">Events:</p>
4594
+ {events.length === 0 ? (
4595
+ <p className="text-gray-500">No events yet</p>
4596
+ ) : (
4597
+ <ul className="space-y-4 text-14 text-gray-700">
4598
+ {events.map((event) => (
4599
+ <li key={event}>{event}</li>
4600
+ ))}
4601
+ </ul>
4602
+ )}
4603
+ </div>
4604
+ </div>
4605
+ ```
4606
+
4607
+ ```tsx
4608
+ <div className="flex flex-col gap-16">
4609
+ <p className="text-14 text-gray-600 max-w-md">
4610
+ Click the button below to open a video player in a dialog. The video
4611
+ will autoplay when the dialog opens.
4612
+ </p>
4613
+ <Dialog
4614
+ trigger={<Button>Watch Video</Button>}
4615
+ size="full"
4616
+ variant="minimal"
4617
+ showClose={false}
4618
+ >
4619
+ <VideoPlayer cloudflare={DGA_CLOUDFLARE} autoPlay rounded="none" />
4620
+ </Dialog>
4621
+ <pre className="text-12 bg-gray-100 p-12 rounded-4 overflow-x-auto">
4622
+ {`import { Dialog } from "@nds/react";
4623
+ import { VideoPlayer } from "@nds/react";
4624
+ <Dialog
4625
+ trigger={<Button>Watch Video</Button>}
4626
+ size="full"
4627
+ variant="minimal"
4628
+ showClose={false}
4629
+ >
4630
+ <VideoPlayer
4631
+ cloudflare={{ videoId: "...", customerCode: "..." }}
4632
+ autoPlay
4633
+ rounded="none"
4634
+ />
4635
+ </Dialog>`}
4636
+ </pre>
4637
+ </div>
4638
+ ```
4639
+
4640
+ ```tsx
4641
+ <div className="flex flex-col gap-16">
4642
+ <p className="text-14 text-gray-600 max-w-md">
4643
+ VideoPlayer supports VTT captions. Click the CC button in the control
4644
+ bar to toggle captions. Captions are displayed as an overlay on the
4645
+ video.
4646
+ </p>
4647
+ <div className="w-640">
4648
+ <VideoPlayer
4649
+ cloudflare={DGA_CLOUDFLARE}
4650
+ captionsSrc={DGA_CAPTIONS_URL}
4651
+ captionsEnabled={true}
4652
+ rounded="md"
4653
+ />
4654
+ </div>
4655
+ <pre className="text-12 bg-gray-100 p-12 rounded-4 overflow-x-auto">
4656
+ {`<VideoPlayer
4657
+ cloudflare={{
4658
+ videoId: "${DGA_CLOUDFLARE.videoId}",
4659
+ customerCode: "${DGA_CLOUDFLARE.customerCode}"
4660
+ }}
4661
+ captionsSrc="https://customer-{customerCode}.cloudflarestream.com/{videoId}/captions/en"
4662
+ captionsEnabled={true}
4663
+ rounded="md"
4664
+ />`}
4665
+ </pre>
4666
+ </div>
4667
+ ```
4668
+
4669
+ ```tsx
4670
+ <div className="flex flex-col gap-16">
4671
+ <p className="text-14 text-gray-600 max-w-md">
4672
+ Captions are available but disabled by default. Users can enable them by
4673
+ clicking the CC button in the control bar.
4674
+ </p>
4675
+ <div className="w-640">
4676
+ <VideoPlayer
4677
+ cloudflare={DGA_CLOUDFLARE}
4678
+ captionsSrc={DGA_CAPTIONS_URL}
4679
+ captionsEnabled={false}
4680
+ rounded="md"
4681
+ />
4682
+ </div>
4683
+ </div>
4684
+ ```
4685
+
4686
+ ```tsx
4687
+ <div className="flex flex-col gap-16">
4688
+ <p className="text-14 text-gray-600 max-w-md">
4689
+ VideoPlayer uses media-chrome for accessible, customizable controls. All
4690
+ controls are keyboard accessible.
4691
+ </p>
4692
+ <div className="w-640">
4693
+ <VideoPlayer cloudflare={DGA_CLOUDFLARE} autoHideControls={false} />
4694
+ </div>
4695
+ <div className="text-14 text-gray-600 max-w-md space-y-8">
4696
+ <p>
4697
+ <strong>Controls:</strong>
4698
+ </p>
4699
+ <ul className="list-disc pl-20 space-y-4">
4700
+ <li>Play/Pause button</li>
4701
+ <li>Time range scrubber with preview</li>
4702
+ <li>Mute button</li>
4703
+ <li>Volume slider</li>
4704
+ <li>Current time / duration display</li>
4705
+ <li>Captions toggle (when captionsSrc provided)</li>
4706
+ <li>Fullscreen toggle</li>
4707
+ </ul>
4708
+ </div>
4709
+ </div>
4710
+ ```
4711
+
4712
+
4713
+ ### VideoWithBackdrop
4714
+
4715
+ **Import:** `import { VideoWithBackdrop } from "@nationaldesignstudio/react";`
4716
+
4717
+ **Category:** Other
4718
+
4719
+ **Props:**
4720
+
4721
+ - `blur`: BlurredVideoBackdropProps["blur"]
4722
+ Blur intensity (default: high) */
4723
+ - `overlay`: BlurredVideoBackdropProps["overlay"]
4724
+ Gradient overlay (default: vignette) */
4725
+ - `backdropOpacity`: number
4726
+ Backdrop opacity (default: 0.6) */
4727
+ - `maxWidth`: string
4728
+ Max width of video player (default: 960px) */
4729
+ - `padding`: "none" | "sm" | "md" | "lg"
4730
+ Content padding (default: md) */
4731
+ Values: `none`, `sm`, `md`, `lg`
4732
+ - `rounded`: VideoPlayerProps["rounded"]
4733
+ Video player rounded corners */
4734
+ - `className`: string
4735
+ Additional className for root container */
4736
+ - `targetFps`: number
4737
+ Target FPS for backdrop canvas (default: 30) */
4738
+ - `scale`: number
4739
+ Canvas scale factor for backdrop (default: 0.5) */
4740
+
4741
+ **Examples:**
4742
+
4743
+ ```tsx
4744
+ <div className="h-[600px]">
4745
+ <VideoWithBackdrop {...args} />
4746
+ </div>
4747
+ ```
4748
+
4749
+ ```tsx
4750
+ <div className="h-[500px]">
4751
+ <VideoWithBackdrop
4752
+ cloudflare={DGA_CLOUDFLARE}
4753
+ blur="high"
4754
+ overlay="vignette"
4755
+ />
4756
+ </div>
4757
+ ```
4758
+
4759
+ ```tsx
4760
+ <div className="flex flex-col gap-16 p-24">
4761
+ <p className="text-14 text-gray-600 max-w-md">
4762
+ For fullscreen video modals, use the dedicated VideoDialog component.
4763
+ The blur video becomes the modal backdrop itself.
4764
+ </p>
4765
+ <VideoDialog
4766
+ trigger={<Button size="lg">Watch Video</Button>}
4767
+ cloudflare={DGA_CLOUDFLARE}
4768
+ blur="high"
4769
+ overlay="vignette"
4770
+ />
4771
+ <pre className="text-12 bg-gray-100 p-12 rounded-4 overflow-x-auto">
4772
+ {`<VideoDialog
4773
+ trigger={<Button>Watch Video</Button>}
4774
+ cloudflare={{ videoId: "...", customerCode: "..." }}
4775
+ blur="high"
4776
+ overlay="vignette"
4777
+ />`}
4778
+ </pre>
4779
+ </div>
4780
+ ```
4781
+
4782
+ ```tsx
4783
+ <div className="h-[600px]">
4784
+ <VideoWithBackdropParts.Root cloudflare={DGA_CLOUDFLARE} fullHeight>
4785
+ <VideoWithBackdropParts.Backdrop
4786
+ blur="extreme"
4787
+ overlay="top-bottom"
4788
+ opacity={0.5}
4789
+ />
4790
+ <VideoWithBackdropParts.Content fullHeight padding="lg">
4791
+ <VideoWithBackdropParts.Video maxWidth="800px" rounded="md" />
4792
+ </VideoWithBackdropParts.Content>
4793
+ </VideoWithBackdropParts.Root>
4794
+ </div>
4795
+ ```
4796
+
4797
+ ```tsx
4798
+ <div className="h-[700px]">
4799
+ <VideoWithBackdropParts.Root cloudflare={DGA_CLOUDFLARE} fullHeight>
4800
+ <VideoWithBackdropParts.Backdrop blur="high" overlay="vignette" />
4801
+ <VideoWithBackdropParts.Content fullHeight padding="lg">
4802
+ <div className="flex flex-col items-center gap-24">
4803
+ <div className="text-center">
4804
+ <h2 className="typography-h2-lg text-white mb-8">
4805
+ Featured Video
4806
+ </h2>
4807
+ <p className="typography-body-md-md text-white/70">
4808
+ Watch our latest announcement
4809
+ </p>
4810
+ </div>
4811
+ <VideoWithBackdropParts.Video maxWidth="960px" rounded="lg" />
4812
+ <div className="flex gap-12">
4813
+ <Button variant="secondary" size="sm">
4814
+ Share
4815
+ </Button>
4816
+ <Button variant="secondary" size="sm">
4817
+ Save for Later
4818
+ </Button>
4819
+ </div>
4820
+ </div>
4821
+ </VideoWithBackdropParts.Content>
4822
+ </VideoWithBackdropParts.Root>
4823
+ </div>
4824
+ ```
4825
+
4826
+ ```tsx
4827
+ <div className="grid grid-cols-2 gap-4 h-screen">
4828
+ {(["low", "medium", "high", "extreme"] as const).map((blur) => (
4829
+ <div key={blur} className="relative">
4830
+ <VideoWithBackdrop
4831
+ cloudflare={DGA_CLOUDFLARE}
4832
+ blur={blur}
4833
+ overlay="none"
4834
+ maxWidth="100%"
4835
+ padding="sm"
4836
+ rounded="md"
4837
+ />
4838
+ <div className="absolute top-16 left-16 z-20 bg-black/60 px-12 py-6 rounded-4">
4839
+ <span className="text-white text-14">blur=&quot;{blur}&quot;</span>
4840
+ </div>
4841
+ </div>
4842
+ ))}
4843
+ </div>
4844
+ ```
4845
+
4846
+ ```tsx
4847
+ <div className="grid grid-cols-3 gap-4 h-[400px]">
4848
+ {(["none", "vignette", "top-bottom"] as const).map((overlay) => (
4849
+ <div key={overlay} className="relative h-full">
4850
+ <VideoWithBackdrop
4851
+ cloudflare={DGA_CLOUDFLARE}
4852
+ blur="high"
4853
+ overlay={overlay}
4854
+ maxWidth="100%"
4855
+ padding="sm"
4856
+ rounded="md"
4857
+ />
4858
+ <div className="absolute top-16 left-16 z-20 bg-black/60 px-12 py-6 rounded-4">
4859
+ <span className="text-white text-14">
4860
+ overlay=&quot;{overlay}&quot;
4861
+ </span>
4862
+ </div>
4863
+ </div>
4864
+ ))}
4865
+ </div>
4866
+ ```
4867
+
4868
+ ```tsx
4869
+ <div className="h-[700px]">
4870
+ <VideoWithBackdropParts.Root cloudflare={DGA_CLOUDFLARE} fullHeight>
4871
+ <VideoWithBackdropParts.Backdrop
4872
+ blur="extreme"
4873
+ overlay="top-bottom"
4874
+ opacity={0.4}
4875
+ syncEnabled={false}
4876
+ />
4877
+ <VideoWithBackdropParts.Content fullHeight padding="lg">
4878
+ <div className="flex flex-col items-center justify-center text-center gap-32 max-w-4xl">
4879
+ <div>
4880
+ <h1 className="typography-h1-lg text-white mb-16">
4881
+ Transform Your Digital Experience
4882
+ </h1>
4883
+ <p className="typography-body-lg-md text-white/80 mb-24">
4884
+ Discover how our platform can help you build stunning,
4885
+ accessible web applications with modern design patterns.
4886
+ </p>
4887
+ <div className="flex gap-16 justify-center">
4888
+ <Button size="lg">Get Started</Button>
4889
+ <Button variant="secondary" size="lg">
4890
+ Learn More
4891
+ </Button>
4892
+ </div>
4893
+ </div>
4894
+ <VideoWithBackdropParts.Video
4895
+ maxWidth="800px"
4896
+ rounded="lg"
4897
+ controls
4898
+ />
4899
+ </div>
4900
+ </VideoWithBackdropParts.Content>
4901
+ </VideoWithBackdropParts.Root>
4902
+ </div>
4903
+ ```
4904
+
4905
+
3660
4906
  ## Typography
3661
4907
 
3662
4908
  ### Prose
@@ -3763,5 +5009,5 @@ className="font-semibold" // Font weight 600
3763
5009
  - **Action:** Button
3764
5010
  - **Display:** Card, Hero, UsGovBanner
3765
5011
  - **Navigation:** Navbar
3766
- - **Other:** Accordion, Background, Banner, CardGrid, DevToolbar, FaqSection, GridOverlay, Input, NdstudioFooter, PagerControl, Popover, QuoteBlock, River, Select, Tooltip, Tout, TwoColumnSection
5012
+ - **Other:** Accordion, Background, Banner, BlurredVideoBackdrop, CardGrid, DevToolbar, Dialog, FaqSection, GridOverlay, Input, NdstudioFooter, PagerControl, Popover, QuoteBlock, River, Select, Tooltip, Tout, TwoColumnSection, VideoDialog, VideoPlayer, VideoWithBackdrop
3767
5013
  - **Typography:** Prose