├── .changeset ├── README.md ├── config.json ├── cool-boxes-tap.md └── lucky-trains-attend.md ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── apps └── docs │ ├── .astro │ ├── settings.json │ └── types.d.ts │ ├── .gitignore │ ├── .vscode │ ├── extensions.json │ └── launch.json │ ├── README.md │ ├── astro.config.mjs │ ├── package.json │ ├── public │ ├── default-og-image.png │ ├── dinosaurchestra.jpeg │ ├── favicon.svg │ ├── fine.vtt │ ├── make-scrollable-code-focusable.js │ ├── sprite-fright.jpg │ └── sprite-fright.vtt │ ├── src │ ├── components │ │ ├── Footer │ │ │ ├── AvatarList.astro │ │ │ └── Footer.astro │ │ ├── HeadCommon.astro │ │ ├── HeadSEO.astro │ │ ├── Header │ │ │ ├── AstroLogo.astro │ │ │ ├── Header.astro │ │ │ ├── LanguageSelect.css │ │ │ ├── LanguageSelect.tsx │ │ │ ├── Search.css │ │ │ ├── Search.tsx │ │ │ ├── SidebarToggle.tsx │ │ │ └── SkipToContent.astro │ │ ├── LeftSidebar │ │ │ └── LeftSidebar.astro │ │ ├── PageContent │ │ │ └── PageContent.astro │ │ └── RightSidebar │ │ │ ├── MoreMenu.astro │ │ │ ├── RightSidebar.astro │ │ │ ├── TableOfContents.tsx │ │ │ ├── ThemeToggleButton.css │ │ │ └── ThemeToggleButton.tsx │ ├── config.ts │ ├── demos │ │ ├── DemoOne.tsx │ │ ├── DemoThree.tsx │ │ ├── DemoTwo.tsx │ │ └── DemoTwoCodeSnippet.tsx │ ├── env.d.ts │ ├── languages.ts │ ├── layouts │ │ └── MainLayout.astro │ ├── pages │ │ ├── en │ │ │ ├── controls-intro.md │ │ │ ├── controls.md │ │ │ ├── core-components.md │ │ │ ├── core-hooks.md │ │ │ ├── core-other-sources.md │ │ │ ├── introduction.md │ │ │ ├── sliders.md │ │ │ ├── text-track-components.md │ │ │ ├── text-track-hooks.md │ │ │ ├── text-track-introduction.md │ │ │ ├── vtt-controls.md │ │ │ └── webvtt.md │ │ └── index.astro │ └── styles │ │ ├── index.css │ │ └── theme.css │ ├── tailwind.config.cjs │ └── tsconfig.json ├── index.js ├── package.json ├── packages ├── controls │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Fullscreen.tsx │ │ ├── Loop.tsx │ │ ├── Mute.tsx │ │ ├── PictureInPicture.tsx │ │ ├── PlayPause.tsx │ │ ├── Timestamp.tsx │ │ └── index.ts │ └── tsconfig.json ├── core │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components.tsx │ │ ├── index.tsx │ │ └── state.tsx │ └── tsconfig.json ├── editor │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Editor.tsx │ │ ├── TimelineContainer.tsx │ │ ├── TimelineControlBar.tsx │ │ ├── TimelineEditor.tsx │ │ ├── TimelineElements.tsx │ │ ├── TimelineEntryLabel.tsx │ │ ├── TimelineHeader.tsx │ │ ├── TimelineOverflowContainer.tsx │ │ ├── TimelineSubtitleCueEditor.tsx │ │ ├── TimelineSubtitlesTrack.tsx │ │ ├── TimelineTrack.tsx │ │ ├── VideoRatio.tsx │ │ ├── index.tsx │ │ └── to-vtt.ts │ └── tsconfig.json ├── hls │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.tsx │ └── tsconfig.json ├── sliders │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── ProgressBarBufferedRanges.tsx │ │ ├── ProgressBarRoot.tsx │ │ ├── ProgressBarTooltip.tsx │ │ ├── VolumeRoot.tsx │ │ └── index.ts │ └── tsconfig.json ├── vtt-controls │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── StoryboardThumbnail.tsx │ │ └── index.ts │ └── tsconfig.json ├── vtt-core │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── GlobalState.ts │ │ ├── NamedCharacterReference.ts │ │ ├── TextTrack.ts │ │ ├── Track.ts │ │ ├── VTTCue.ts │ │ ├── VTTCueTextParser.ts │ │ ├── VTTParser.ts │ │ ├── VTTRegion.ts │ │ ├── VTTRenderer.ts │ │ ├── index.ts │ │ └── renderer │ │ │ ├── Cues.ts │ │ │ └── TextTrackContainer.ts │ └── tsconfig.json └── vtt │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── InterfaceOverlay.tsx │ ├── TextTrack.tsx │ ├── Track.tsx │ └── index.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [ 11 | "@react-av/docs" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.changeset/cool-boxes-tap.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@react-av/controls": patch 3 | "@react-av/core": patch 4 | "@react-av/hls": patch 5 | "@react-av/sliders": patch 6 | "@react-av/vtt": patch 7 | "@react-av/vtt-controls": patch 8 | "@react-av/vtt-core": patch 9 | --- 10 | 11 | Removed rollup bundling. Library users should use their own bundler. 12 | -------------------------------------------------------------------------------- /.changeset/lucky-trains-attend.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@react-av/editor": patch 3 | --- 4 | 5 | Initial release for React AV editor components 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .turbo 3 | dist/ 4 | .next/ 5 | build/ 6 | .DS_Store/ 7 | .turbo/ 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .turbo 3 | dist/ 4 | .next/ 5 | build/ 6 | .DS_Store/ 7 | .turbo/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Daniel Wykerd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React AV 2 | 3 | **Build modern media players with React.** 4 | 5 | Fully-featured, headless, hooks-based, and declarative media player framework for React. 6 | 7 | ## Documentation 8 | 9 | [Check our documentation](https://react-av.wykerd.dev/) 10 | -------------------------------------------------------------------------------- /apps/docs/.astro/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "_variables": { 3 | "lastUpdateCheck": 1725385655000 4 | } 5 | } -------------------------------------------------------------------------------- /apps/docs/.astro/types.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # dependencies 5 | node_modules/ 6 | 7 | # logs 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | pnpm-debug.log* 12 | 13 | 14 | # environment variables 15 | .env 16 | .env.production 17 | 18 | # macOS-specific files 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /apps/docs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /apps/docs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /apps/docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import preact from '@astrojs/preact'; 3 | import react from '@astrojs/react'; 4 | 5 | // https://astro.build/config 6 | import tailwind from "@astrojs/tailwind"; 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | integrations: [ 11 | // Enable Preact to support Preact JSX components. 12 | preact({ 13 | include: ['src/components/**/*.tsx'], 14 | }), 15 | // Enable React for the Algolia search component. 16 | react({ 17 | include: ['src/demos/**/*.tsx'], 18 | }), tailwind()], 19 | site: `http://astro.build` 20 | }); -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-av/docs", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "check": "astro check && tsc", 10 | "build": "astro build", 11 | "preview": "astro preview", 12 | "astro": "astro" 13 | }, 14 | "dependencies": { 15 | "@algolia/client-search": "^4.13.1", 16 | "@astrojs/preact": "^3.5.2", 17 | "@astrojs/react": "^3.6.2", 18 | "@astrojs/tailwind": "^5.1.0", 19 | "@docsearch/css": "^3.1.0", 20 | "@docsearch/react": "^3.1.0", 21 | "@radix-ui/react-icons": "^1.3.0", 22 | "@radix-ui/react-slider": "^1.0.0", 23 | "@react-av/controls": "workspace:^", 24 | "@react-av/core": "workspace:^", 25 | "@react-av/editor": "workspace:^", 26 | "@react-av/hls": "workspace:^", 27 | "@react-av/sliders": "workspace:^", 28 | "@react-av/vtt": "workspace:^", 29 | "@react-av/vtt-controls": "workspace:^", 30 | "@react-av/vtt-core": "workspace:^", 31 | "@types/node": "^18.0.0", 32 | "@types/react": "^18.3.5", 33 | "@types/react-dom": "^18.3.0", 34 | "astro": "^4.15.2", 35 | "@phosphor-icons/react": "^2.1.7", 36 | "postcss": "^8.4.14", 37 | "preact": "^10.7.3", 38 | "react": "^18.3.1", 39 | "react-dom": "^18.3.1", 40 | "tailwindcss": "^3.0.24", 41 | "windy-radix-palette": "2.0.0-beta.7" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/docs/public/default-og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wykerd/react-av/586ebb40e229aa9c2576a6fcad137ca89356216a/apps/docs/public/default-og-image.png -------------------------------------------------------------------------------- /apps/docs/public/dinosaurchestra.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wykerd/react-av/586ebb40e229aa9c2576a6fcad137ca89356216a/apps/docs/public/dinosaurchestra.jpeg -------------------------------------------------------------------------------- /apps/docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /apps/docs/public/fine.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:54.640 --> 00:00:57.870 4 | (One, two, three, four) 5 | 6 | 00:01:11.110 --> 00:01:14.750 7 | Today has a way of scarring your eyes 8 | 9 | 00:01:14.750 --> 00:01:18.280 10 | With negative light, but it's a disguise 11 | 12 | 00:01:18.280 --> 00:01:22.930 13 | I put on my shades and see through the lies 14 | 15 | 00:01:22.930 --> 00:01:24.760 16 | The convenient truth is 17 | 18 | 00:01:24.760 --> 00:01:28.150 19 | Light is on the way, we'll be having a fun time 20 | 21 | 00:01:28.150 --> 00:01:31.690 22 | It's such a lovely day, we should pocket the sunshine 23 | 24 | 00:01:31.690 --> 00:01:35.120 25 | And never give it back, even if there's a heat wave 26 | 27 | 00:01:35.120 --> 00:01:38.760 28 | Or terrorist attack, it will just be a close shave 29 | 30 | 00:01:38.760 --> 00:01:42.270 31 | I know (I know) 32 | 33 | 00:01:42.270 --> 00:01:45.650 34 | That every bomb has a silver lining 35 | 36 | 00:01:45.650 --> 00:01:49.460 37 | I know (I know) 38 | 39 | 00:01:49.460 --> 00:01:52.200 40 | It won't be long until 41 | 42 | 00:01:52.200 --> 00:01:55.270 43 | Everything works out nice in the end 44 | 45 | 00:01:55.270 --> 00:01:57.330 46 | The sun will marry the moon 47 | 48 | 00:01:57.330 --> 00:01:59.000 49 | It'll be fine 50 | 51 | 00:01:59.000 --> 00:02:02.370 52 | Why don't we sit back, mellow again 53 | 54 | 00:02:02.370 --> 00:02:04.550 55 | And have a nice afternoon? 56 | 57 | 00:02:04.550 --> 00:02:07.640 58 | It'll be fine 59 | 60 | 00:02:11.620 --> 00:02:15.200 61 | I go for a walk, the sidewalk is cracked 62 | 63 | 00:02:15.200 --> 00:02:18.850 64 | I'm not superstitious, but I made a pact 65 | 66 | 00:02:18.850 --> 00:02:23.210 67 | With old Mother Earth, she'd get off my back 68 | 69 | 00:02:23.210 --> 00:02:25.170 70 | If I get off hers 71 | 72 | 00:02:25.170 --> 00:02:28.610 73 | Light is on the way, we'll be having a fun time 74 | 75 | 00:02:28.610 --> 00:02:32.170 76 | It's such a lovely day, we should pocket the sunshine 77 | 78 | 00:02:32.170 --> 00:02:35.680 79 | And never give it back, even if there's a heat wave 80 | 81 | 00:02:35.680 --> 00:02:39.290 82 | We're stalling on the track, it will just be a close shave 83 | 84 | 00:02:39.290 --> 00:02:42.870 85 | I know (I know) 86 | 87 | 00:02:42.870 --> 00:02:45.900 88 | That in a snap, all the birds will sing 89 | 90 | 00:02:45.900 --> 00:02:49.830 91 | I know (I know) 92 | 93 | 00:02:49.830 --> 00:02:52.510 94 | I'm full of crap, but still 95 | 96 | 00:02:52.510 --> 00:02:55.610 97 | Everything works out nice in the end 98 | 99 | 00:02:55.610 --> 00:02:57.980 100 | The sun will marry the moon 101 | 102 | 00:02:57.980 --> 00:02:59.600 103 | It'll be fine 104 | 105 | 00:02:59.600 --> 00:03:02.850 106 | Why don't we sit back, mellow again 107 | 108 | 00:03:02.850 --> 00:03:05.050 109 | And have a nice afternoon? 110 | 111 | 00:03:05.050 --> 00:03:07.950 112 | It'll be fine 113 | 114 | 00:03:07.950 --> 00:03:13.250 115 | Fine, fine, fine 116 | 117 | 00:03:13.250 --> 00:03:14.980 118 | Everything is gonna be 119 | 120 | 00:03:14.980 --> 00:03:20.220 121 | Fine, fine, fine 122 | 123 | 00:03:20.220 --> 00:03:22.020 124 | Everything is gonna be 125 | 126 | 00:03:22.020 --> 00:03:27.330 127 | Fine, fine, fine 128 | 129 | 00:03:27.330 --> 00:03:29.280 130 | Everything is gonna be 131 | 132 | 00:03:29.280 --> 00:03:34.530 133 | Fine, fine, fine 134 | 135 | 00:03:34.530 --> 00:03:37.940 136 | Everything is gonna be 137 | 138 | 00:03:49.300 --> 00:03:52.620 139 | Everything works out nice in the end 140 | 141 | 00:03:52.620 --> 00:03:54.830 142 | The sun will marry the moon 143 | 144 | 00:03:54.830 --> 00:03:56.580 145 | It'll be fine 146 | 147 | 00:03:56.580 --> 00:03:59.670 148 | Why don't we sit back, mellow again 149 | 150 | 00:03:59.670 --> 00:04:01.950 151 | And have a nice afternoon? 152 | 153 | 00:04:01.950 --> 00:04:03.590 154 | It'll be fine 155 | 156 | 00:04:03.590 --> 00:04:06.830 157 | Everything works out nice in the end (I know) 158 | 159 | 00:04:06.830 --> 00:04:08.470 160 | The sun will marry the moon (that every bomb) 161 | 162 | 00:04:08.470 --> 00:04:10.760 163 | Works out nice in the end (has a silver lining, I know) 164 | 165 | 00:04:10.760 --> 00:04:13.950 166 | Why don't we sit back mellow again (I know) 167 | 168 | 00:04:13.950 --> 00:04:15.330 169 | And have a nice afternoon? (It won't be long until) 170 | 171 | 00:04:15.330 --> 00:04:17.850 172 | Sit back mellow again 173 | 174 | 00:04:17.850 --> 00:04:21.050 175 | Everything works out nice in the end 176 | 177 | 00:04:21.050 --> 00:04:23.100 178 | The sun will marry the moon 179 | 180 | 00:04:23.100 --> 00:04:24.960 181 | It'll be fine 182 | 183 | 00:04:24.960 --> 00:04:28.070 184 | Why don't we sit back mellow again 185 | 186 | 00:04:28.070 --> 00:04:30.310 187 | And have a nice afternoon? 188 | 189 | 00:04:30.310 --> 00:04:35.170 190 | It'll be fine, fine, fine, fine 191 | -------------------------------------------------------------------------------- /apps/docs/public/make-scrollable-code-focusable.js: -------------------------------------------------------------------------------- 1 | Array.from(document.getElementsByTagName('pre')).forEach((element) => { 2 | element.setAttribute('tabindex', '0'); 3 | }); 4 | -------------------------------------------------------------------------------- /apps/docs/public/sprite-fright.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wykerd/react-av/586ebb40e229aa9c2576a6fcad137ca89356216a/apps/docs/public/sprite-fright.jpg -------------------------------------------------------------------------------- /apps/docs/public/sprite-fright.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | Kind: captions 3 | Language: en 4 | 5 | 00:00:17.375 --> 00:00:19.458 6 | Hello, Mr.Snail! 7 | 8 | 00:00:19.458 --> 00:00:22.333 9 | Aw, you cute little cornu aspersum. 10 | 11 | 00:00:22.333 --> 00:00:25.125 12 | Latin name bonus-points! 13 | 14 | 00:00:28.375 --> 00:00:30.833 15 | Can’t believe we got stuck with the tree hugger. 16 | 17 | 00:00:30.833 --> 00:00:34.667 18 | If I have to hear the latin name for one more animal, I swear I'm gonna kill her. 19 | 20 | 00:00:34.667 --> 00:00:39.542 21 | Relax, Sugar Buns. While she does all the work we’ll have all the fun. 22 | 23 | 00:00:39.542 --> 00:00:42.583 24 | Yeah! And get wasted out of our minds! 25 | 26 | 00:00:42.583 --> 00:00:44.250 27 | Way ahead of you, man. 28 | 29 | 00:00:44.250 --> 00:00:48.625 30 | C’mon, team! We mustn’t dilly-dally when there’s so much nature to see! 31 | 32 | 00:00:48.625 --> 00:00:53.667 33 | I was thinking we should call our class project, “Fungis in a Forest!” 34 | 35 | 00:00:53.667 --> 00:00:56.167 36 | Get it? Because we’re a group of fun guys. 37 | 38 | 00:00:56.167 --> 00:00:57.667 39 | And also fungis which- 40 | 41 | 00:00:57.667 --> 00:01:02.458 42 | Shush! That’s a yellow wagtail. And she’s in distress! 43 | 44 | 00:01:02.750 --> 00:01:06.333 45 | There, there, my little motacilla flava. 46 | 47 | 00:01:07.792 --> 00:01:10.042 48 | Oh! Look at her go! 49 | 50 | 00:01:10.375 --> 00:01:11.500 51 | Wow! 52 | 53 | 00:01:11.500 --> 00:01:14.417 54 | Out of the way, dorkwad! We're camping here. 55 | 56 | 00:01:14.417 --> 00:01:17.500 57 | Finally! Time to get that BBQ burning. 58 | 59 | 00:01:18.417 --> 00:01:20.167 60 | Oh. Oh, dear. 61 | 62 | 00:01:27.333 --> 00:01:28.875 63 | Gross! 64 | 65 | 00:01:30.833 --> 00:01:35.458 66 | Oh. Easy, guys. We’re supposed to be studying nature, not wrecking it. 67 | 68 | 00:01:35.458 --> 00:01:37.958 69 | Piss break! 70 | 71 | 00:01:40.708 --> 00:01:44.667 72 | Oy, fungus freak! Make yourself useful and fix the tent. 73 | 74 | 00:01:44.667 --> 00:01:48.917 75 | Oh, a bit of friendly campsite ribbing. Fun. 76 | 77 | 00:01:49.875 --> 00:01:52.583 78 | She wouldn't survive 5 minutes alone in these woods. 79 | 80 | 00:01:52.583 --> 00:01:56.792 81 | Yeah. She's a real weenie that one, innit? 82 | 83 | 00:01:56.792 --> 00:01:59.000 84 | Oy, Phil! How those sausages coming? 85 | 86 | 00:01:59.000 --> 00:02:02.458 87 | Almost ready! Just need a bit more salt... 88 | 89 | 00:02:36.333 --> 00:02:39.208 90 | It's incredible. It's a new species. 91 | 92 | 00:02:39.208 --> 00:02:41.875 93 | What the bloody hell are those things? 94 | 95 | 00:02:41.875 --> 00:02:44.542 96 | It's like a gnome shagged a mushroom. 97 | 98 | 00:02:57.375 --> 00:02:59.583 99 | They’re absolutely adorable. 100 | 101 | 00:02:59.583 --> 00:03:02.000 102 | Yeah, absolutely bloody adorable. 103 | 104 | 00:03:02.000 --> 00:03:05.625 105 | And I bet we’ll make a fortune if we catch one of those things. 106 | 107 | 00:03:05.625 --> 00:03:06.958 108 | We'd be famous! 109 | 110 | 00:03:06.958 --> 00:03:08.292 111 | Get on MTV. 112 | 113 | 00:03:08.292 --> 00:03:09.875 114 | I wonder what they taste like. 115 | 116 | 00:03:09.875 --> 00:03:12.500 117 | We really shouldn't disrupt their habitat. 118 | 119 | 00:03:12.500 --> 00:03:15.917 120 | Right. Sure, sure. 121 | 122 | 00:03:15.917 --> 00:03:17.500 123 | Greetings. 124 | 125 | 00:03:17.500 --> 00:03:19.000 126 | Welcome, friends. 127 | 128 | 00:03:19.000 --> 00:03:21.208 129 | I'll be right down. 130 | 131 | 00:03:26.250 --> 00:03:28.750 132 | Could I interest you in some peppermint tea? 133 | 134 | 00:03:28.750 --> 00:03:30.667 135 | What are you guys? 136 | 137 | 00:03:30.667 --> 00:03:34.417 138 | We are Sprites. We sprite balance to the forest. 139 | 140 | 00:03:34.417 --> 00:03:39.667 141 | We take care of the animals, plants, birds, moss... 142 | 143 | 00:03:41.000 --> 00:03:43.917 144 | Guys? Guys, I... 145 | 146 | 00:03:46.583 --> 00:03:51.542 147 | Hey, uh, little mushroom geezer, you cool with us camping here for the night? 148 | 149 | 00:03:51.542 --> 00:03:56.333 150 | Absolutely. A friend of the forest is a friend of the sprites. 151 | 152 | 00:03:57.708 --> 00:04:01.375 153 | Maybe it was just ketchup... or something... like... 154 | 155 | 00:04:01.375 --> 00:04:09.125 156 | Red paint? Yeah, maybe they were just painting their little hats or... something... 157 | 158 | 00:04:15.500 --> 00:04:17.042 159 | Guys? 160 | 161 | 00:04:20.083 --> 00:04:21.625 162 | Guys? 163 | 164 | 00:04:22.417 --> 00:04:23.958 165 | Guys, this isn't funny. 166 | 167 | 00:04:25.083 --> 00:04:26.625 168 | Guys? 169 | 170 | 00:04:47.208 --> 00:04:50.167 171 | We were just gonna catch one! But they’re psycho! 172 | 173 | 00:04:50.167 --> 00:04:52.958 174 | You gotta get me outta these! Hurry! They’re gonna kill us! 175 | 176 | 00:04:55.667 --> 00:04:56.583 177 | Hurry! 178 | 179 | 00:04:56.583 --> 00:04:59.458 180 | Right foot, left foot, leaf and stone. 181 | 182 | 00:04:59.458 --> 00:05:03.583 183 | You picked the wrong camping spot, bitches. 184 | 185 | 00:05:03.583 --> 00:05:05.292 186 | Sprite them! 187 | 188 | 00:05:05.833 --> 00:05:07.542 189 | Run!!! 190 | 191 | 00:05:18.370 --> 00:05:20.091 192 | Take that, you little freaks! 193 | 194 | 00:05:21.567 --> 00:05:22.286 195 | Sprite this! 196 | 197 | 00:05:30.250 --> 00:05:32.083 198 | Aaaah! My eyes! 199 | 200 | 00:05:57.625 --> 00:05:59.625 201 | Sugar Buns?! Help me! 202 | 203 | 00:06:07.125 --> 00:06:08.875 204 | Sweet cakes! 205 | 206 | 00:06:11.292 --> 00:06:13.208 207 | Love muffin... 208 | 209 | 00:06:14.167 --> 00:06:15.667 210 | Huh? 211 | 212 | 00:06:40.042 --> 00:06:43.458 213 | No, no, no! I can’t swim! 214 | 215 | 00:06:55.083 --> 00:06:56.875 216 | Yes! 217 | 218 | 00:06:58.292 --> 00:06:59.708 219 | No! 220 | 221 | 00:07:06.359 --> 00:07:08.141 222 | Son of a Spriteberry! 223 | 224 | 00:07:08.141 --> 00:07:09.608 225 | It burns! It burns! 226 | 227 | 00:07:12.833 --> 00:07:16.208 228 | I don’t want to hurt you! Just keep your distance! 229 | 230 | 00:07:17.208 --> 00:07:21.125 231 | All right, then. Eat salt, you little green bastards! 232 | 233 | 00:07:56.833 --> 00:07:58.542 234 | Nuh-uh-uh. 235 | 236 | 00:07:58.542 --> 00:08:03.333 237 | Those punks treated the world as their own personal rubbish bin. 238 | 239 | 00:08:03.333 --> 00:08:05.625 240 | And you set them free! 241 | 242 | 00:08:05.625 --> 00:08:07.833 243 | Now you'll join them! 244 | 245 | 00:08:45.875 --> 00:08:49.167 246 | I’ll give you that one, Brother Bird! 247 | 248 | 00:08:50.000 --> 00:08:55.417 249 | Alright sprites, let’s burn the bodies and have some peppermint tea. 250 | -------------------------------------------------------------------------------- /apps/docs/src/components/Footer/AvatarList.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // fetch all commits for just this page's path 3 | type Props = { 4 | path: string; 5 | }; 6 | const { path } = Astro.props as Props; 7 | const resolvedPath = `apps/docs/${path}`; 8 | const url = `https://api.github.com/repos/Wykerd/react-av/commits?path=${resolvedPath}`; 9 | const commitsURL = `https://github.com/Wykerd/react-av/commits/main/${resolvedPath}`; 10 | 11 | type Commit = { 12 | author: { 13 | id: string; 14 | login: string; 15 | }; 16 | }; 17 | 18 | async function getCommits(url: string) { 19 | try { 20 | const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello'; 21 | if (!token) { 22 | throw new Error( 23 | 'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.' 24 | ); 25 | } 26 | 27 | const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`; 28 | 29 | const res = await fetch(url, { 30 | method: 'GET', 31 | headers: { 32 | Authorization: auth, 33 | 'User-Agent': 'astro-docs/1.0', 34 | }, 35 | }); 36 | 37 | const data = await res.json(); 38 | 39 | if (!res.ok) { 40 | throw new Error( 41 | `Request to fetch commits failed. Reason: ${res.statusText} 42 | Message: ${data.message}` 43 | ); 44 | } 45 | 46 | return data as Commit[]; 47 | } catch (e) { 48 | console.warn(`[error] /src/components/AvatarList.astro 49 | ${(e as any)?.message ?? e}`); 50 | return [] as Commit[]; 51 | } 52 | } 53 | 54 | function removeDups(arr: Commit[]) { 55 | const map = new Map(); 56 | 57 | for (let item of arr) { 58 | const author = item.author; 59 | // Deduplicate based on author.id 60 | map.set(author.id, { login: author.login, id: author.id }); 61 | } 62 | 63 | return [...map.values()]; 64 | } 65 | 66 | const data = await getCommits(url); 67 | const unique = removeDups(data); 68 | const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors 69 | const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors 70 | --- 71 | 72 | 73 |
74 |
    75 | {recentContributors.map((item) => ( 76 |
  • 77 | 78 | {`Contributor 85 | 86 |
  • 87 | ))} 88 |
89 | {additionalContributors > 0 && ( 90 | 91 | {`and ${additionalContributors} additional contributor${ 92 | additionalContributors > 1 ? 's' : '' 93 | }.`} 94 | 95 | )} 96 | {unique.length === 0 && Contributors} 97 |
98 | 99 | 168 | -------------------------------------------------------------------------------- /apps/docs/src/components/Footer/Footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AvatarList from './AvatarList.astro'; 3 | type Props = { 4 | path: string; 5 | }; 6 | const { path } = Astro.props as Props; 7 | --- 8 | 9 |
10 | 11 |
12 | 13 | 20 | -------------------------------------------------------------------------------- /apps/docs/src/components/HeadCommon.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import '../styles/theme.css'; 3 | import '../styles/index.css'; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 27 | 28 | 29 | 38 | 39 | 40 | 47 | -------------------------------------------------------------------------------- /apps/docs/src/components/HeadSEO.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { SITE, OPEN_GRAPH, Frontmatter } from '../config'; 3 | 4 | export interface Props { 5 | frontmatter: Frontmatter; 6 | canonicalUrl: URL; 7 | } 8 | 9 | const { frontmatter, canonicalUrl } = Astro.props as Props; 10 | const formattedContentTitle = `${frontmatter.title} 🚀 ${SITE.title}`; 11 | const imageSrc = frontmatter.image?.src ?? OPEN_GRAPH.image.src; 12 | const canonicalImageSrc = new URL(imageSrc, Astro.site); 13 | const imageAlt = frontmatter.image?.alt ?? OPEN_GRAPH.image.alt; 14 | --- 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/AstroLogo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | type Props = { 3 | size: number; 4 | }; 5 | const { size } = Astro.props as Props; 6 | --- 7 | 8 | 41 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/Header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getLanguageFromURL, KNOWN_LANGUAGE_CODES } from '../../languages'; 3 | import * as CONFIG from '../../config'; 4 | import AstroLogo from './AstroLogo.astro'; 5 | import SkipToContent from './SkipToContent.astro'; 6 | import SidebarToggle from './SidebarToggle'; 7 | import LanguageSelect from './LanguageSelect'; 8 | import Search from './Search'; 9 | 10 | type Props = { 11 | currentPage: string; 12 | }; 13 | 14 | const { currentPage } = Astro.props as Props; 15 | const lang = getLanguageFromURL(currentPage); 16 | --- 17 | 18 |
19 | 20 | 36 |
37 | 38 | 144 | 145 | 150 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/LanguageSelect.css: -------------------------------------------------------------------------------- 1 | .language-select { 2 | flex-grow: 1; 3 | width: 48px; 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0.33em 0.5em; 7 | overflow: visible; 8 | font-weight: 500; 9 | font-size: 1rem; 10 | font-family: inherit; 11 | line-height: inherit; 12 | background-color: var(--theme-bg); 13 | border-color: var(--theme-text-lighter); 14 | color: var(--theme-text-light); 15 | border-style: solid; 16 | border-width: 1px; 17 | border-radius: 0.25rem; 18 | outline: 0; 19 | cursor: pointer; 20 | transition-timing-function: ease-out; 21 | transition-duration: 0.2s; 22 | transition-property: border-color, color; 23 | -webkit-font-smoothing: antialiased; 24 | padding-left: 30px; 25 | padding-right: 1rem; 26 | } 27 | .language-select-wrapper .language-select:hover, 28 | .language-select-wrapper .language-select:focus { 29 | color: var(--theme-text); 30 | border-color: var(--theme-text-light); 31 | } 32 | .language-select-wrapper { 33 | color: var(--theme-text-light); 34 | position: relative; 35 | } 36 | .language-select-wrapper > svg { 37 | position: absolute; 38 | top: 7px; 39 | left: 10px; 40 | pointer-events: none; 41 | } 42 | 43 | @media (min-width: 50em) { 44 | .language-select { 45 | width: 100%; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/LanguageSelect.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource react */ 2 | import type { FunctionComponent } from 'react'; 3 | import './LanguageSelect.css'; 4 | import { KNOWN_LANGUAGES, langPathRegex } from '../../languages'; 5 | 6 | const LanguageSelect: FunctionComponent<{ lang: string }> = ({ lang }) => { 7 | return ( 8 |
9 | 27 | 45 |
46 | ); 47 | }; 48 | 49 | export default LanguageSelect; 50 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/Search.css: -------------------------------------------------------------------------------- 1 | /** Style Algolia */ 2 | :root { 3 | --docsearch-primary-color: var(--theme-accent); 4 | --docsearch-logo-color: var(--theme-text); 5 | } 6 | .search-input { 7 | flex-grow: 1; 8 | box-sizing: border-box; 9 | width: 100%; 10 | margin: 0; 11 | padding: 0.33em 0.5em; 12 | overflow: visible; 13 | font-weight: 500; 14 | font-size: 1rem; 15 | font-family: inherit; 16 | line-height: inherit; 17 | background-color: var(--theme-divider); 18 | border-color: var(--theme-divider); 19 | color: var(--theme-text-light); 20 | border-style: solid; 21 | border-width: 1px; 22 | border-radius: 0.25rem; 23 | outline: 0; 24 | cursor: pointer; 25 | transition-timing-function: ease-out; 26 | transition-duration: 0.2s; 27 | transition-property: border-color, color; 28 | -webkit-font-smoothing: antialiased; 29 | } 30 | .search-input:hover, 31 | .search-input:focus { 32 | color: var(--theme-text); 33 | border-color: var(--theme-text-light); 34 | } 35 | .search-input:hover::placeholder, 36 | .search-input:focus::placeholder { 37 | color: var(--theme-text-light); 38 | } 39 | .search-input::placeholder { 40 | color: var(--theme-text-light); 41 | } 42 | .search-hint { 43 | position: absolute; 44 | top: 7px; 45 | right: 19px; 46 | padding: 3px 5px; 47 | display: none; 48 | display: none; 49 | align-items: center; 50 | justify-content: center; 51 | letter-spacing: 0.125em; 52 | font-size: 13px; 53 | font-family: var(--font-mono); 54 | pointer-events: none; 55 | border-color: var(--theme-text-lighter); 56 | color: var(--theme-text-light); 57 | border-style: solid; 58 | border-width: 1px; 59 | border-radius: 0.25rem; 60 | line-height: 14px; 61 | } 62 | 63 | @media (min-width: 50em) { 64 | .search-hint { 65 | display: flex; 66 | } 67 | } 68 | 69 | /* ------------------------------------------------------------ *\ 70 | DocSearch (Algolia) 71 | \* ------------------------------------------------------------ */ 72 | 73 | .DocSearch-Modal .DocSearch-Hit a { 74 | box-shadow: none; 75 | border: 1px solid var(--theme-accent); 76 | } 77 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/Search.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource react */ 2 | import { useState, useCallback, useRef } from 'react'; 3 | import { ALGOLIA } from '../../config'; 4 | import '@docsearch/css'; 5 | import './Search.css'; 6 | 7 | import { createPortal } from 'react-dom'; 8 | import * as docSearchReact from '@docsearch/react'; 9 | 10 | /** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */ 11 | const DocSearchModal = 12 | docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal; 13 | const useDocSearchKeyboardEvents = 14 | docSearchReact.useDocSearchKeyboardEvents || 15 | (docSearchReact as any).default.useDocSearchKeyboardEvents; 16 | 17 | export default function Search() { 18 | const [isOpen, setIsOpen] = useState(false); 19 | const searchButtonRef = useRef(null); 20 | const [initialQuery, setInitialQuery] = useState(''); 21 | 22 | const onOpen = useCallback(() => { 23 | setIsOpen(true); 24 | }, [setIsOpen]); 25 | 26 | const onClose = useCallback(() => { 27 | setIsOpen(false); 28 | }, [setIsOpen]); 29 | 30 | const onInput = useCallback( 31 | (e) => { 32 | setIsOpen(true); 33 | setInitialQuery(e.key); 34 | }, 35 | [setIsOpen, setInitialQuery] 36 | ); 37 | 38 | useDocSearchKeyboardEvents({ 39 | isOpen, 40 | onOpen, 41 | onClose, 42 | onInput, 43 | searchButtonRef, 44 | }); 45 | 46 | return ( 47 | <> 48 | 69 | 70 | {isOpen && 71 | createPortal( 72 | { 80 | return items.map((item) => { 81 | // We transform the absolute URL into a relative URL to 82 | // work better on localhost, preview URLS. 83 | const a = document.createElement('a'); 84 | a.href = item.url; 85 | const hash = a.hash === '#overview' ? '' : a.hash; 86 | return { 87 | ...item, 88 | url: `${a.pathname}${hash}`, 89 | }; 90 | }); 91 | }} 92 | />, 93 | document.body 94 | )} 95 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/SidebarToggle.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource preact */ 2 | import type { FunctionalComponent } from 'preact'; 3 | import { useState, useEffect } from 'preact/hooks'; 4 | 5 | const MenuToggle: FunctionalComponent = () => { 6 | const [sidebarShown, setSidebarShown] = useState(false); 7 | 8 | useEffect(() => { 9 | const body = document.querySelector('body')!; 10 | if (sidebarShown) { 11 | body.classList.add('mobile-sidebar-toggle'); 12 | } else { 13 | body.classList.remove('mobile-sidebar-toggle'); 14 | } 15 | }, [sidebarShown]); 16 | 17 | return ( 18 | 41 | ); 42 | }; 43 | 44 | export default MenuToggle; 45 | -------------------------------------------------------------------------------- /apps/docs/src/components/Header/SkipToContent.astro: -------------------------------------------------------------------------------- 1 | --- 2 | type Props = {}; 3 | --- 4 | 5 | 6 | 7 | 27 | -------------------------------------------------------------------------------- /apps/docs/src/components/LeftSidebar/LeftSidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getLanguageFromURL } from '../../languages'; 3 | import { SIDEBAR } from '../../config'; 4 | 5 | type Props = { 6 | currentPage: string; 7 | }; 8 | 9 | const { currentPage } = Astro.props as Props; 10 | const currentPageMatch = currentPage.endsWith('/') 11 | ? currentPage.slice(1, -1) 12 | : currentPage.slice(1); 13 | const langCode = getLanguageFromURL(currentPage); 14 | const sidebar = SIDEBAR[langCode]; 15 | --- 16 | 17 | 40 | 41 | 49 | 50 | 112 | 113 | 118 | -------------------------------------------------------------------------------- /apps/docs/src/components/PageContent/PageContent.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Frontmatter } from '../../config'; 3 | import MoreMenu from '../RightSidebar/MoreMenu.astro'; 4 | import TableOfContents from '../RightSidebar/TableOfContents'; 5 | import type { MarkdownHeading } from 'astro'; 6 | 7 | type Props = { 8 | frontmatter: Frontmatter; 9 | headings: MarkdownHeading[]; 10 | githubEditUrl: string; 11 | }; 12 | 13 | const { frontmatter, headings, githubEditUrl } = Astro.props as Props; 14 | const title = frontmatter.title; 15 | --- 16 | 17 |
18 |
19 |

{title}

20 | 23 | 24 |
25 | 28 |
29 | 30 | 54 | -------------------------------------------------------------------------------- /apps/docs/src/components/RightSidebar/MoreMenu.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import ThemeToggleButton from './ThemeToggleButton'; 3 | import * as CONFIG from '../../config'; 4 | 5 | type Props = { 6 | editHref: string; 7 | }; 8 | 9 | const { editHref } = Astro.props as Props; 10 | const showMoreSection = CONFIG.COMMUNITY_INVITE_URL; 11 | --- 12 | 13 | {showMoreSection &&

More

} 14 | 64 |
65 | 66 |
67 | 68 | 76 | -------------------------------------------------------------------------------- /apps/docs/src/components/RightSidebar/RightSidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import TableOfContents from './TableOfContents'; 3 | import MoreMenu from './MoreMenu.astro'; 4 | import type { MarkdownHeading } from 'astro'; 5 | 6 | type Props = { 7 | headings: MarkdownHeading[]; 8 | githubEditUrl: string; 9 | }; 10 | 11 | const { headings, githubEditUrl } = Astro.props as Props; 12 | --- 13 | 14 | 20 | 21 | 35 | -------------------------------------------------------------------------------- /apps/docs/src/components/RightSidebar/TableOfContents.tsx: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent } from 'preact'; 2 | import { useState, useEffect, useRef } from 'preact/hooks'; 3 | import type { MarkdownHeading } from 'astro'; 4 | 5 | type ItemOffsets = { 6 | id: string; 7 | topOffset: number; 8 | }; 9 | 10 | const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ 11 | headings = [], 12 | }) => { 13 | const itemOffsets = useRef([]); 14 | // FIXME: Not sure what this state is doing. It was never set to anything truthy. 15 | const [activeId] = useState(''); 16 | useEffect(() => { 17 | const getItemOffsets = () => { 18 | const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)'); 19 | itemOffsets.current = Array.from(titles).map((title) => ({ 20 | id: title.id, 21 | topOffset: title.getBoundingClientRect().top + window.scrollY, 22 | })); 23 | }; 24 | 25 | getItemOffsets(); 26 | window.addEventListener('resize', getItemOffsets); 27 | 28 | return () => { 29 | window.removeEventListener('resize', getItemOffsets); 30 | }; 31 | }, []); 32 | 33 | return ( 34 | <> 35 |

On this page

36 |
    37 |
  • 38 | Overview 39 |
  • 40 | {headings 41 | .filter(({ depth }) => depth > 1 && depth < 4) 42 | .map((heading) => ( 43 |
  • 48 | {heading.text} 49 |
  • 50 | ))} 51 |
52 | 53 | ); 54 | }; 55 | 56 | export default TableOfContents; 57 | -------------------------------------------------------------------------------- /apps/docs/src/components/RightSidebar/ThemeToggleButton.css: -------------------------------------------------------------------------------- 1 | .theme-toggle { 2 | display: inline-flex; 3 | align-items: center; 4 | gap: 0.25em; 5 | padding: 0.33em 0.67em; 6 | border-radius: 99em; 7 | background-color: var(--theme-code-inline-bg); 8 | } 9 | 10 | .theme-toggle > label:focus-within { 11 | outline: 2px solid transparent; 12 | box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white; 13 | } 14 | 15 | .theme-toggle > label { 16 | color: var(--theme-code-inline-text); 17 | position: relative; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | opacity: 0.5; 22 | } 23 | 24 | .theme-toggle .checked { 25 | color: var(--theme-accent); 26 | opacity: 1; 27 | } 28 | 29 | input[name='theme-toggle'] { 30 | position: absolute; 31 | opacity: 0; 32 | top: 0; 33 | right: 0; 34 | bottom: 0; 35 | left: 0; 36 | z-index: -1; 37 | } 38 | -------------------------------------------------------------------------------- /apps/docs/src/components/RightSidebar/ThemeToggleButton.tsx: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent } from 'preact'; 2 | import { useState, useEffect } from 'preact/hooks'; 3 | import './ThemeToggleButton.css'; 4 | 5 | const themes = ['light', 'dark']; 6 | 7 | const icons = [ 8 | 15 | 20 | , 21 | 28 | 29 | , 30 | ]; 31 | 32 | const ThemeToggle: FunctionalComponent = () => { 33 | const [theme, setTheme] = useState(() => { 34 | if (import.meta.env.SSR) { 35 | return undefined; 36 | } 37 | if (typeof localStorage !== undefined && localStorage.getItem('theme')) { 38 | return localStorage.getItem('theme'); 39 | } 40 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) { 41 | return 'dark'; 42 | } 43 | return 'light'; 44 | }); 45 | 46 | useEffect(() => { 47 | const root = document.documentElement; 48 | if (theme === 'light') { 49 | root.classList.remove('theme-dark'); 50 | } else { 51 | root.classList.add('theme-dark'); 52 | } 53 | }, [theme]); 54 | 55 | return ( 56 |
57 | {themes.map((t, i) => { 58 | const icon = icons[i]; 59 | const checked = t === theme; 60 | return ( 61 | 76 | ); 77 | })} 78 |
79 | ); 80 | }; 81 | 82 | export default ThemeToggle; 83 | -------------------------------------------------------------------------------- /apps/docs/src/config.ts: -------------------------------------------------------------------------------- 1 | export const SITE = { 2 | title: 'React AV', 3 | description: 'Fully-featured, headless, hooks-based, and declarative media player framework for React.', 4 | defaultLanguage: 'en_US', 5 | }; 6 | 7 | export const OPEN_GRAPH = { 8 | image: { 9 | src: 'https://github.com/withastro/astro/blob/main/assets/social/banner.jpg?raw=true', 10 | alt: 11 | 'Fully-featured, headless, hooks-based, and declarative media player framework for React.', 12 | }, 13 | twitter: 'danielwykerd', 14 | }; 15 | 16 | // This is the type of the frontmatter you put in the docs markdown files. 17 | export type Frontmatter = { 18 | title: string; 19 | description: string; 20 | layout: string; 21 | image?: { src: string; alt: string }; 22 | dir?: 'ltr' | 'rtl'; 23 | ogLocale?: string; 24 | lang?: string; 25 | }; 26 | 27 | export const KNOWN_LANGUAGES = { 28 | English: 'en', 29 | } as const; 30 | export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES); 31 | 32 | export const GITHUB_EDIT_URL = `https://github.com/Wykerd/react-av/tree/master/apps/docs`; 33 | 34 | export const COMMUNITY_INVITE_URL = `https://astro.build/chat`; 35 | 36 | // See "Algolia" section of the README for more information. 37 | export const ALGOLIA = { 38 | indexName: 'XXXXXXXXXX', 39 | appId: 'XXXXXXXXXX', 40 | apiKey: 'XXXXXXXXXX', 41 | }; 42 | 43 | export type Sidebar = Record< 44 | typeof KNOWN_LANGUAGE_CODES[number], 45 | Record 46 | >; 47 | export const SIDEBAR: Sidebar = { 48 | en: { 49 | 'Getting Started': [ 50 | { text: 'Introduction', link: 'en/introduction' }, 51 | ], 52 | 'Core': [ 53 | { text: 'Components', link: 'en/core-components' }, 54 | { text: 'Hooks', link: 'en/core-hooks' }, 55 | { text: 'HLS and DASH support', link: 'en/core-other-sources' }, 56 | ], 57 | 'Text Tracks': [ 58 | { text: 'Introduction', link: 'en/text-track-introduction' }, 59 | { text: 'Components', link: 'en/text-track-components' }, 60 | { text: 'Hooks', link: 'en/text-track-hooks' }, 61 | { text: 'Implementation', link: 'en/webvtt' } 62 | ], 63 | 'Controls': [ 64 | { text: 'Introduction', link: 'en/controls-intro' }, 65 | { text: 'Core Controls', link: 'en/controls' }, 66 | { text: 'Sliders', link: 'en/sliders' }, 67 | { text: 'VTT Controls', link: 'en/vtt-controls' }, 68 | ], 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /apps/docs/src/demos/DemoOne.tsx: -------------------------------------------------------------------------------- 1 | import { Fullscreen, Mute, PictureInPicture, PlayPause, Timestamp } from "@react-av/controls"; 2 | import { Container, Root, Video, Viewport } from "@react-av/core"; 3 | import { ProgressBarBufferedRanges, ProgressBarRoot, VolumeRoot } from "@react-av/sliders"; 4 | import { InterfaceOverlay, Track } from "@react-av/vtt"; 5 | import * as Slider from '@radix-ui/react-slider'; 6 | import { toTimestampString } from "@react-av/controls"; 7 | import { useMediaDuration } from "@react-av/core"; 8 | import { ProgressBarTooltip, useMediaProgressBarTooltip } from "@react-av/sliders"; 9 | 10 | const ButtonStyles = "p-1 bg-transparent focus-visible:bg-primeA-4 focus-visible:backdrop-blur hover:bg-primeA-4 hover:backdrop-blur rounded-md block text-white transition"; 11 | 12 | function StyledProgressBarTooltip() { 13 | const { percentage } = useMediaProgressBarTooltip(); 14 | const duration = useMediaDuration(); 15 | 16 | return 17 | {toTimestampString(duration * percentage, duration >= 3600)} 18 | 19 | } 20 | 21 | export default function DemoOne() { 22 | return 23 | 24 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 60 | 61 | 62 | 63 | } -------------------------------------------------------------------------------- /apps/docs/src/demos/DemoThree.tsx: -------------------------------------------------------------------------------- 1 | import * as Media from '@react-av/core'; 2 | import { Track, useMediaTextTrack, Cue } from '@react-av/vtt'; 3 | import * as Slider from '@radix-ui/react-slider'; 4 | import { ProgressBarBufferedRanges, ProgressBarRoot, VolumeRoot } from "@react-av/sliders"; 5 | import { Mute, PlayPause, Timestamp } from '@react-av/controls'; 6 | import { useRef } from 'react'; 7 | 8 | const ButtonStyles = "p-1 bg-transparent focus-visible:bg-prime-4 focus-visible:backdrop-blur hover:bg-prime-4 hover:backdrop-blur rounded-md block text-white transition"; 9 | 10 | function Teleprompter() { 11 | const [cues, activeCues] = useMediaTextTrack('lyrics'); 12 | const [, setCurrentTime] = Media.useMediaCurrentTime(); 13 | 14 | const containerRef = useRef(null); 15 | 16 | return
    20 |
    21 | { 22 | cues.map((cue, i) => { 28 | if (!(current && containerRef.current)) return; 29 | if (!activeCues.includes(cue)) return; 30 | 31 | const elementRect = current.getBoundingClientRect(); 32 | const containerRect = containerRef.current.getBoundingClientRect(); 33 | 34 | const offsetTop = elementRect.top - containerRect.top + containerRef.current.scrollTop; 35 | 36 | const centeringOffset = containerRect.height / 2 - elementRect.height / 2; 37 | 38 | containerRef.current.scrollTo({ 39 | top: window.innerWidth <= 640 ? offsetTop : offsetTop - centeringOffset, 40 | behavior: "smooth" 41 | }); 42 | }} 43 | onClick={() => setCurrentTime(cue.startTime)} 44 | role="button" 45 | />) 46 | } 47 |
    48 |
49 | } 50 | 51 | export default function DemoThree() { 52 | return 53 | 54 | 55 |
56 |
59 |
60 | 61 |
62 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
85 |
86 | 87 |
88 | 89 | } -------------------------------------------------------------------------------- /apps/docs/src/demos/DemoTwo.tsx: -------------------------------------------------------------------------------- 1 | import { Root, Video } from "@react-av/core"; 2 | import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"; 3 | import { Editor, TimelineContainer, TimelineControlBar, TimelineEditor, TimelineHeader, TimelineSubtitleCueEditor, TimelineSubtitlesTrack } from "@react-av/editor"; 4 | import { Track } from "@react-av/vtt"; 5 | 6 | export default function DemoTwo() { 7 | return 8 | 9 | } 11 | styling={{ 12 | timelineBaseReelContainer: 'border-b border-l border-gray-6', 13 | timelineHeaderReelContainer: '', 14 | timelinePlayheadLine: 'bg-prime-11 top-0 h-full w-0.5', 15 | timelineDragElementBase: 'bg-primeA-4', 16 | timelineDragElementSelected: 'bg-primeA-8 cursor-move', 17 | timelineDraftElementBase: 'bg-primeA-6', 18 | timelineTimelineElement: 'text-xs items-center cursor-pointer', 19 | timelineTimelineElementBase: 'bg-primeA-7', 20 | timelineEntryLabelContainer: "flex flex-row gap-2 items-center px-2 border-b border-b-gray-6", 21 | timelineEntryLabelTextContainer: "flex flex-row grow items-center gap-2", 22 | timelineEntryLabelText: "text-sm text-gray-11", 23 | timelineEntryLabelControlsContainer: "flex flex-row items-center gap-2", 24 | }} 25 | > 26 | 27 | 35 | 36 | 44 | {/* */} 45 | 46 | , 57 | timelineSubtitleCueEditorDoneButton: 'bg-prime-3 hover:bg-prime-4 text-prime-11', 58 | timelineSubtitleCueEditorDoneIcon: 59 | }} 60 | /> 61 | 62 | 63 | 64 | 65 | 66 | } -------------------------------------------------------------------------------- /apps/docs/src/demos/DemoTwoCodeSnippet.tsx: -------------------------------------------------------------------------------- 1 | const DEMO_TWO_CODE_SNIPPET = ` 2 | 5 | } 6 | > 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ` 18 | 19 | function CodeSnippet({ 20 | snippet 21 | }: { 22 | snippet: string 23 | }) { 24 | return
25 |
26 |             {snippet.split('\n').map((_, i) => `${i + 1}`.padStart(2, ' ')).join('\n')}
27 |         
28 |
29 |             {snippet}
30 |         
31 |
32 | } 33 | 34 | export default function DemoTwoCodeSnippet() { 35 | return 36 | } -------------------------------------------------------------------------------- /apps/docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /apps/docs/src/languages.ts: -------------------------------------------------------------------------------- 1 | import { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES } from './config'; 2 | export { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES }; 3 | 4 | export const langPathRegex = /\/([a-z]{2}-?[A-Z]{0,2})\//; 5 | 6 | export function getLanguageFromURL(pathname: string) { 7 | const langCodeMatch = pathname.match(langPathRegex); 8 | const langCode = langCodeMatch ? langCodeMatch[1] : 'en'; 9 | return langCode as typeof KNOWN_LANGUAGE_CODES[number]; 10 | } 11 | -------------------------------------------------------------------------------- /apps/docs/src/layouts/MainLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import HeadCommon from '../components/HeadCommon.astro'; 3 | import HeadSEO from '../components/HeadSEO.astro'; 4 | import Header from '../components/Header/Header.astro'; 5 | import PageContent from '../components/PageContent/PageContent.astro'; 6 | import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro'; 7 | import RightSidebar from '../components/RightSidebar/RightSidebar.astro'; 8 | import * as CONFIG from '../config'; 9 | import type { MarkdownHeading } from 'astro'; 10 | import Footer from '../components/Footer/Footer.astro'; 11 | 12 | type Props = { 13 | frontmatter: CONFIG.Frontmatter; 14 | headings: MarkdownHeading[]; 15 | }; 16 | 17 | const { frontmatter, headings } = Astro.props as Props; 18 | const canonicalURL = new URL(Astro.url.pathname, Astro.site); 19 | const currentPage = Astro.url.pathname; 20 | const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.md`; 21 | const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`; 22 | --- 23 | 24 | 25 | 26 | 27 | 28 | 29 | {frontmatter.title ? `${frontmatter.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title} 30 | 31 | 105 | 120 | 121 | 122 | 123 |
124 |
125 | 128 |
129 | 130 | 131 | 132 |
133 | 136 |
137 |