diff --git a/.env.development b/.env.development index 1f194856..59e2435a 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,3 @@ SHOW_TEST_PAGES=true -SHOW_DRAFT_PAGES=true \ No newline at end of file +SHOW_DRAFT_PAGES=true +GITHUB_TOKEN=${GITHUB_TOKEN_READONLY} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f99b2997..1611a389 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@blacksheepcode/example-react-autocomplete": "git+https://github.com/dwjohnston/example-react-autocomplete.git#c8e74acf9d10aef0dfc2b6f6d33ee811371ab99a", "@blacksheepcode/react-text-highlight": "^0.4.0", "@mdx-js/loader": "^3.0.1", "@mdx-js/mdx": "^3.0.1", @@ -526,6 +527,19 @@ "node": ">=6.9.0" } }, + "node_modules/@blacksheepcode/example-react-autocomplete": { + "name": "example-react-autocomplete", + "version": "0.1.0", + "resolved": "git+ssh://git@github.com/dwjohnston/example-react-autocomplete.git#c8e74acf9d10aef0dfc2b6f6d33ee811371ab99a", + "integrity": "sha512-614u8N7AHeN2pcnIb1TcmNjvL/rzTAVEQVOuVqUJGL/l12LYuorRJI3G6N4d+M51qACmSWc76+SiJy2mpqBkfw==", + "dependencies": { + "@tanstack/react-query": "^5.87.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@blacksheepcode/react-text-highlight": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@blacksheepcode/react-text-highlight/-/react-text-highlight-0.4.0.tgz", @@ -5316,6 +5330,30 @@ "tslib": "^2.8.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.90.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.5.tgz", + "integrity": "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.5.tgz", + "integrity": "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q==", + "dependencies": { + "@tanstack/query-core": "5.90.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/cypress": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.0.3.tgz", @@ -18252,6 +18290,14 @@ "@babel/helper-validator-identifier": "^7.27.1" } }, + "@blacksheepcode/example-react-autocomplete": { + "version": "git+ssh://git@github.com/dwjohnston/example-react-autocomplete.git#c8e74acf9d10aef0dfc2b6f6d33ee811371ab99a", + "integrity": "sha512-614u8N7AHeN2pcnIb1TcmNjvL/rzTAVEQVOuVqUJGL/l12LYuorRJI3G6N4d+M51qACmSWc76+SiJy2mpqBkfw==", + "from": "@blacksheepcode/example-react-autocomplete@git+https://github.com/dwjohnston/example-react-autocomplete.git#c8e74acf9d10aef0dfc2b6f6d33ee811371ab99a", + "requires": { + "@tanstack/react-query": "^5.87.1" + } + }, "@blacksheepcode/react-text-highlight": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@blacksheepcode/react-text-highlight/-/react-text-highlight-0.4.0.tgz", @@ -21112,6 +21158,19 @@ "tslib": "^2.8.0" } }, + "@tanstack/query-core": { + "version": "5.90.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.5.tgz", + "integrity": "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w==" + }, + "@tanstack/react-query": { + "version": "5.90.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.5.tgz", + "integrity": "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q==", + "requires": { + "@tanstack/query-core": "5.90.5" + } + }, "@testing-library/cypress": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.0.3.tgz", diff --git a/package.json b/package.json index 2b8ff6be..56532e20 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@blacksheepcode/react-text-highlight": "^0.4.0", + "@blacksheepcode/example-react-autocomplete": "git+https://github.com/dwjohnston/example-react-autocomplete.git#c8e74acf9d10aef0dfc2b6f6d33ee811371ab99a", "@mdx-js/loader": "^3.0.1", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", diff --git a/src/app/globals.css b/src/app/globals.css index b1d34dd9..248bbcb7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -57,6 +57,10 @@ body { --column-width: 1100px; } +img { + max-width: 100%; +} + pre, pre:hover { white-space: pre-wrap; diff --git a/src/assets/autocomplete.png b/src/assets/autocomplete.png new file mode 100644 index 00000000..8123ddfe Binary files /dev/null and b/src/assets/autocomplete.png differ diff --git a/src/components/BlogPostFrame/react-text-highlight.css b/src/components/BlogPostFrame/react-text-highlight.css index efa3d316..d1c7e724 100644 --- a/src/components/BlogPostFrame/react-text-highlight.css +++ b/src/components/BlogPostFrame/react-text-highlight.css @@ -56,7 +56,7 @@ @media (prefers-color-scheme: dark) { :root { - --highlight-bg-color: #4a4b80; + --highlight-bg-color: #5790d459; --highlight-text-color: #fff; --highlight-bg-color-hover: #4a4b80; --highlight-border-hover: #5b5f99; diff --git a/src/demos/encapsulate-state/AutocompleteDemo.tsx b/src/demos/encapsulate-state/AutocompleteDemo.tsx new file mode 100644 index 00000000..a8f58063 --- /dev/null +++ b/src/demos/encapsulate-state/AutocompleteDemo.tsx @@ -0,0 +1,91 @@ + +"use client"; +import { AutocompleteB } from "@blacksheepcode/example-react-autocomplete"; + + +type TestItem = { + id: number; + name: string; + description: string; +}; + +const mockItems: TestItem[] = [ + { id: 1, name: 'Apple', description: 'A red fruit' }, + { id: 2, name: 'Banana', description: 'A yellow fruit' }, + { id: 3, name: 'Cherry', description: 'A small red fruit' }, + { id: 4, name: 'Dragonfruit', description: 'A vibrant exotic fruit with a speckled interior' }, + { id: 5, name: 'Elderberry', description: 'Small dark berries often used in syrups' }, + { id: 6, name: 'Fig', description: 'A sweet, soft fruit with many seeds' }, + { id: 7, name: 'Grape', description: 'Small round fruit used for snacking and wine' }, + { id: 8, name: 'Honeydew', description: 'A sweet, pale green melon' }, + { id: 9, name: 'Indian Fig', description: 'Also known as prickly pear, a desert fruit' }, + { id: 10, name: 'Jackfruit', description: 'A large tropical fruit with sweet flesh' }, + { id: 11, name: 'Kiwi', description: 'A small fruit with fuzzy skin and bright green flesh' }, + { id: 12, name: 'Lemon', description: 'A tart yellow citrus fruit' }, + { id: 13, name: 'Mango', description: 'A juicy tropical stone fruit' }, + { id: 14, name: 'Nectarine', description: 'A smooth-skinned variety of peach' }, + { id: 15, name: 'Orange', description: 'A sweet citrus fruit' }, + { id: 16, name: 'Papaya', description: 'A soft, tropical fruit with orange flesh' }, + { id: 17, name: 'Quince', description: 'A yellow fruit often cooked into jams' }, + { id: 18, name: 'Raspberry', description: 'A small red berry with a tart-sweet flavor' }, + { id: 19, name: 'Strawberry', description: 'A popular red berry with a sweet taste' }, + { id: 20, name: 'Tomato', description: 'Botanically a fruit commonly used as a vegetable' }, + { id: 21, name: 'Ugli Fruit', description: 'A tangy citrus hybrid with rough skin' }, + { id: 22, name: 'Vanilla', description: 'A fragrant pod used for flavoring (technically an orchid fruit)' }, + { id: 23, name: 'Watermelon', description: 'A large, juicy melon with green rind and red flesh' }, + { id: 24, name: 'Xigua', description: 'Another name for watermelon, used in some regions' }, + { id: 25, name: 'Yuzu', description: 'A tart citrus used in Japanese cuisine' }, + { id: 26, name: 'Zucchini', description: 'A summer squash often treated as a vegetable' }, + { id: 27, name: 'Pineapple', description: 'A tropical fruit with sweet, juicy flesh and spiky skin' }, + { id: 28, name: 'Crabapple', description: 'A small tart apple often used for jams and preserves' }, + { id: 29, name: 'Apple Cider', description: 'A beverage made from pressed apples, often enjoyed warm or spiced' }, + { id: 30, name: 'Black Cherry', description: 'A dark-sweet cherry variety' }, + { id: 31, name: 'Maraschino Cherry', description: 'A preserved, sweet, bright red cherry used for desserts and cocktails' }, + { id: 32, name: 'Sour Cherry', description: 'A tart cherry commonly used in baking and preserves' }, + { id: 33, name: 'Concord Grape', description: 'A dark purple grape used for juice and jelly' }, + { id: 34, name: 'Grape Jelly', description: 'A sweet spread made from grapes' }, + { id: 35, name: 'Grapefruit', description: 'A large citrus fruit with a tangy, slightly bitter flavor' }, +]; + + +const searchFn = async (searchTerm: string, pageNumber: number) => { + await new Promise(resolve => setTimeout(resolve, 300)); + + if (!searchTerm) { + + return { + items: [], + pageMeta: { + totalResults: 0, + pageNumber: 1, + resultsPerPage: 10, + }, + }; + } + + const filteredItems = mockItems.filter(item => + (`${item.name} ${item.description}`).toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return { + items: filteredItems, + pageMeta: { + totalResults: filteredItems.length, + pageNumber: pageNumber, + resultsPerPage: 10, + }, + }; +} +export function AutocompleteDemo() { + return ( +
Enter some search terms to see the behaviour of the Autocomplete component
+ Hint: Use the terms 'apple', 'cherry' or 'grape'
>}> +AbortablePromise instead of a regular Promise.
+}>cancelling+ Not to mention that this implementation doesn't allow for an initially selected value - we would need to add an `initialValue` and an async function `fetchValueById` props.
The thinking here is that we shouldn't need a full `T` object to provide as the initial value. Often when a page or form loads - we just have the ID of the thing (a userId, a todoId, etc).
+ Rather than the parent component loading the thing, and _then_ showing the autocomplete, the parent component simply provides an async function in order for the Autocomplete to fetch it itself.
+>}>`selectedValueDisplayStringFn` stuff