diff --git a/examples/s2-webpack-5-typescript-example/.gitignore b/examples/s2-webpack-5-typescript-example/.gitignore
new file mode 100644
index 00000000000..911108854aa
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/.gitignore
@@ -0,0 +1,9 @@
+node_modules
+build
+.DS_Store
+npm-debug.log
+yarn-error.log
+.yarnclean
+.vscode
+.idea
+dist
\ No newline at end of file
diff --git a/examples/s2-webpack-5-typescript-example/README.md b/examples/s2-webpack-5-typescript-example/README.md
new file mode 100644
index 00000000000..13525dc3e29
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/README.md
@@ -0,0 +1,55 @@
+# Webpack 5 example
+
+This is a [Webpack](https://webpack.js.org/) project with a minimal React configuration.
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+yarn install
+yarn dev
+```
+
+Open [http://localhost:8080](http://localhost:8080) with your browser to see the result.
+
+style-macro and React Spectrum - Spectrum 2 have been added to `src/App.js` to show an example of a Spectrum 2 styled component. This file does client side rendering. The page auto-updates as you edit the file.
+
+## Macros config
+
+Edit the webpack.config.js to add an import for the plugin and add a webpack config that adds the webpack version of the macros plugin. An empty config file would be updated to look like the following.
+
+```
+const macros = require("unplugin-parcel-macros");
+
+module.exports = {
+ // ...
+ plugins: [
+ // ...
+ macros.webpack(),
+ // ...
+ ],
+};
+```
+
+To use the spectrum-theme via macros, pass your styles object to the style() macro and set the result as a new function. This new function or style() should be used within a `className` prop to style your html elements. Use the `styles` prop on React Spectrum components.
+
+```jsx
+
+ Hello Spectrum 2!
+
+```
+
+```jsx
+
+```
+
+## Application setup
+
+Please include the page level CSS in the root of your application to configure and support the light and dark themes.
+
+```
+import "@react-spectrum/s2/page.css";
+```
diff --git a/examples/s2-webpack-5-typescript-example/package.json b/examples/s2-webpack-5-typescript-example/package.json
new file mode 100644
index 00000000000..b89f287aad5
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "webpack-5-typescript-example",
+ "version": "1.0.0",
+ "description": "",
+ "main": "./src/index.tsx",
+ "packageManager": "yarn@4.2.2",
+ "scripts": {
+ "dev": "webpack serve",
+ "build": "webpack --mode production"
+ },
+ "dependencies": {
+ "@react-spectrum/s2": "latest",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@swc/core": "^1.11.13",
+ "@types/react": "^19.0.12",
+ "@types/react-dom": "^19.0.4",
+ "css-loader": "^6.10.0",
+ "css-minimizer-webpack-plugin": "^7.0.0",
+ "html-webpack-plugin": "^5.6.0",
+ "lightningcss": "^1.27.0",
+ "mini-css-extract-plugin": "^2.9.1",
+ "style-loader": "^3.3.4",
+ "swc-loader": "^0.2.6",
+ "swc-minify-webpack-plugin": "^2.1.3",
+ "typescript": "^5.8.2",
+ "unplugin-parcel-macros": "0.0.3",
+ "webpack": "^5.91.0",
+ "webpack-cli": "^5.1.4",
+ "webpack-dev-server": "^5.0.4"
+ }
+}
diff --git a/examples/s2-webpack-5-typescript-example/src/App.tsx b/examples/s2-webpack-5-typescript-example/src/App.tsx
new file mode 100644
index 00000000000..dd380117008
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/src/App.tsx
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import "@react-spectrum/s2/page.css";
+import {
+ ActionButton,
+ ActionButtonGroup,
+ ActionMenu,
+ Button,
+ ButtonGroup,
+ Cell,
+ Column,
+ Divider,
+ Heading,
+ LinkButton,
+ Menu,
+ MenuItem,
+ MenuTrigger,
+ Picker,
+ PickerItem,
+ Row,
+ SubmenuTrigger,
+ TableBody,
+ TableHeader,
+ TableView,
+ Text,
+ ToggleButton,
+ ToggleButtonGroup,
+ TreeView,
+ TreeViewItem,
+ TreeViewItemContent
+} from "@react-spectrum/s2";
+import {CardViewExample} from "./components/CardViewExample";
+import {CollectionCardsExample} from "./components/CollectionCardsExample";
+import Edit from "@react-spectrum/s2/icons/Edit";
+import FileTxt from "@react-spectrum/s2/icons/FileText";
+import Folder from "@react-spectrum/s2/icons/Folder";
+import {LoadingState} from '@react-types/shared'
+import React, {useState} from "react";
+import Section from "./components/Section";
+import {style} from "@react-spectrum/s2/style" with { type: "macro" };
+
+const Lazy = React.lazy(() => import('./Lazy'));
+
+function App() {
+ let [isLazyLoaded, setLazyLoaded] = useState(false);
+ let [cardViewState, setCardViewState] = useState<{layout?: 'grid' | 'waterfall' , loadingState: LoadingState}>({
+ layout: 'grid',
+ loadingState: 'idle',
+ });
+ let cardViewLoadingOptions = [
+ {id: 'idle', label: 'Idle'},
+ {id: 'loading', label: 'Loading'},
+ {id: 'sorting', label: 'Sorting'},
+ {id: 'loadingMore', label: 'Loading More'},
+ {id: 'error', label: 'Error'},
+ ];
+ let cardViewLayoutOptions = [
+ {id: 'grid', label: 'Grid'},
+ {id: 'waterfall', label: 'Waterfall'}
+ ];
+ return (
+
+
+ Spectrum 2 + Webpack + Typescript
+
+
+
+
+
+
+
+
+
+
+
+ Action Button
+
+ Toggle Button
+
+ Link Button
+
+
+ Cut
+ Copy
+ Paste
+
+
+ Bold
+ Italic
+ Underline
+
+
+
+
+
+
+
+
+
+
+ setCardViewState({...cardViewState, loadingState} as any)}>
+ {item => {item.label}}
+
+ setCardViewState({...cardViewState, layout} as any)}>
+ {item => {item.label}}
+
+
+
+
+
+ Menu
+
+
+
+ Menu Trigger
+
+
+
+
+
+
+
+
+
+ Name
+ Type
+ Date Modified
+ A
+ B
+
+
+
+ Games
+ File folder
+ 6/7/2020
+ Dummy content
+ Long long long long long long long cell
+
+
+ Program Files
+ File folder
+ 4/7/2021
+ Dummy content
+ Long long long long long long long cell
+
+
+ bootmgr
+ System file
+ 11/20/2010
+ Dummy content
+ Long long long long long long long cell
+
+
+
+
+
+
+ Photos
+
+
+
+
+
+ Projects
+
+
+
+
+ Projects-1
+
+
+
+
+ Projects-1A
+
+
+
+
+
+
+ Projects-2
+
+
+
+
+
+ Projects-3
+
+
+
+
+
+
+
+ {!isLazyLoaded && setLazyLoaded(true)}>Load more}
+ {isLazyLoaded && Loading>}>
+
+ }
+
+
+ Home
+ Trendy
+ March 2020 Assets
+
+
+ The missing link.
+
+ Foo
+
+ Day
+ Week
+ Month
+ Year
+
+
+
+ Founding of Rome
+ Monarchy and Republic
+ Empire
+
+
+ Arma virumque cano, Troiae qui primus ab oris.
+
+ Senatus Populusque Romanus.
+ Alea jacta est.
+
+
+
+
+
+ Save
+
+ You are running low on disk space. Delete unnecessary files to
+ free up space.
+
+
+
+
+ Need help?
+
+
+ If you are having issues accessing your account, contact our
+ customer support team for help.
+
+
+
+
+ setIsDialogOpen(true)}>
+ Show Dialog
+
+ setIsDialogOpen(false)}>
+ {isDialogOpen && (
+
+ Are you sure you want to delete this item?
+
+ )}
+
+
+
+ Check connectivity
+
+
+
+
+ Illustration
+
+
+
+ Thank you!
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+
+
+
+
+
+ Disk Status
+
+ C://
+
+
+ 50% disk space remaining.
+
+
+
+
+
+
+
+
+ Change Name
+
+
+
+
+
+ Red Panda
+ Cat
+ Dog
+ Aardvark
+ Kangaroo
+ Snake
+
+
+
+ Rarely
+ Sometimes
+ Always
+
+
+
+
+
+
+
+
+
+
+ Badge
+
+
+
+
+
+
+
+
+
+
+
+ Ready
+
+
+ News
+ Travel
+ Gaming
+ Shopping
+
+
+ Payment Information
+
+ Enter your billing address, shipping address, and payment method
+ to complete your purchase.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Content is king
+
+
+
+ Cute cats
+
+ Edit
+
+
+
+ No results
+ Try another search
+
+
+
+
+ ⌘V
+
+ Paste
+
+ >
+ );
+}
diff --git a/examples/s2-webpack-5-typescript-example/src/components/CardViewExample.tsx b/examples/s2-webpack-5-typescript-example/src/components/CardViewExample.tsx
new file mode 100644
index 00000000000..bf63bb80ee2
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/src/components/CardViewExample.tsx
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import {ActionMenu, Avatar, Card, CardPreview, CardView, CardViewProps, Collection, CollectionCardPreview, Content, Image, MenuItem, SkeletonCollection, Text} from '@react-spectrum/s2';
+import ErrorIcon from '@react-spectrum/s2/illustrations/linear/AlertNotice';
+import Folder from '@react-spectrum/s2/icons/Folder';
+import {iconStyle, style} from "@react-spectrum/s2/style" with { type: "macro" };
+import {useAsyncList} from 'react-stately';
+
+const cardViewStyles = style({
+ width: 'full',
+ maxWidth: '[800px]',
+ height: '[800px]',
+ margin: 32
+});
+
+const avatarSize = {
+ XS: 16,
+ S: 20,
+ M: 24,
+ L: 28,
+ XL: 32
+};
+
+type Item = {
+ id: number,
+ user: {
+ name: string,
+ profile_image: { small: string }
+ },
+ urls: { regular: string },
+ description: string,
+ alt_description: string,
+ width: number,
+ height: number
+};
+
+function PhotoCard({item, layout}: {item: Item, layout: string}) {
+ return (
+
+ {({size}) => (<>
+
+ (
+
+
+
+ );
+}
+
+export const CollectionCardsExample = (props: CardViewProps) => {
+ let list = useAsyncList({
+ async load({signal, cursor}) {
+ let page = cursor || 1;
+ let res = await fetch(
+ `https://api.unsplash.com/topics?page=${page}&per_page=30&client_id=AJuU-FPh11hn7RuumUllp4ppT8kgiLS7LtOHp_sp4nc`,
+ {signal}
+ );
+ let items = (await res.json()).filter((topic: Topic) => !!topic.preview_photos);
+ return {items, cursor: items.length ? page + 1 : null};
+ }
+ });
+
+ let loadingState = props.loadingState === 'idle' ? list.loadingState : props.loadingState;
+ let items = loadingState === 'loading' ? [] : list.items;
+
+ return (
+
+
+ {topic => }
+
+ {(loadingState === 'loading' || loadingState === 'loadingMore') && (
+
+ {() => (
+
+ )}
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/examples/s2-webpack-5-typescript-example/src/components/CollectionCardsExample.tsx b/examples/s2-webpack-5-typescript-example/src/components/CollectionCardsExample.tsx
new file mode 100644
index 00000000000..127ff83254c
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/src/components/CollectionCardsExample.tsx
@@ -0,0 +1,92 @@
+
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import {Card, CardView, Collection, CollectionCardPreview, Content, Image, SkeletonCollection, Text} from '@react-spectrum/s2';
+import Folder from '@react-spectrum/s2/icons/Folder';
+import {style} from "@react-spectrum/s2/style" with { type: "macro" };
+import {useAsyncList} from 'react-stately';
+
+const cardViewStyles = style({
+ width: 'full',
+ maxWidth: '[800px]',
+ height: '[800px]',
+ margin: 32
+});
+
+function TopicCard({topic}: any) {
+ return (
+
+
+ {topic.preview_photos.slice(0, 4).map((photo: any) => (
+
+ ))}
+
+
+ {topic.title}
+
+
+
+ );
+}
+
+export const CollectionCardsExample = (props: any) => {
+ let list = useAsyncList({
+ async load({signal, cursor}: any) {
+ let page = cursor || 1;
+ let res = await fetch(
+ `https://api.unsplash.com/topics?page=${page}&per_page=30&client_id=AJuU-FPh11hn7RuumUllp4ppT8kgiLS7LtOHp_sp4nc`,
+ {signal}
+ );
+ let items = (await res.json()).filter((topic: any) => !!topic.preview_photos);
+ return {items, cursor: items.length ? page + 1 : null};
+ }
+ });
+
+ let loadingState = props.loadingState === 'idle' ? list.loadingState : props.loadingState;
+ let items = loadingState === 'loading' ? [] : list.items;
+
+ return (
+
+
+ {topic => }
+
+ {(loadingState === 'loading' || loadingState === 'loadingMore') && (
+
+ {() => (
+
+ )}
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/examples/s2-webpack-5-typescript-example/src/components/Section.tsx b/examples/s2-webpack-5-typescript-example/src/components/Section.tsx
new file mode 100644
index 00000000000..16779348d73
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/src/components/Section.tsx
@@ -0,0 +1,30 @@
+import {Heading} from "@react-spectrum/s2";
+import {style} from "@react-spectrum/s2/style" with { type: "macro" };
+
+export default function Section(props: any) {
+ let { title, children } = props;
+ return (
+
+
+ {title}
+
+
+ {children}
+
+
+ );
+}
diff --git a/examples/s2-webpack-5-typescript-example/src/index.html b/examples/s2-webpack-5-typescript-example/src/index.html
new file mode 100644
index 00000000000..d1078b284bc
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/src/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Spectrum 2 + Webpack
+
+
+
+
+
+
diff --git a/examples/s2-webpack-5-typescript-example/src/index.tsx b/examples/s2-webpack-5-typescript-example/src/index.tsx
new file mode 100644
index 00000000000..c8ff5374360
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/src/index.tsx
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import React from 'react';
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+const root: any = document.getElementById('root');
+createRoot(root).render();
diff --git a/examples/s2-webpack-5-typescript-example/tsconfig.json b/examples/s2-webpack-5-typescript-example/tsconfig.json
new file mode 100644
index 00000000000..115bb034d22
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "noEmit": true
+ },
+ "include": ["src"]
+}
\ No newline at end of file
diff --git a/examples/s2-webpack-5-typescript-example/webpack.config.js b/examples/s2-webpack-5-typescript-example/webpack.config.js
new file mode 100644
index 00000000000..8ee5b271a2c
--- /dev/null
+++ b/examples/s2-webpack-5-typescript-example/webpack.config.js
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+const path = require("path");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const {SwcMinifyWebpackPlugin} = require("swc-minify-webpack-plugin");
+const {browserslistToTargets} = require('lightningcss');
+const browserslist = require('browserslist');
+const macros = require("unplugin-parcel-macros");
+const webpack = require('webpack');
+
+// Adjust this to target the browsers your product supports.
+// https://browserslist.dev/
+const BROWSERSLIST = 'last 2 Chrome versions, last 2 Safari versions, last 2 Firefox versions';
+
+module.exports = (env, argv) => ({
+ entry: path.join(__dirname, "src", "index.tsx"),
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ },
+ resolve: {
+ extensions: ['.tsx', '.ts', '.js', '.jsx'],
+ alias: {
+ 'browser': false
+ }
+ },
+ mode: argv.mode || "development",
+ module: {
+ rules: [
+ {
+ test: /\.(ts|tsx|js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "swc-loader",
+ options: {
+ env: {
+ targets: BROWSERSLIST
+ },
+ jsc: {
+ parser: {
+ syntax: "typescript",
+ tsx: true
+ }
+ }
+ },
+ },
+ },
+ {
+ // Use mini-css-extract-plugin instead of style-loader to that CSS is extracted
+ // into a standalone .css bundle instead of inlined into JS via