diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 031ed658cb9..2e2fbc4dc98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -269,14 +269,15 @@ jobs: with: cache_mode: ro - - name: module-size tests (non-sharded) - if: matrix.shard == 0 - id: test-no-shard - run: yarn nx ${{ github.event.inputs.nx_command || 'affected' }} -t test:e2e -c staging --exclude '!tag:module-size' --exclude 'tag:sharding' --exclude all - - name: module-size tests - if: matrix.shard != 0 - id: test - run: yarn nx ${{ github.event.inputs.nx_command || 'affected' }} -t test:e2e -c staging --exclude '!tag:module-size' --exclude 'tag:non-sharding' --exclude all --shard=${{ matrix.shard }}/$((${{ strategy.job-total }} - 1)) + # Disable now that module size tests are reported on PRs + # - name: module-size tests (non-sharded) + # if: matrix.shard == 0 + # id: test-no-shard + # run: yarn nx ${{ github.event.inputs.nx_command || 'affected' }} -t test:e2e -c staging --exclude '!tag:module-size' --exclude 'tag:sharding' --exclude all + # - name: module-size tests + # if: matrix.shard != 0 + # id: test + # run: yarn nx ${{ github.event.inputs.nx_command || 'affected' }} -t test:e2e -c staging --exclude '!tag:module-size' --exclude 'tag:non-sharding' --exclude all --shard=${{ matrix.shard }}/$((${{ strategy.job-total }} - 1)) - name: Persist test results if: always() && matrix.shard != 0 diff --git a/.tool-versions b/.tool-versions index 6f8fb7bdafa..668b21d92dc 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 20.19.4 +nodejs 22.21.1 diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/customers/Customers.module.scss b/documentation/ag-grid-docs/src/components/customers/Customers.module.scss similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/customers/Customers.module.scss rename to documentation/ag-grid-docs/src/components/customers/Customers.module.scss diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/customers/Customers.tsx b/documentation/ag-grid-docs/src/components/customers/Customers.tsx similarity index 67% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/customers/Customers.tsx rename to documentation/ag-grid-docs/src/components/customers/Customers.tsx index a43432d9fb3..3b84d5e9389 100644 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/customers/Customers.tsx +++ b/documentation/ag-grid-docs/src/components/customers/Customers.tsx @@ -1,18 +1,21 @@ import { CustomerLogos } from '@components/customer-logos/CustomerLogos'; import { Quotes } from '@components/quotes/Quotes'; import { quotesData } from '@components/quotes/quotesData'; -import React from 'react'; +import React, { type FunctionComponent } from 'react'; import styles from './Customers.module.scss'; -const Customers: React.FC = () => { +interface Props { + displayLogos: boolean; +} + +const Customers: FunctionComponent = ({ displayLogos = true }) => { return (
- - + {displayLogos && }
); }; diff --git a/documentation/ag-grid-docs/src/components/example-runner/components/CodeOptions.tsx b/documentation/ag-grid-docs/src/components/example-runner/components/CodeOptions.tsx index c62c71034df..3d282263a36 100644 --- a/documentation/ag-grid-docs/src/components/example-runner/components/CodeOptions.tsx +++ b/documentation/ag-grid-docs/src/components/example-runner/components/CodeOptions.tsx @@ -67,7 +67,7 @@ function CodeOptionSelector({ value={internalFramework} onChange={(event) => { onChange(event); - tracking && tracking(event.target.value); + tracking?.(event.target.value); }} onBlur={onChange} > diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallText.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallText.tsx deleted file mode 100644 index 50ca131346a..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallText.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import type { Framework } from '@ag-grid-types'; -import { Icon } from '@ag-website-shared/components/icon/Icon'; -import AngularIcon from '@ag-website-shared/images/inline-svgs/angular.svg?react'; -import JavaScriptIcon from '@ag-website-shared/images/inline-svgs/javascript.svg?react'; -import ReactIcon from '@ag-website-shared/images/inline-svgs/react.svg?react'; -import VueIcon from '@ag-website-shared/images/inline-svgs/vue.svg?react'; -import { useFrameworkSelector } from '@ag-website-shared/utils/useFrameworkSelector'; -import { useRef, useState } from 'react'; - -import styles from './InstallText.module.scss'; - -const FRAMEWORK_CONFIGS: Record = { - react: { - Icon: ReactIcon, - command: 'npm install ag-grid-react', - name: 'React', - }, - angular: { - Icon: AngularIcon, - command: 'npm install ag-grid-angular', - name: 'Angular', - }, - vue: { - Icon: VueIcon, - command: 'npm install ag-grid-vue3', - name: 'Vue', - }, - javascript: { - Icon: JavaScriptIcon, - command: 'npm install ag-grid-community', - name: 'JavaScript', - }, -}; - -const InstallText = () => { - const { framework, handleFrameworkChange } = useFrameworkSelector(); - const [iconState, setIconState] = useState<'copy' | 'animating' | 'tick'>('copy'); - const [isHovering, setIsHovering] = useState(false); - const [isHiding, setIsHiding] = useState(false); - const installTextRef = useRef(null); - const containerRef = useRef(null); - const copyButtonRef = useRef(null); - const overlayTimerRef = useRef(null); - - const copyToClipboard = () => { - const text = installTextRef?.current?.innerText?.replace('$', '').trim(); - navigator.clipboard.writeText(text).then(() => { - // Start the animation sequence - setIconState('animating'); - - // After a slight pause, show the tick - setTimeout(() => { - setIconState('tick'); - - // After 2 seconds, return to copy icon - setTimeout(() => { - setIconState('copy'); - }, 2000); - }, 200); // slight pause before tick appears - }); - }; - - const handleFrameworkSelection = (newFramework: Framework) => { - handleFrameworkChange(newFramework); - setIsHovering(false); - setIsHiding(true); - }; - - const handleMouseEnter = (e) => { - // Only show overlay if not hovering over copy button - if (copyButtonRef.current && !copyButtonRef.current.contains(e.target)) { - // Clear any existing timeout - if (overlayTimerRef.current) { - clearTimeout(overlayTimerRef.current); - } - setIsHiding(false); - setIsHovering(true); - } - }; - - const handleMouseLeave = () => { - // Set a timeout to allow moving between icon and dropdown - overlayTimerRef.current = setTimeout(() => { - if (containerRef.current && !containerRef.current.matches(':hover')) { - setIsHiding(true); - // After animation completes, set hovering to false - setTimeout(() => { - setIsHovering(false); - setIsHiding(false); - }, 150); // match animation duration - } - }, 100); - }; - - const CurrentIcon = FRAMEWORK_CONFIGS[framework].Icon; - const installCommand = FRAMEWORK_CONFIGS[framework].command; - - return ( -
- -
- -
{' '} - - $ - {installCommand} - -
- - - {iconState === 'copy' && ( - - )} - {iconState === 'animating' &&
} - {iconState === 'tick' && ( - - )} -
- - {isHovering && ( -
{ - if (overlayTimerRef.current) { - clearTimeout(overlayTimerRef.current); - } - setIsHiding(false); - }} - onMouseLeave={handleMouseLeave} - > - {Object.keys(FRAMEWORK_CONFIGS).map((fw: Framework) => { - const FrameworkIcon = FRAMEWORK_CONFIGS[fw].Icon; - const name = FRAMEWORK_CONFIGS[fw].name; - const isCurrentFramework = framework === fw; - return ( -
!isCurrentFramework && handleFrameworkSelection(fw)} - > - - {name} -
- ); - })} -
- )} -
- ); -}; - -export default InstallText; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallTextReact.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallTextReact.tsx deleted file mode 100644 index dd9a8afc46d..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallTextReact.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Icon } from '@ag-website-shared/components/icon/Icon'; -import { useRef, useState } from 'react'; - -import styles from './InstallTextReact.module.scss'; - -const InstallText = () => { - const [isCopied, setIsCopied] = useState(false); - const installTextRef = useRef(null); - const copyToClipboard = () => { - const text = installTextRef?.current?.innerText?.replace('$', ''); - navigator.clipboard.writeText(text).then(() => { - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }); - }; - - return ( - <> - - $ npm install ag-grid-react - - - - - - ); -}; - -export default InstallText; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/FeaturesSection.module.scss b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/FeaturesSection.module.scss deleted file mode 100644 index 0a404355e40..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/FeaturesSection.module.scss +++ /dev/null @@ -1,84 +0,0 @@ -@use 'design-system' as *; - -.container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - @media screen and (max-width: $breakpoint-landing-page-medium) { - align-items: unset; - } -} - -.tabContainer { - display: flex; - justify-content: space-between; - gap: $spacing-size-4; - background: var(--color-brand-200); - border-radius: var(--radius-4xl); - padding: $spacing-size-1; - margin-bottom: $spacing-size-12; - width: auto; - - #{$selector-darkmode} & { - background: var(--color-bg-secondary); - } -} - -.tab { - width: 150px; - height: 50px; - color: var(--color-fg-primary); - border-radius: var(--radius-4xl); - border: none; - appearance: none; - background-color: transparent; - box-shadow: none; - font-weight: var(--text-regular); -} - -.activeTab { - width: 150px; - height: 50px; - border-radius: var(--radius-4xl); - background: white; - color: var(--color-brand-600); - border: none; - box-shadow: var(--shadow-lg); - - #{$selector-darkmode} & { - background-color: var(--color-fg-secondary); - } -} - -.buttonContainer { - width: 100%; - display: flex; - justify-content: center; - gap: $spacing-size-2; -} - -.featureNavButton { - appearance: none; - padding: 0; - text-align: left; - font-weight: var(--text-regular); - border: none; - background-color: transparent; - box-shadow: none; -} - -.featureNavIcon { - --icon-color: var(--color-fg-secondary); - - cursor: pointer; - - &:hover { - --icon-color: var(--color-link-hover); - } -} - -.featureNavIconDisabled { - fill: var(--color-fg-disabled); -} diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/FeaturesSection.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/FeaturesSection.tsx deleted file mode 100644 index f2edc7b7e98..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/FeaturesSection.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Icon } from '@ag-website-shared/components/icon/Icon'; -import GithubSlugger from 'github-slugger'; -import React from 'react'; - -import styles from './FeaturesSection.module.scss'; -import AdvancedFeatures from './tabs/advancedfeatures/AdvancedFeatures'; -import BasicFeatures from './tabs/basicfeatures/BasicFeatures'; -import CustomFeatures from './tabs/customfeatures/CustomFeatures'; - -const FeaturesSection: React.FC = () => { - const slugger = new GithubSlugger(); - const [activeTab, setActiveTab] = React.useState(0); - const tabs = [ - { title: 'Build', component: }, - { title: 'Customise', component: }, - { title: 'Expand', component: }, - ]; - - const handleTabClick = (index: number) => { - if (index >= tabs.length) { - setActiveTab(0); // Loop to the first tab if the index exceeds the last tab - } else if (index < 0) { - setActiveTab(tabs.length - 1); // Loop to the last tab if the index is below the first tab - } else { - setActiveTab(index); - } - }; - - return ( - <> -
-
- {tabs.map((tab, index) => ( - - ))} -
-
{tabs[activeTab]?.component}
-
- handleTabClick(activeTab - 1)} - onMouseDown={(e) => e.preventDefault()} - role="button" - className="icon-button" - > - - - - handleTabClick(activeTab + 1)} - onMouseDown={(e) => e.preventDefault()} - role="button" - className="icon-button" - > - - -
-
- - ); -}; - -export default FeaturesSection; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/FeatureTabs.module.scss b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/FeatureTabs.module.scss deleted file mode 100644 index 28470472c6a..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/FeatureTabs.module.scss +++ /dev/null @@ -1,47 +0,0 @@ -@use 'design-system' as *; - -.columnContainer { - display: flex; - justify-content: space-between; - - @media screen and (max-width: $breakpoint-landing-page-medium) { - flex-direction: column; - } -} - -.column { - width: var(--layout-width-2-4); - - @media screen and (max-width: $breakpoint-landing-page-medium) { - width: 100%; - } -} - -.featureContainer { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: $spacing-size-4; - margin-bottom: $spacing-size-4; -} - -.title { - font-size: var(--text-fs-2xl); -} - -.feature { - display: flex; - flex-direction: column; -} - -.featureHeading { - display: flex; - flex-grow: 1; - font-size: var(--text-xl); - margin-bottom: $spacing-size-2; -} - -.featureDetail { - font-size: var(--text-fs-md); - margin-bottom: $spacing-size-2; -} diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/advancedfeatures/AdvancedFeatures.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/advancedfeatures/AdvancedFeatures.tsx deleted file mode 100644 index 42cb18a1254..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/advancedfeatures/AdvancedFeatures.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Snippet } from '@ag-website-shared/components/snippet/Snippet'; -import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; -import React from 'react'; - -import styles from '../FeatureTabs.module.scss'; - -const AdvancedFeatures: React.FC = () => { - const codeExample = `const [colDefs, setColDefs] = useState[]>([ - { field: "make", pivot: true, rowGroup: true, aggFunc: 'sum' }, -]); - -`; - - return ( -
-
-
-

Expand

-
-
Advanced Features
- - Build charts directly - from your React Table. Perform data analysis with{' '} - Row Grouping,{' '} - Pivoting and{' '} - Master/Detail features. - Access all features from our{' '} - Accessory Panels. - -
-
-
Dedicated Support
- - Access dedicated support via{' '} - Zendesk, monitored by - our support teams 365 days a year, to help build your perfect React Table. - -
-
-
AG Charts
- - Purchase a discounted bundle licence to access all of the advanced features and additional - series types available in{' '} - AG Charts Enterprise. - -
-
-
-
- -
-
- ); -}; - -export default AdvancedFeatures; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/basicfeatures/BasicFeatures.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/basicfeatures/BasicFeatures.tsx deleted file mode 100644 index 9fe87ec120e..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/basicfeatures/BasicFeatures.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Snippet } from '@ag-website-shared/components/snippet/Snippet'; -import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; -import React from 'react'; - -import styles from '../FeatureTabs.module.scss'; - -const BasicFeatures: React.FC = () => { - const codeExample = `const GridExample = () => { - const [rowData, setRowData] = getRowDataJson(); - const [colDefs, setColDefs] = useState[]>([ - { field: "make" }, - { field: "model" }, - { field: "price" } - ]); - - return ( -
- -
- ); -} -`; - - return ( -
-
-
-

Build

-
-
Get Started in Minutes
- - Add a React Table with less than 15 lines of code. Just add your data, and define your - column structure. View the{' '} - Quick Start to learn - more. - -
-
-
Handle Millions of Cells
- - Easily handle millions of rows with our{' '} - - Client-Side Row Model - {' '} - or upgrade to enterprise for{' '} - Infinite Scrolling{' '} - with our{' '} - Server-Side Row Model. - -
-
-
100s of Features
- - Enable complex features with single properties, including:{' '} - Sorting,{' '} - Filtering,{' '} - Cell Editing,{' '} - CSV Export,{' '} - Pagination,{' '} - Row Selection, and{' '} - Accessibility. - -
-
-
-
- -
-
- ); -}; - -export default BasicFeatures; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/customfeatures/CustomFeatures.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/customfeatures/CustomFeatures.tsx deleted file mode 100644 index 29d5d600de1..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/features/tabs/customfeatures/CustomFeatures.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Snippet } from '@ag-website-shared/components/snippet/Snippet'; -import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; -import React from 'react'; - -import styles from '../FeatureTabs.module.scss'; - -const CustomFeatures: React.FC = () => { - const codeExample = `import { themeQuartz } from "ag-grid-community"; // or themeBalham, themeAlpine - -const myTheme = themeQuartz - // Customise Theme Parameters - .withParams({ - spacing: 2, - foregroundColor: 'rgb(14, 68, 145)', - }) - // Use Material Icons - .withPart(iconSetMaterial); - -return ( - -) -`; - - return ( -
-
-
-

Customise

-
-
Flexible Theming API
- - Customise our Built-in Themes with the{' '} - Theming API. Define a{' '} - Color Scheme, modify{' '} - Theme Parameters, mix and match{' '} - Theme Parts, and use{' '} - CSS for unlimited control. - -
-
-
Powerful Theming Tools
- - Use our Theme Builder to create - ready-to-use custom themes which can be imported into your app, or build them from scratch - with our{' '} - Figma Design System - . - -
-
-
Custom Components
- - Override the default rendering of any part of the grid with your own{' '} - - Custom React Components - - . Add buttons to cells, define your own filtering logic, and add custom functionality. - -
-
-
-
- -
-
- ); -}; - -export default CustomFeatures; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/utils/CustomerLogos.module.scss b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/utils/CustomerLogos.module.scss deleted file mode 100644 index 9ba964bc306..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/utils/CustomerLogos.module.scss +++ /dev/null @@ -1,55 +0,0 @@ -@use 'design-system' as *; - -.customerLogos { - --overflow-width: 10vw; - - position: relative; - width: calc(100% + var(--overflow-width) * 2); - height: 107px; - margin-left: calc(var(--overflow-width) * -1); - // NOTE: This image is also prefetched on the page - background-image: url(urlWithBaseUrl('/images/ag-grid-customer-logos.webp')); - background-size: auto 107px; - animation-name: customerMarquee; - animation-duration: 1600s; - animation-timing-function: linear; - animation-iteration-count: infinite; - - @media screen and (min-width: $breakpoint-grid-homepage-medium) { - animation-duration: 400s; - } - - &::before, - &::after { - content: ''; - position: absolute; - height: 100%; - width: var(--overflow-width); - } - - &::before { - left: 0; - background-image: linear-gradient(90deg, transparent, var(--color-util-gray-900)); - } - - &::after { - right: 0; - background-image: linear-gradient(90deg, var(--color-util-gray-900), transparent); - } - - @media screen and (max-width: $breakpoint-hero-small) { - margin: 0 auto; - width: 100%; - animation-duration: 1100s; - } -} - -@keyframes customerMarquee { - from { - background-position-x: 0%; - } - - to { - background-position-x: 5000%; - } -} diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/utils/CustomerLogos.tsx b/documentation/ag-grid-docs/src/components/framework-landing-pages/react/utils/CustomerLogos.tsx deleted file mode 100644 index 354af1619da..00000000000 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/utils/CustomerLogos.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -import styles from './CustomerLogos.module.scss'; - -const CustomerLogos: React.FC = () => { - return ( -
-
-
- ); -}; - -export default CustomerLogos; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallTextReact.module.scss b/documentation/ag-grid-docs/src/components/install-text/InstallText.module.scss similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/InstallTextReact.module.scss rename to documentation/ag-grid-docs/src/components/install-text/InstallText.module.scss diff --git a/documentation/ag-grid-docs/src/components/landing-pages/LandingPage.astro b/documentation/ag-grid-docs/src/components/landing-pages/LandingPage.astro new file mode 100644 index 00000000000..833f8ac84c4 --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/LandingPage.astro @@ -0,0 +1,307 @@ +--- +/** + * LandingPage.astro - Content-driven landing page template + * + * This component renders a landing page based on a JSON content definition. + * Each section in the content.sections array is rendered in order. + * + * Usage: + * ```astro + * --- + * import LandingPage from '@components/landing-pages/LandingPage.astro'; + * import { getEntry } from 'astro:content'; + * + * const { data: content } = await getEntry('landingPages', 'react-data-grid'); + * const { data: versions } = await getEntry('versions', 'ag-grid-versions'); + * --- + * + * ``` + */ + +import type { + LandingPageContent, + HeroSection as HeroSectionType, +} from '@ag-website-shared/components/landing-pages/types'; +import Layout from '@layouts/Layout.astro'; +import frameworkStyles from '@pages-styles/framework-landing-page.module.scss'; +import enterpriseStyles from '@pages-styles/enterprise.module.scss'; +import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; +import { getPageImages } from '@components/docs/utils/filesData'; + +// Shared section components from @ag-website-shared +import HeroSection from '@ag-website-shared/components/landing-pages/sections/HeroSection.astro'; +import FeaturesSection from '@ag-website-shared/components/landing-pages/sections/FeaturesSection.astro'; +import ShowcaseSection from '@ag-website-shared/components/landing-pages/sections/ShowcaseSection.astro'; +import CustomersSection from '@ag-website-shared/components/landing-pages/sections/CustomersSection.astro'; +import ExamplesSection from '@ag-website-shared/components/landing-pages/sections/ExamplesSection.astro'; +import FAQSection from '@ag-website-shared/components/landing-pages/sections/FAQSection.astro'; +import ContactSection from '@ag-website-shared/components/landing-pages/sections/ContactSection.astro'; +import { LandingPageSection } from '@ag-website-shared/components/landing-pages/LandingPageSection'; +import { Video } from '@ag-website-shared/components/video/Video'; +import { InstallText } from '@ag-website-shared/components/install-text'; +import { AutomatedIntegratedCharts } from '@ag-website-shared/components/automated-examples/AutomatedIntegratedCharts'; +import { TrialButton } from '@ag-website-shared/components/trial-licence-modal/TrialButton'; +import { EnterpriseTrial } from '@ag-website-shared/components/license-pricing/EnterpriseTrial'; + +// Site-specific components +import { getHeadingWithLogo } from '@utils/framework-landing-page-utils'; +import { Finance } from '@components/demos/examples/finance'; +import { CustomerLogos } from '@components/customer-logos/CustomerLogos'; +import Showcase from '@components/showcase/Showcase'; +import Customers from '@components/customers/Customers'; +import FeaturesWithExamples from './sections/features-with-examples/FeaturesWithExamples.astro'; +import ComparisonTable from './sections/comparison/ComparisonTable'; + +interface Props { + content: LandingPageContent; + versions?: Array<{ + version: string; + landingPageHighlight?: string; + }>; +} + +const { content, versions } = Astro.props; +const latestVersion = versions?.find((v) => v.landingPageHighlight); + +// Determine if this is an enterprise page based on hero variant +const heroSection = content.sections.find((s) => s.type === 'hero') as HeroSectionType | undefined; +const isEnterprise = heroSection?.variant === 'enterprise'; +const styles = isEnterprise ? enterpriseStyles : frameworkStyles; + +// For framework pages, add logo to headings (only for specific framework landing pages) +const frameworkLandingPageSlugs = ['react-data-grid', 'javascript-data-grid', 'vue-data-grid', 'angular-data-grid']; +const isFrameworkLandingPage = + !isEnterprise && content.framework && frameworkLandingPageSlugs.includes(content.docsPath); +const frameworkKey = isFrameworkLandingPage ? content.framework || '' : ''; + +// Get theme builder video for enterprise pages +const themeBuilderVideo = isEnterprise + ? await getPageImages({ + pageName: 'key-features', + imagePath: 'resources/theme-builder-demo.mp4', + }) + : null; +--- + + + { + content.sections.map((section) => { + switch (section.type) { + case 'hero': + // Unified hero section for both enterprise and framework variants + // All content comes from JSON - no hardcoded text + // Add logo to heading for framework landing pages only + const heroSectionWithLogo = + isFrameworkLandingPage && frameworkKey + ? { + ...section, + headingHtml: getHeadingWithLogo(frameworkKey, section.heading, urlWithBaseUrl), + } + : section; + return ( + + + + {section.primaryCta?.useTrialButton && ( + + {section.primaryCta.text} + + )} + + + ); + + case 'features': + // Add logo to heading for framework landing pages only + const featuresSectionWithLogo = + isFrameworkLandingPage && frameworkKey + ? { + ...section, + headingHtml: getHeadingWithLogo( + frameworkKey, + section.heading || 'Build, Customise and Expand your Data Grid', + urlWithBaseUrl + ), + } + : section; + return ( + + + + ); + + case 'showcase': + return ( + + + + ); + + case 'customers': + return ( + + + + ); + + case 'examples': + return ( + + ); + + case 'faq': + return ; + + case 'contact': + return ( + + + + ); + + case 'integrated-charts': + return ( + +
+ +
+
+ ); + + case 'theme-builder': + return ( + +
+ {themeBuilderVideo && ( +
+
+ )} + + Open Theme Builder + +
+
+ ); + + case 'comparison': + return ( + + + + ); + + case 'pricing': + return ( + +
+ {section.cards.map((card) => ( +
+

{card.title}

+
+ {card.price} +
+

{card.priceNote}

+
    + {card.features.map((feature) => ( +
  • {feature}
  • + ))} +
+ + {card.ctaText} + + {card.showTrialButton && ( + + Free Trial + + )} +
+ ))} +
+
+ ); + + default: + // Unknown section type - skip + return null; + } + }) + } +
diff --git a/documentation/ag-grid-docs/src/components/landing-pages/sections/comparison/ComparisonTable.module.scss b/documentation/ag-grid-docs/src/components/landing-pages/sections/comparison/ComparisonTable.module.scss new file mode 100644 index 00000000000..3fb887c2b38 --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/sections/comparison/ComparisonTable.module.scss @@ -0,0 +1,327 @@ +@use 'design-system' as *; + +.container { + display: flex; + flex-direction: column; + gap: $spacing-size-6; + width: 100%; +} + +.tableWrapper { + display: flex; + flex-direction: column; + border-radius: var(--radius-lg); + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); + overflow: hidden; +} + +.tableHeader { + display: flex; + align-items: stretch; + border-bottom: 1px solid var(--color-border-primary); +} + +.headerCellFeature { + flex: 1; + min-width: 200px; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + min-width: 140px; + } +} + +.headerCellCommunity, +.headerCellEnterprise { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: $spacing-size-2; + padding: $spacing-size-4; + width: 140px; + text-align: center; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + width: 100px; + padding: $spacing-size-3 $spacing-size-2; + } +} + +.headerCellEnterprise { + // No background - matches Community column +} + +.headerTitle { + font-size: var(--text-fs-lg); + font-weight: var(--text-bold); + line-height: var(--text-lh-tight); + color: var(--color-fg-primary); + + @media screen and (max-width: $breakpoint-landing-page-medium) { + font-size: var(--text-fs-md); + } +} + +.headerCellEnterprise .headerTitle { + color: var(--color-brand-600); + + #{$selector-darkmode} & { + color: var(--color-brand-400); + } +} + +.headerSubtitle { + font-size: var(--text-fs-xs); + color: var(--color-fg-secondary); + margin-top: 2px; +} + +.tableBody { + display: flex; + flex-direction: column; +} + +.categoryRow { + display: flex; + padding: $spacing-size-3 $spacing-size-4; + background: var(--color-bg-tertiary); + border-top: 1px solid var(--color-border-secondary); + + &:first-child { + border-top: none; + } + + span { + font-weight: var(--text-bold); + font-size: var(--text-fs-sm); + color: var(--color-fg-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + } +} + +.featureRow { + display: flex; + align-items: center; + min-height: $spacing-size-10; + border-top: 1px solid var(--color-border-secondary); + transition: background-color 0.15s ease; + + &:hover { + background: color-mix(in srgb, var(--color-bg-secondary), var(--color-bg-primary) 50%); + } +} + +.featureNameCell { + flex: 1; + padding: $spacing-size-3 $spacing-size-4; + padding-left: $spacing-size-6; + font-size: var(--text-fs-md); + color: var(--color-fg-primary); + min-width: 200px; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + min-width: 140px; + padding-left: $spacing-size-4; + font-size: var(--text-fs-sm); + } + + a { + color: var(--color-link); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } +} + +.communityCell, +.enterpriseCell { + display: flex; + align-items: center; + justify-content: center; + padding: $spacing-size-2; + width: 140px; + text-align: center; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + width: 100px; + } +} + +.enterpriseCell { + background: color-mix(in srgb, var(--color-brand-50), var(--color-bg-primary) 70%); + border-left: 1px solid var(--color-border-secondary); + + #{$selector-darkmode} & { + background: color-mix(in srgb, var(--color-brand-900), var(--color-bg-primary) 90%); + } +} + +.tick { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.tickIcon { + width: 20px !important; + height: 20px !important; + padding: 3px; + border-radius: 20px; + fill: var(--color-util-brand-700) !important; + background: color-mix(in srgb, var(--color-util-brand-700), var(--color-bg-primary) 92%); +} + +.dash { + color: var(--color-fg-tertiary); + font-size: var(--text-fs-lg); + opacity: 0.4; +} + +.expandedSection { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.3s ease-out; + + &.expanded { + grid-template-rows: 1fr; + } +} + +.expandedContent { + overflow: hidden; +} + +.expandButton { + display: flex; + align-items: center; + justify-content: center; + gap: $spacing-size-2; + width: 100%; + padding: $spacing-size-3 $spacing-size-4; + background: var(--color-bg-secondary); + border: none; + border-top: 1px solid var(--color-border-secondary); + cursor: pointer; + font-size: var(--text-fs-sm); + font-weight: var(--text-medium); + color: var(--color-link); + transition: + background-color 0.15s ease, + color 0.15s ease; + + &:hover { + background: color-mix(in srgb, var(--color-bg-tertiary), var(--color-bg-secondary) 50%); + color: var(--color-link); + } + + &.expanded .expandIcon { + transform: rotate(180deg); + } +} + +.expandIcon { + --icon-size: 20px; + + transition: transform 0.3s ease; + fill: var(--color-link) !important; +} + +.ctaContainer { + display: flex; + justify-content: center; + gap: $spacing-size-4; + margin-top: $spacing-size-2; + + @media screen and (max-width: $breakpoint-landing-page-small) { + flex-direction: column; + } +} + +.ctaButton { + padding: $spacing-size-3 $spacing-size-6; +} + +.ctaButtonSecondary { + padding: $spacing-size-3 $spacing-size-6; +} + +// Subgroup styles +.groupSection { + // Container for group heading and its features +} + +.subGroup { + border-top: 1px solid var(--color-border-secondary); + + &.isOpen .subGroupHeader .subGroupIconWrapper svg { + transform: rotate(180deg); + } + + &.isOpen .subGroupHeader .communityCell, + &.isOpen .subGroupHeader .enterpriseCell { + opacity: 0.3; + } +} + +.subGroupHeader { + display: flex; + align-items: center; + min-height: $spacing-size-10; + cursor: pointer; + transition: background-color 0.15s ease; + + &:hover { + background: color-mix(in srgb, var(--color-bg-secondary), var(--color-bg-primary) 50%); + } +} + +.subGroupTitle { + display: flex; + align-items: center; + font-weight: var(--text-semibold); + color: var(--color-link); +} + +.subGroupIconWrapper { + --icon-size: 16px; + + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + margin-left: $spacing-size-2; + border-radius: var(--radius-md); + border: 1px solid color-mix(in srgb, var(--color-link), var(--color-bg-primary) 80%); + transition: + border-color 0.15s ease, + box-shadow 0.15s ease; + + svg { + transition: transform 0.2s ease; + fill: var(--color-link) !important; + } + + &:hover { + border-color: color-mix(in srgb, var(--color-link), var(--color-bg-primary) 40%); + box-shadow: var(--shadow-sm); + } +} + +.subGroupContent { + .featureRow { + border-top: 1px solid var(--color-border-secondary); + } + + .featureNameCell { + padding-left: $spacing-size-10; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + padding-left: $spacing-size-8; + } + } +} diff --git a/documentation/ag-grid-docs/src/components/landing-pages/sections/comparison/ComparisonTable.tsx b/documentation/ag-grid-docs/src/components/landing-pages/sections/comparison/ComparisonTable.tsx new file mode 100644 index 00000000000..0b1e523ba11 --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/sections/comparison/ComparisonTable.tsx @@ -0,0 +1,227 @@ +import { Collapsible } from '@ag-website-shared/components/collapsible/Collapsible'; +import { Icon } from '@ag-website-shared/components/icon/Icon'; +import { TrialButton } from '@ag-website-shared/components/trial-licence-modal/TrialButton'; +import gridFeaturesData from '@ag-website-shared/content/license-features/gridFeaturesMatrix.json'; +import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; +import classnames from 'classnames'; +import React, { useState } from 'react'; + +import styles from './ComparisonTable.module.scss'; + +const PREVIEW_GROUPS = 3; // Show first 3 groups in preview + +interface FeatureValue { + value?: boolean; + detail?: string; +} + +interface LabelValue { + name: string; + link?: string; +} + +interface FeatureItem { + label?: LabelValue; + name?: string; + community: boolean | FeatureValue; + enterprise: boolean | FeatureValue; + chartsGrid: boolean | FeatureValue; + isSubGroup?: boolean; + items?: FeatureItem[]; +} + +interface FeatureGroup { + group: { + name: string; + }; + items: FeatureItem[]; +} + +const getFeatureValue = (value: boolean | FeatureValue): boolean => { + if (typeof value === 'boolean') { + return value; + } + return value?.value ?? false; +}; + +const FeatureIcon: React.FC<{ value: boolean | FeatureValue }> = ({ value }) => { + const boolValue = getFeatureValue(value); + if (boolValue) { + return ( + + + + ); + } + return ; +}; + +const FeatureLabel: React.FC<{ item: FeatureItem }> = ({ item }) => { + const name = item.label?.name || item.name || ''; + const link = item.label?.link; + + if (link) { + return ( + + {name} + + ); + } + return <>{name}; +}; + +const SubGroupRow: React.FC<{ item: FeatureItem; index: number }> = ({ item, index }) => { + const [isOpen, setIsOpen] = useState(false); + + const allCommunity = item.items?.every((subItem) => getFeatureValue(subItem.community)) ?? false; + + return ( +
+
setIsOpen(!isOpen)}> +
+ + {item.name} + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+ {item.items?.map((subItem, subIndex) => ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ))} +
+
+
+ ); +}; + +const FeatureRow: React.FC<{ item: FeatureItem; index: number }> = ({ item, index }) => { + if (item.isSubGroup) { + return ; + } + + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ); +}; + +const GroupSection: React.FC<{ group: FeatureGroup }> = ({ group }) => ( +
+
+ {group.group.name} +
+ {group.items.map((item, index) => ( + + ))} +
+); + +const ComparisonTable: React.FC = () => { + const [isExpanded, setIsExpanded] = useState(false); + + const data = gridFeaturesData as FeatureGroup[]; + const previewGroups = data.slice(0, PREVIEW_GROUPS); + const expandedGroups = data.slice(PREVIEW_GROUPS); + const hasMoreGroups = expandedGroups.length > 0; + + return ( +
+
+
+
+
+ Community + Free Forever +
+
+ Enterprise + $999/dev +
+
+ Enterprise Bundle + $1,498/dev +
+
+
+ {previewGroups.map((group, index) => ( + + ))} + + {hasMoreGroups && ( +
+
+ {expandedGroups.map((group, index) => ( + + ))} +
+
+ )} +
+ + {hasMoreGroups && ( + + )} +
+
+ + View Full Pricing + + + Start Free Trial + +
+
+ ); +}; + +export default ComparisonTable; diff --git a/documentation/ag-grid-docs/src/components/landing-pages/sections/example-runner/ExampleRunner.astro b/documentation/ag-grid-docs/src/components/landing-pages/sections/example-runner/ExampleRunner.astro new file mode 100644 index 00000000000..f3929fddcd1 --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/sections/example-runner/ExampleRunner.astro @@ -0,0 +1,61 @@ +--- +/** + * ExampleRunner for landing pages + * + * A wrapper around DocsExampleRunner that accepts pageName as a prop + * instead of deriving it from the URL. + */ +import type { InternalFramework } from '@ag-grid-types'; +import ExampleRunnerContainer from '@components/example-runner/components/ExampleRunnerContainer.astro'; +import { getIsDev } from '@utils/env'; +import { DocsExampleRunner } from '@components/docs/components/DocsExampleRunner'; +import { DISABLE_EXAMPLE_RUNNER } from '@constants'; +import { getGeneratedContents, type GeneratedExampleParams } from '@components/example-generator'; + +interface Props { + title: string; + name: string; + pageName: string; + exampleHeight?: number; + framework?: InternalFramework; +} + +const { title, name, pageName, exampleHeight = 450, framework } = Astro.props; +const isDev = getIsDev(); + +const generatedExampleParams: GeneratedExampleParams = { + type: 'docs', + framework: framework || 'reactFunctionalTs', + pageName, + exampleName: name, +}; + +let hasExampleConsoleLog = false; +try { + const contents = await getGeneratedContents(generatedExampleParams); + hasExampleConsoleLog = Boolean(contents?.hasExampleConsoleLog); +} catch (error) { + console.error('Error generating contents for:', generatedExampleParams); +} +--- + +{ + !DISABLE_EXAMPLE_RUNNER && ( + + + + ) +} diff --git a/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamples.astro b/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamples.astro new file mode 100644 index 00000000000..0e5cafd5d1b --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamples.astro @@ -0,0 +1,111 @@ +--- +import type { InternalFramework } from '@ag-grid-types'; +import ExampleRunner from '../example-runner/ExampleRunner.astro'; +import { FeaturesWithExamplesContent, type FeatureConfig } from './FeaturesWithExamplesContent'; +import styles from './FeaturesWithExamples.module.scss'; + +interface Props { + features: FeatureConfig[]; + framework: InternalFramework; +} + +const { features, framework } = Astro.props; +--- + +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ { + features.map((feature, index) => ( +
+ +
+ )) + } +
+
+
+ + + + diff --git a/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamples.module.scss b/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamples.module.scss new file mode 100644 index 00000000000..be1471ccc3b --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamples.module.scss @@ -0,0 +1,216 @@ +@use 'design-system' as *; + +.container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + align-items: unset; + } +} + +.tabContainer { + display: flex; + justify-content: flex-start; + gap: $spacing-size-2; + margin-bottom: $spacing-size-8; + width: 100%; + border-bottom: 1px solid var(--color-border-secondary); + + @media screen and (max-width: $breakpoint-landing-page-medium) { + flex-wrap: wrap; + justify-content: flex-start; + } +} + +.tab { + // Reset default button styling from style guide + --icon-size: unset; + --icon-color: unset; + padding: $spacing-size-2 $spacing-size-4; + min-width: 120px; + height: 44px; + color: var(--color-fg-secondary); + opacity: 0.6; + border: none; + border-bottom: 2px solid transparent; + appearance: none; + background-color: transparent; + box-shadow: none; + font-weight: var(--text-semibold); + font-size: 16px; + white-space: nowrap; + border-radius: 0; + cursor: pointer; + outline: none; + transition: + color 0.3s ease, + border-bottom-color 0.3s ease, + opacity 0.3s ease; + + &:hover { + background: transparent; + color: var(--color-fg-primary); + border-bottom-color: var(--color-border-secondary); + opacity: 0.8; + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + min-width: auto; + padding: $spacing-size-2 $spacing-size-3; + } +} + +.activeTab { + // Reset default button styling from style guide + --icon-size: unset; + --icon-color: unset; + padding: $spacing-size-2 $spacing-size-4; + min-width: 120px; + height: 44px; + color: var(--color-brand-500); + opacity: 1; + border: none; + border-bottom: 2px solid var(--color-brand-500); + appearance: none; + background-color: var(--color-bg-primary) !important; + box-shadow: none; + font-weight: var(--text-semibold); + font-size: 16px; + white-space: nowrap; + border-radius: 0; + cursor: pointer; + outline: none; + transition: border-bottom-color 0.3s ease; + + #{$selector-darkmode} & { + color: var(--color-link); + border-bottom-color: var(--color-link); + } + + &:hover { + background-color: var(--color-bg-primary) !important; + color: var(--color-brand-500) !important; + + #{$selector-darkmode} & { + color: var(--color-link) !important; + } + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + min-width: auto; + padding: $spacing-size-2 $spacing-size-3; + } +} + +.contentContainer { + width: 100%; +} + +.columnContainer { + display: flex; + justify-content: space-between; + gap: $spacing-size-8; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + flex-direction: column; + } +} + +.column { + width: var(--layout-width-2-4); + + @media screen and (max-width: $breakpoint-landing-page-medium) { + width: 100%; + } +} + +.featureContainer { + display: flex; + flex-direction: column; + gap: $spacing-size-4; + margin-bottom: $spacing-size-4; +} + +.title { + font-size: var(--text-fs-2xl); + font-weight: var(--text-bold); + color: var(--color-fg-primary); + margin: 0 0 $spacing-size-4 0; +} + +.feature { + display: flex; + flex-direction: column; +} + +.featureHeading { + display: flex; + flex-grow: 1; + font-size: var(--text-fs-lg); + font-weight: var(--text-semibold); + margin-bottom: $spacing-size-2; + color: var(--color-fg-primary); +} + +.featureDetail { + font-size: var(--text-fs-md); + margin-bottom: $spacing-size-2; + color: var(--color-fg-secondary); + line-height: 1.5; + + a { + color: var(--color-link); + + &:hover { + text-decoration: underline; + } + } +} + +.docsLink { + display: inline-flex; + align-items: center; + gap: $spacing-size-2; + margin-top: $spacing-size-4; + color: var(--color-link); + font-weight: var(--text-medium); + font-size: var(--text-fs-md); + text-decoration: none; + transition: color 0.2s ease; + + &:hover { + color: var(--color-link-hover); + text-decoration: underline; + } +} + +.buttonContainer { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: $spacing-size-4; + margin-top: $spacing-size-6; +} + +.trialCta { + flex-shrink: 0; +} + +.navButtons { + display: flex; + gap: $spacing-size-2; +} + +.featureNavIcon { + --icon-color: var(--color-fg-secondary); + + cursor: pointer; + + &:hover { + --icon-color: var(--color-link-hover); + } +} diff --git a/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamplesContent.tsx b/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamplesContent.tsx new file mode 100644 index 00000000000..fe942d61f10 --- /dev/null +++ b/documentation/ag-grid-docs/src/components/landing-pages/sections/features-with-examples/FeaturesWithExamplesContent.tsx @@ -0,0 +1,140 @@ +import type { InternalFramework } from '@ag-grid-types'; +import { TrialButton } from '@ag-website-shared/components/trial-licence-modal/TrialButton'; +import { getFrameworkFromInternalFramework } from '@utils/framework'; +import { useSyncFrameworkStoreState } from '@utils/hooks/useSyncFrameworkStoreState'; +import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; +import React, { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import styles from './FeaturesWithExamples.module.scss'; + +export interface FeatureItem { + heading: string; + detail: string; + link?: string; +} + +export interface FeatureConfig { + id: string; + title: string; + isEnterprise?: boolean; + example: { + pageName: string; + exampleName: string; + }; + features: FeatureItem[]; + docsLink: string; +} + +interface FeaturesWithExamplesContentProps { + features: FeatureConfig[]; + framework: InternalFramework; +} + +export const FeaturesWithExamplesContent: React.FC = ({ features, framework }) => { + const [activeTab, setActiveTab] = useState(0); + const [contentTarget, setContentTarget] = useState(null); + + // Sync framework store state so example runners can load correct framework + const frameworkForStore = getFrameworkFromInternalFramework(framework); + useSyncFrameworkStoreState(frameworkForStore); + + // Find the content target element for the portal + useEffect(() => { + const target = document.getElementById('landing-page-features-content-target'); + if (target) { + setContentTarget(target); + } + }, []); + + const handleTabClick = (index: number) => { + let newIndex = index; + if (index >= features.length) { + newIndex = 0; + } else if (index < 0) { + newIndex = features.length - 1; + } + + setActiveTab(newIndex); + + // Dispatch custom event to notify Astro about tab change + const event = new CustomEvent('landing-page-feature-tab-change', { + detail: { + index: newIndex, + featureId: features[newIndex].id, + }, + }); + document.dispatchEvent(event); + }; + + // Set initial tab on mount + useEffect(() => { + const event = new CustomEvent('landing-page-feature-tab-change', { + detail: { + index: 0, + featureId: features[0]?.id, + }, + }); + document.dispatchEvent(event); + }, [features]); + + const activeFeature = features[activeTab]; + + const featureContent = activeFeature ? ( +
+
+

{activeFeature.title}

+ {activeFeature.features.map((feature, index) => ( +
+
{feature.heading}
+ + {feature.link ? ( + {feature.detail} + ) : ( + feature.detail + )} + +
+ ))} +
+
+ {activeFeature.isEnterprise ? ( + + Start Free Trial + + ) : ( + + Get Started + + )} +
+
+ ) : null; + + return ( + <> +
+ {features.map((feature, index) => ( + + ))} +
+ {contentTarget ? createPortal(featureContent, contentTarget) : featureContent} + + ); +}; + +export default FeaturesWithExamplesContent; diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/Showcase.module.scss b/documentation/ag-grid-docs/src/components/showcase/Showcase.module.scss similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/Showcase.module.scss rename to documentation/ag-grid-docs/src/components/showcase/Showcase.module.scss diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/Showcase.tsx b/documentation/ag-grid-docs/src/components/showcase/Showcase.tsx similarity index 91% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/Showcase.tsx rename to documentation/ag-grid-docs/src/components/showcase/Showcase.tsx index 13362421396..a2f85ce7240 100644 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/Showcase.tsx +++ b/documentation/ag-grid-docs/src/components/showcase/Showcase.tsx @@ -60,7 +60,23 @@ const SHOWCASE_ITEMS = [ }, ]; -const ShowcaseItem: React.FC = ({ title, titleIcon, description, projectName, projectLogo, projectHref }) => { +interface ShowcaseItemProps { + title: string; + titleIcon: React.ReactNode; + description: string; + projectName: string; + projectLogo: React.ReactNode; + projectHref: string; +} + +const ShowcaseItem: React.FC = ({ + title, + titleIcon, + description, + projectName, + projectLogo, + projectHref, +}) => { return (
diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/aerospace.svg b/documentation/ag-grid-docs/src/components/showcase/images/aerospace.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/aerospace.svg rename to documentation/ag-grid-docs/src/components/showcase/images/aerospace.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/ai.svg b/documentation/ag-grid-docs/src/components/showcase/images/ai.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/ai.svg rename to documentation/ag-grid-docs/src/components/showcase/images/ai.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/dashboard.svg b/documentation/ag-grid-docs/src/components/showcase/images/dashboard.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/dashboard.svg rename to documentation/ag-grid-docs/src/components/showcase/images/dashboard.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/finance.svg b/documentation/ag-grid-docs/src/components/showcase/images/finance.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/finance.svg rename to documentation/ag-grid-docs/src/components/showcase/images/finance.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/jpmorgan.svg b/documentation/ag-grid-docs/src/components/showcase/images/jpmorgan.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/jpmorgan.svg rename to documentation/ag-grid-docs/src/components/showcase/images/jpmorgan.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/microsoft.svg b/documentation/ag-grid-docs/src/components/showcase/images/microsoft.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/microsoft.svg rename to documentation/ag-grid-docs/src/components/showcase/images/microsoft.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/model.svg b/documentation/ag-grid-docs/src/components/showcase/images/model.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/model.svg rename to documentation/ag-grid-docs/src/components/showcase/images/model.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/mongodb.svg b/documentation/ag-grid-docs/src/components/showcase/images/mongodb.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/mongodb.svg rename to documentation/ag-grid-docs/src/components/showcase/images/mongodb.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/nasa.svg b/documentation/ag-grid-docs/src/components/showcase/images/nasa.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/nasa.svg rename to documentation/ag-grid-docs/src/components/showcase/images/nasa.svg diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/ra.svg b/documentation/ag-grid-docs/src/components/showcase/images/ra.svg similarity index 100% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/react/sections/showcase/images/ra.svg rename to documentation/ag-grid-docs/src/components/showcase/images/ra.svg diff --git a/documentation/ag-grid-docs/src/content.config.ts b/documentation/ag-grid-docs/src/content.config.ts index bb81bd17c0f..0570eb83dcc 100644 --- a/documentation/ag-grid-docs/src/content.config.ts +++ b/documentation/ag-grid-docs/src/content.config.ts @@ -238,20 +238,6 @@ const seedProjects = defineCollection({ ), }); -const reactLandingPage = defineCollection({ - loader: glob({ base: './src/content/react-landing-page', pattern: 'examples.json' }), - schema: z.array( - z.object({ - title: z.string(), - img: z.string(), - imgAlt: z.string(), - content: z.string(), - docs: z.string(), - demo: z.string(), - }) - ), -}); - const aboutPage = defineCollection({ loader: glob({ base: './src/content/about', pattern: 'about.json' }), schema: z.object({ @@ -286,6 +272,192 @@ const contactResults = defineCollection({ ), }); +// ============================================================================ +// Unified Landing Pages Collection +// All landing page content is defined in a single JSON file per page +// ============================================================================ + +const featureItemSchema = z.object({ + id: z.string(), + title: z.string(), + isEnterprise: z.boolean().optional(), + example: z.object({ + pageName: z.string(), + exampleName: z.string(), + }), + features: z.array( + z.object({ + heading: z.string(), + detail: z.string(), + link: z.string().optional(), + }) + ), + docsLink: z.string(), +}); + +const exampleItemSchema = z.object({ + img: z.string(), + imgAlt: z.string(), + title: z.string(), + content: z.string(), + docs: z.string(), + demo: z.string(), +}); + +const faqItemSchema = z.object({ + question: z.string(), + answer: z.string(), +}); + +const pricingCardSchema = z.object({ + title: z.string(), + price: z.string(), + priceNote: z.string(), + features: z.array(z.string()), + ctaText: z.string(), + ctaUrl: z.string(), + ctaId: z.string().optional(), + isPrimary: z.boolean().optional(), + showTrialButton: z.boolean().optional(), +}); + +const landingPages = defineCollection({ + loader: glob({ base: './src/content/landing-pages', pattern: '*.json' }), + schema: z.object({ + meta: z.object({ + title: z.string(), + description: z.string(), + }), + framework: z.string().optional(), + packageName: z.string().optional(), + docsPath: z.string(), + analyticsPrefix: z.string(), + + sections: z.array( + z.discriminatedUnion('type', [ + // Hero section + z.object({ + type: z.literal('hero'), + variant: z.enum(['default', 'enterprise']).optional(), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + subHeadingHtml: z.string().optional(), + showVersionBadge: z.boolean().optional(), + primaryCta: z + .object({ + text: z.string(), + url: z.string().optional(), + useTrialButton: z.boolean().optional(), + }) + .optional(), + secondaryCta: z + .object({ + text: z.string(), + url: z.string().optional(), + }) + .optional(), + demo: z + .object({ + enableRowGroup: z.boolean().optional(), + gridHeight: z.number().optional(), + }) + .optional(), + }), + + // Features section + z.object({ + type: z.literal('features'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + items: z.array(featureItemSchema), + }), + + // Showcase section + z.object({ + type: z.literal('showcase'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + }), + + // Customers section + z.object({ + type: z.literal('customers'), + tag: z.string(), + headingHtml: z.string(), + subHeadingHtml: z.string(), + displayLogos: z.boolean().optional(), + }), + + // Examples section + z.object({ + type: z.literal('examples'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + showBackgroundGradient: z.boolean().optional(), + items: z.array(exampleItemSchema), + }), + + // FAQ section + z.object({ + type: z.literal('faq'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + items: z.array(faqItemSchema), + }), + + // Contact section + z.object({ + type: z.literal('contact'), + variant: z.enum(['default', 'sales']).optional(), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + features: z.array(z.string()), + }), + + // Enterprise-specific sections + z.object({ + type: z.literal('integrated-charts'), + tag: z.string(), + heading: z.string().optional(), + headingHtml: z.string().optional(), + subHeading: z.string(), + showBackgroundGradient: z.boolean().optional(), + }), + + z.object({ + type: z.literal('theme-builder'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + }), + + z.object({ + type: z.literal('comparison'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + showBackgroundGradient: z.boolean().optional(), + }), + + z.object({ + type: z.literal('pricing'), + tag: z.string(), + heading: z.string(), + subHeading: z.string(), + showBackgroundGradient: z.boolean().optional(), + cards: z.array(pricingCardSchema), + }), + ]) + ), + }), +}); + export const collections = { docs, apiDocumentation, @@ -302,7 +474,7 @@ export const collections = { faqs, siteHeader, seedProjects, - reactLandingPage, aboutPage, contactResults, + landingPages, }; diff --git a/documentation/ag-grid-docs/src/content/faqs/angular-landing-page.json b/documentation/ag-grid-docs/src/content/faqs/angular-landing-page.json new file mode 100644 index 00000000000..c0ab662f5b7 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/faqs/angular-landing-page.json @@ -0,0 +1,42 @@ +[ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create an Angular Data Grid for free?", + "answer": "Yes, you can create an Angular Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my Angular Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between Angular Data Grids and [Angular Charts](https://www.ag-grid.com/charts/angular/quick-start/)." + }, + { + "question": "Can I customise the Angular Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "AG Grid vs. Angular Material Table", + "answer": "AG Grid is a feature-rich data grid offering advanced capabilities like grouping, pivoting, and server-side row model, while Angular Material Table provides a simpler table component as part of its UI library." + }, + { + "question": "Why use AG Grid over PrimeNG Table?", + "answer": "AG Grid offers superior performance with millions of rows, more advanced features like integrated charts and Excel export, and better TypeScript support compared to PrimeNG Table." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or request a trial licence at [info@ag-grid.com](mailto:info@ag-grid.com) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } +] diff --git a/documentation/ag-grid-docs/src/content/faqs/enterprise-landing-page.json b/documentation/ag-grid-docs/src/content/faqs/enterprise-landing-page.json new file mode 100644 index 00000000000..bbfbd9ddfb7 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/faqs/enterprise-landing-page.json @@ -0,0 +1,42 @@ +[ + { + "question": "Can I try AG Grid Enterprise for free?", + "answer": "Yes! You can install and test all AG Grid Enterprise features locally for free. A watermark and console message will appear until you add a licence key. You can also [request a trial licence](/trial-licence/) to remove these during your evaluation period." + }, + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is free and open-source with powerful core features. [AG Grid Enterprise](/license-pricing/) adds advanced capabilities like Row Grouping, Server-Side Row Model, AI Toolkit, Integrated Charts, Master/Detail, and dedicated support." + }, + { + "question": "How much does AG Grid Enterprise cost?", + "answer": "AG Grid Enterprise costs $999 USD per developer for a perpetual licence. This includes 1 year of support and updates. Volume discounts are available. Visit the [Pricing page](/license-pricing/) for full details." + }, + { + "question": "What is included in the Enterprise Bundle?", + "answer": "The Enterprise Bundle includes both AG Grid Enterprise and AG Charts Enterprise for $1,598 per developer. This is ideal if you need both advanced grid features and charting capabilities." + }, + { + "question": "What does the AI Toolkit do?", + "answer": "The [AI Toolkit](./data-grid/ai-toolkit/) allows you to integrate AG Grid with any LLM (like ChatGPT or Gemini) to enable natural language grid control. Users can filter, sort, group, and manipulate data using conversational queries." + }, + { + "question": "What is the Server-Side Row Model?", + "answer": "The [Server-Side Row Model](./data-grid/server-side-model/) loads data on-demand from your server as users scroll. It's designed for large datasets that cannot be loaded entirely into the browser, supporting server-side sorting, filtering, and grouping." + }, + { + "question": "How many rows can AG Grid Enterprise handle?", + "answer": "With the Server-Side Row Model, AG Grid Enterprise can handle virtually unlimited rows since data is loaded on-demand. The Client-Side Row Model can comfortably handle hundreds of thousands of rows with excellent performance." + }, + { + "question": "What support is included with an Enterprise licence?", + "answer": "Enterprise licences include access to our dedicated [support portal](https://ag-grid.zendesk.com/hc/en-us), monitored 365 days a year. You'll receive priority support from our engineering team, plus access to all updates and new features during your support period." + }, + { + "question": "Is the Enterprise licence perpetual?", + "answer": "Yes, AG Grid Enterprise licences are perpetual. You can use the version you licensed forever. The annual support and updates subscription is optional for renewal, but recommended to access new features and fixes." + }, + { + "question": "Can I upgrade from Community to Enterprise?", + "answer": "Yes, upgrading is simple. Just install `ag-grid-enterprise` alongside your existing AG Grid packages and add your licence key. All your existing code will continue to work, and Enterprise features become available immediately." + } +] diff --git a/documentation/ag-grid-docs/src/content/faqs/javascript-landing-page.json b/documentation/ag-grid-docs/src/content/faqs/javascript-landing-page.json new file mode 100644 index 00000000000..e018aa80dce --- /dev/null +++ b/documentation/ag-grid-docs/src/content/faqs/javascript-landing-page.json @@ -0,0 +1,42 @@ +[ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create a JavaScript Data Grid for free?", + "answer": "Yes, you can create a JavaScript Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my JavaScript Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between JavaScript Data Grids and [JavaScript Charts](https://www.ag-grid.com/charts/javascript/quick-start/)." + }, + { + "question": "Can I customise the JavaScript Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "Does AG Grid work with any JavaScript framework?", + "answer": "Yes, AG Grid works with vanilla JavaScript and has dedicated wrappers for [React](./react-data-grid/getting-started/), [Angular](./angular-data-grid/getting-started/), and [Vue](./vue-data-grid/getting-started/)." + }, + { + "question": "Can I use AG Grid with TypeScript?", + "answer": "Yes, AG Grid is written in TypeScript and provides full type definitions out of the box for excellent TypeScript support." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or request a trial licence at [info@ag-grid.com](mailto:info@ag-grid.com) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } +] diff --git a/documentation/ag-grid-docs/src/content/faqs/vue-landing-page.json b/documentation/ag-grid-docs/src/content/faqs/vue-landing-page.json new file mode 100644 index 00000000000..6a8732d25e8 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/faqs/vue-landing-page.json @@ -0,0 +1,42 @@ +[ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create a Vue Data Grid for free?", + "answer": "Yes, you can create a Vue Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my Vue Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between Vue Data Grids and [Vue Charts](https://www.ag-grid.com/charts/vue/quick-start/)." + }, + { + "question": "Can I customise the Vue Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own Vue components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "Does AG Grid support Vue 3?", + "answer": "Yes, AG Grid fully supports Vue 3 with the `ag-grid-vue3` package. It works seamlessly with Vue 3's Composition API and script setup syntax." + }, + { + "question": "Can I use AG Grid with Nuxt?", + "answer": "Yes, AG Grid works with Nuxt. Check our documentation for guidance on integrating AG Grid into your Nuxt application." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or request a trial licence at [info@ag-grid.com](mailto:info@ag-grid.com) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } +] diff --git a/documentation/ag-grid-docs/src/content/landing-pages/angular-data-grid.json b/documentation/ag-grid-docs/src/content/landing-pages/angular-data-grid.json new file mode 100644 index 00000000000..2337bd9d9cf --- /dev/null +++ b/documentation/ag-grid-docs/src/content/landing-pages/angular-data-grid.json @@ -0,0 +1,260 @@ +{ + "meta": { + "title": "AG Grid: A Fast, Powerful, and Flexible Angular Data Grid", + "description": "Explore AG Grid's fast, powerful, and customisable Angular Data Grid library. Add high-performance Angular Data Grids to your Angular applications in minutes, for free." + }, + "framework": "angular", + "packageName": "ag-grid-angular", + "docsPath": "angular-data-grid", + "analyticsPrefix": "angular-data-grid", + + "sections": [ + { + "type": "hero", + "tag": "Angular Data Grid", + "heading": "Fast, Powerful and Flexible Angular Data Grids", + "subHeading": "Angular Data Grids", + "subHeadingHtml": "Add high-performance, feature rich, and fully customisable Angular Data Grids to your application in minutes, all for free.", + "showVersionBadge": true, + "primaryCta": { + "text": "Get Started" + }, + "secondaryCta": { + "text": "View All Demos", + "url": "./example" + }, + "demo": { + "gridHeight": 600 + } + }, + { + "type": "features", + "tag": "Why Use AG Grid to Build Angular Data Grids?", + "heading": "Amazingly Fast and Fully Customisable with an Unbeatable Feature Set", + "subHeading": "Get started in minutes and access 1000s of features without compromising on performance. Customise your Angular Data Grid with your own styles and components or upgrade to enterprise to use our advanced features.", + "items": [ + { + "id": "showing-data", + "title": "Showing Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "showing-data-example" + }, + "features": [ + { + "heading": "Value Mapping", + "detail": "Map data to columns using field or valueGetter. Format values with valueFormatter for display.", + "link": "./angular-data-grid/column-definitions/" + }, + { + "heading": "Cell Components", + "detail": "Add buttons, checkboxes, or images to cells with custom cell renderers.", + "link": "./angular-data-grid/component-cell-renderer/" + }, + { + "heading": "Column Resizing", + "detail": "Resize columns by dragging header edges. Use flex values for responsive column widths.", + "link": "./angular-data-grid/column-sizing/" + } + ], + "docsLink": "./angular-data-grid/key-features/#showing-data" + }, + { + "id": "working-with-data", + "title": "Working with Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "working-with-data-example" + }, + "features": [ + { + "heading": "Filtering", + "detail": "Built-in column filters with text, number, and date types. Add floating filters for quick access.", + "link": "./angular-data-grid/filtering/" + }, + { + "heading": "Cell Editing", + "detail": "Enable inline editing with text, number, date, and select editors. Supports undo/redo.", + "link": "./angular-data-grid/cell-editing/" + }, + { + "heading": "Row Selection & Pagination", + "detail": "Select single or multiple rows with checkbox selection. Paginate large datasets with configurable page sizes.", + "link": "./angular-data-grid/row-selection/" + } + ], + "docsLink": "./angular-data-grid/key-features/#working-with-data" + }, + { + "id": "themes-style", + "title": "Themes & Style", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "cell-row-style" + }, + "features": [ + { + "heading": "Theming", + "detail": "Choose from built-in themes (Quartz, Alpine, Balham) or create custom themes with the Theming API.", + "link": "./angular-data-grid/theming/" + }, + { + "heading": "Cell Styles", + "detail": "Apply dynamic styles to cells based on values using cellClassRules for conditional formatting.", + "link": "./angular-data-grid/cell-styles/" + }, + { + "heading": "Row Styles", + "detail": "Style entire rows based on data values with rowClassRules for visual indicators.", + "link": "./angular-data-grid/row-styles/" + } + ], + "docsLink": "./angular-data-grid/key-features/#themes--style" + }, + { + "id": "enterprise-features", + "title": "Enterprise Features", + "isEnterprise": true, + "example": { + "pageName": "key-features", + "exampleName": "enterprise-features-example" + }, + "features": [ + { + "heading": "Integrated Charts", + "detail": "Create and customize charts directly from grid data. Users can build their own visualizations.", + "link": "./angular-data-grid/integrated-charts/" + }, + { + "heading": "Row Grouping & Aggregation", + "detail": "Group rows by columns with automatic aggregations. Users can drag columns to group dynamically.", + "link": "./angular-data-grid/grouping/" + }, + { + "heading": "Tool Panels", + "detail": "Side panels for columns and filters let users customize their view without code changes.", + "link": "./angular-data-grid/tool-panel/" + } + ], + "docsLink": "./angular-data-grid/community-vs-enterprise/" + } + ] + }, + { + "type": "showcase", + "tag": "Where Can I See AG Grid Angular Data Grids Being Used?", + "heading": "Used in Every Industry, for All Types of Data", + "subHeading": "Trusted by 90% of Fortune 500 industries from Finance and AI, to DevTools and Aeronautics. Most of these uses are private, but we've hand-picked a few open-source examples:" + }, + { + "type": "customers", + "tag": "Who Builds Angular Data Grids with AG Grid?", + "headingHtml": "Loved By Developers
Trusted By The Worlds Largest Enterprises", + "subHeadingHtml": "Over 90% of the Fortune 500 build Angular Data Grids using AG Grid, with 1,000,000+ npm downloads per week and over 12,000 Stars on GitHub." + }, + { + "type": "examples", + "tag": "How Do I Build an Angular Data Grid with AG Grid?", + "heading": "Get Started with Angular Data Grid Examples", + "subHeading": "We have a range of examples, tutorials and documentation to help you start building your first Angular Data Grid with AG Grid.", + "showBackgroundGradient": true, + "items": [ + { + "img": "quick-start.png", + "imgAlt": "Quick start guide for creating an Angular Data Grid in minutes", + "title": "Quick Start", + "content": "Create your first Angular Data Grid and learn the basics in just a few minutes.", + "docs": "./angular-data-grid/getting-started", + "demo": "./examples/getting-started/quick-start-example/angular/" + }, + { + "img": "beginner-tutorial.png", + "imgAlt": "Beginner tutorial for building an Angular Data Grid with key functionality", + "title": "Beginner Tutorial", + "content": "Learn key concepts by building an Angular Data Grid with common functionality & customisations.", + "docs": "./angular-data-grid/deep-dive/", + "demo": "./examples/deep-dive/testing-example/angular" + }, + { + "img": "custom-themes.png", + "imgAlt": "Learn how to customise an Angular Data Grid with unique CSS themes", + "title": "Custom Styles", + "content": "Customise your Angular Data Grid with the Theming API to create a unique theme for your brand.", + "docs": "./angular-data-grid/theming/", + "demo": "./examples/theming-colors/color-customisation/angular" + }, + { + "img": "integrated-charts.png", + "imgAlt": "Create and style Integrated Charts from your Angular Data Grid", + "title": "Integrated Charts", + "content": "Build & style charts programatically, or let users create their own from the grid.", + "docs": "./angular-data-grid/integrated-charts/", + "demo": "./examples/integrated-charts-chart-tool-panels/chart-tool-panels/angular" + } + ] + }, + { + "type": "faq", + "tag": "Angular Data Grid FAQs", + "heading": "Frequently Asked Questions", + "subHeading": "Answers to some commonly asked questions when building Angular Data Grids with AG Grid", + "items": [ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create an Angular Data Grid for free?", + "answer": "Yes, you can create an Angular Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my Angular Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between Angular Data Grids and [Angular Charts](https://www.ag-grid.com/charts/angular/quick-start/)." + }, + { + "question": "Can I customise the Angular Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "AG Grid vs. Angular Material Table", + "answer": "AG Grid is a feature-rich data grid offering advanced capabilities like grouping, pivoting, and server-side row model, while Angular Material Table provides a simpler table component as part of its UI library." + }, + { + "question": "Why use AG Grid over PrimeNG Table?", + "answer": "AG Grid offers superior performance with millions of rows, more advanced features like integrated charts and Excel export, and better TypeScript support compared to PrimeNG Table." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or [request a trial licence](./community-vs-enterprise/#request-a-30-day-enterprise-bundle-trial-licence) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } + ] + }, + { + "type": "contact", + "tag": "Get In Touch", + "heading": "Contact Our Team", + "subHeading": "Have questions about AG Grid? Get in touch with our team", + "features": [ + "Expert technical guidance", + "Demos & technical walkthroughs", + "Licencing and pricing information", + "Response within 24 hours" + ] + } + ] +} diff --git a/documentation/ag-grid-docs/src/content/landing-pages/enterprise-data-grid.json b/documentation/ag-grid-docs/src/content/landing-pages/enterprise-data-grid.json new file mode 100644 index 00000000000..87482fd146a --- /dev/null +++ b/documentation/ag-grid-docs/src/content/landing-pages/enterprise-data-grid.json @@ -0,0 +1,374 @@ +{ + "meta": { + "title": "AG Grid Enterprise: Advanced Data Grid Features for Professional Applications", + "description": "Unlock AG Grid's full potential with Enterprise features including AI Toolkit, Server-Side Row Model, Row Grouping, Master/Detail, and Integrated Charts. Start your free trial today." + }, + "framework": "react", + "packageName": "ag-grid-enterprise", + "docsPath": "data-grid", + "analyticsPrefix": "enterprise", + + "sections": [ + { + "type": "hero", + "variant": "enterprise", + "tag": "Enterprise Data Grid", + "heading": "Enterprise Grade Data Grid for Web Apps", + "subHeading": "Fast, secure and accessible, AG Grid Enterprise handles millions of rows and comes with advanced features, including an AI Toolkit, Integrated Charts, Grouping, and Pivoting.", + "showVersionBadge": true, + "primaryCta": { + "text": "Start Free Trial", + "useTrialButton": true + }, + "secondaryCta": { + "text": "View All Demos", + "url": "./example" + }, + "demo": { + "enableRowGroup": true, + "gridHeight": 600 + } + }, + { + "type": "features", + "tag": "Why Upgrade to AG Grid Enterprise?", + "heading": "Advanced Features for Complex Data Requirements", + "subHeading": "Handle millions of rows with server-side processing, build interactive charts, enable AI-powered grid interactions, and create hierarchical data views - all with simple configuration.", + "items": [ + { + "id": "grouping-aggregation", + "title": "Grouping & Aggregation", + "isEnterprise": true, + "example": { + "pageName": "grouping", + "exampleName": "kitchen-sink" + }, + "features": [ + { + "heading": "Multi-Level Grouping", + "detail": "Group rows by one or more columns to create hierarchical data views. Users can expand and collapse groups interactively.", + "link": "./data-grid/grouping/" + }, + { + "heading": "Automatic Aggregations", + "detail": "Calculate aggregations automatically with built-in functions (sum, avg, count, min, max) or create custom aggregation functions.", + "link": "./data-grid/aggregation/" + }, + { + "heading": "Drag & Drop Grouping", + "detail": "Enable row group panel to let users drag columns to group data dynamically without code changes.", + "link": "./data-grid/grouping-group-panel/" + } + ], + "docsLink": "./data-grid/grouping/" + }, + { + "id": "tree-data", + "title": "Tree Data", + "isEnterprise": true, + "example": { + "pageName": "tree-data", + "exampleName": "kitchen-sink" + }, + "features": [ + { + "heading": "Hierarchical Data", + "detail": "Display hierarchical data with parent-child relationships. Perfect for file browsers, org charts, and nested categories.", + "link": "./data-grid/tree-data/" + }, + { + "heading": "Expand & Collapse", + "detail": "Users can expand and collapse tree nodes interactively. Control expansion state programmatically via the API." + }, + { + "heading": "Aggregation Support", + "detail": "Apply aggregations to tree data to roll up values from child nodes to parent nodes automatically." + } + ], + "docsLink": "./data-grid/tree-data/" + }, + { + "id": "ssrm", + "title": "Server-Side Row Model", + "isEnterprise": true, + "example": { + "pageName": "server-side-model-configuration", + "exampleName": "cache-configurations" + }, + "features": [ + { + "heading": "Lazy Loading", + "detail": "Handle millions of rows by loading data on-demand as users scroll. Perfect for large datasets that cannot fit in browser memory.", + "link": "./data-grid/server-side-model/" + }, + { + "heading": "Server-Side Operations", + "detail": "Perform sorting, filtering, and grouping on your server for optimal performance.", + "link": "./data-grid/server-side-model-sorting/" + }, + { + "heading": "Infinite Scrolling", + "detail": "Combine with infinite scrolling for seamless data loading. Reduces memory footprint and improves initial load times.", + "link": "./data-grid/infinite-scrolling/" + } + ], + "docsLink": "./data-grid/server-side-model/" + }, + { + "id": "excel-export", + "title": "Excel Export", + "isEnterprise": true, + "example": { + "pageName": "excel-export-styles", + "exampleName": "excel-export-with-styles" + }, + "features": [ + { + "heading": "Native Excel Files", + "detail": "Export grid data to native Excel files (.xlsx) with full formatting support. No server-side processing required.", + "link": "./data-grid/excel-export/" + }, + { + "heading": "Style & Format Export", + "detail": "Export with cell styles, fonts, and colors. Preserve your grid's appearance in the exported spreadsheet.", + "link": "./data-grid/excel-export-styles/" + }, + { + "heading": "Multiple Sheets", + "detail": "Export to multiple sheets in a single workbook. Include charts, images, and custom content.", + "link": "./data-grid/excel-export-multiple-sheets/" + } + ], + "docsLink": "./data-grid/excel-export/" + }, + { + "id": "ai-toolkit", + "title": "AI Toolkit", + "isEnterprise": true, + "example": { + "pageName": "ai-toolkit", + "exampleName": "natural-language-grid-state" + }, + "features": [ + { + "heading": "Natural Language Control", + "detail": "Integrate your LLM with AG Grid to let users control grid state via natural language.", + "link": "./data-grid/ai-toolkit/" + }, + { + "heading": "Structured Schema Generation", + "detail": "Use getStructuredSchema() to generate LLM-compatible schemas. Works with any LLM supporting structured outputs." + }, + { + "heading": "Full State Manipulation", + "detail": "Apply LLM responses directly to the grid with setState(). Supports filtering, sorting, aggregation, pivoting, and more." + } + ], + "docsLink": "./data-grid/ai-toolkit/" + }, + { + "id": "set-filter", + "title": "Set Filter", + "isEnterprise": true, + "example": { + "pageName": "filter-set", + "exampleName": "enabling-set-filters" + }, + "features": [ + { + "heading": "Excel-Style Filtering", + "detail": "Provide users with an Excel-like filter experience with checkboxes for each unique value in a column.", + "link": "./data-grid/filter-set/" + }, + { + "heading": "Search & Select", + "detail": "Built-in search box lets users quickly find values in large lists. Select all, select none, and invert selection with one click." + }, + { + "heading": "Async Values", + "detail": "Load filter values asynchronously from your server. Perfect for columns with thousands of unique values.", + "link": "./data-grid/filter-set-filter-list/" + } + ], + "docsLink": "./data-grid/filter-set/" + }, + { + "id": "master-detail", + "title": "Master/Detail", + "isEnterprise": true, + "example": { + "pageName": "master-detail", + "exampleName": "simple" + }, + "features": [ + { + "heading": "Nested Grids", + "detail": "Display hierarchical data with expandable rows. Each detail row contains a fully featured grid with its own columns and features.", + "link": "./data-grid/master-detail/" + }, + { + "heading": "Custom Detail Panels", + "detail": "Use custom detail renderers to display any content in the detail section - forms, charts, or custom components.", + "link": "./data-grid/master-detail-custom-detail/" + }, + { + "heading": "Lazy Loading Details", + "detail": "Load detail data on-demand when rows are expanded. Independent configuration for each detail grid." + } + ], + "docsLink": "./data-grid/master-detail/" + }, + { + "id": "context-menu", + "title": "Context Menu", + "isEnterprise": true, + "example": { + "pageName": "context-menu", + "exampleName": "context-menu" + }, + "features": [ + { + "heading": "Right-Click Menu", + "detail": "Provide a right-click context menu with built-in actions like copy, paste, export, and chart creation.", + "link": "./data-grid/context-menu/" + }, + { + "heading": "Custom Menu Items", + "detail": "Add custom menu items with icons, submenus, and keyboard shortcuts. Full control over menu structure.", + "link": "./data-grid/context-menu/#configuring-the-context-menu" + }, + { + "heading": "Dynamic Menus", + "detail": "Show different menu items based on the clicked cell, row, or column. Context-aware menus for better user experience." + } + ], + "docsLink": "./data-grid/context-menu/" + } + ] + }, + { + "type": "integrated-charts", + "tag": "Built-in Charting", + "headingHtml": "Integrated Charts, Powered by AG Charts", + "subHeading": "Let your users visualise data with charts directly from the Grid. Select cells, right-click, and create beautiful charts instantly.", + "showBackgroundGradient": true + }, + { + "type": "comparison", + "tag": "Community vs Enterprise", + "heading": "Choose the Right Edition for Your Needs", + "subHeading": "AG Grid Community is free forever and includes powerful features. Enterprise unlocks advanced capabilities for professional applications.", + "showBackgroundGradient": true + }, + { + "type": "customers", + "tag": "Who Uses AG Grid Enterprise?", + "headingHtml": "Trusted By The World's Leading Companies", + "subHeadingHtml": "Over 90% of the Fortune 500 use AG Grid Enterprise, with 2,500,000+ npm downloads per month.", + "displayLogos": false + }, + { + "type": "pricing", + "tag": "Ready to Get Started?", + "heading": "Simple, Transparent Pricing", + "subHeading": "One-time perpetual licence with 1 years of support and updates. Volume & renewal discounts available.", + "showBackgroundGradient": true, + "cards": [ + { + "title": "Community", + "price": "Free", + "priceNote": "forever", + "features": ["Core grid functionality", "Sorting, filtering & pagination", "MIT licensed"], + "ctaText": "Get Started", + "ctaUrl": "./data-grid/getting-started/" + }, + { + "title": "Enterprise", + "price": "$999", + "priceNote": "per developer, perpetual licence", + "features": ["All Enterprise features", "Buy once, use forever", "1 year of support & updates"], + "ctaText": "Buy Now", + "ctaUrl": "https://www.ag-grid.com/ecommerce/#/ecommerce/?licenseType=single&productType=both", + "isPrimary": true, + "ctaId": "buy-now" + }, + { + "title": "Enterprise Bundle", + "price": "$1,498", + "priceNote": "per developer, perpetual licence", + "features": [ + "All Grid & Charts Enterprise Features", + "Buy once, use forever", + "1 year of support & updates" + ], + "ctaText": "Buy Now", + "ctaUrl": "https://www.ag-grid.com/ecommerce/#/ecommerce/?licenseType=single&productType=both", + "ctaId": "bundle-buy-now", + "showTrialButton": true + } + ] + }, + { + "type": "faq", + "tag": "Enterprise FAQs", + "heading": "Frequently Asked Questions", + "subHeading": "Common questions about AG Grid Enterprise features and licensing", + "items": [ + { + "question": "Can I try AG Grid Enterprise for free?", + "answer": "Yes! You can install and test all AG Grid Enterprise features locally for free. A watermark and console message will appear until you add a licence key. You can also [request a trial licence](./community-vs-enterprise/#request-a-30-day-enterprise-bundle-trial-licence/) to remove these during your evaluation period." + }, + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is free and open-source with powerful core features. [AG Grid Enterprise](/license-pricing/) adds advanced capabilities like Row Grouping, Server-Side Row Model, AI Toolkit, Integrated Charts, Master/Detail, and dedicated support." + }, + { + "question": "How much does AG Grid Enterprise cost?", + "answer": "AG Grid Enterprise costs $999 USD per developer for a perpetual licence. This includes 1 year of support and updates. Volume discounts are available. Visit the [Pricing page](/license-pricing/) for full details." + }, + { + "question": "What is included in the Enterprise Bundle?", + "answer": "The Enterprise Bundle includes both AG Grid Enterprise and AG Charts Enterprise for $1,598 per developer. This is ideal if you need both advanced grid features and charting capabilities." + }, + { + "question": "What does the AI Toolkit do?", + "answer": "The [AI Toolkit](./ai-toolkit/) allows you to integrate AG Grid with any LLM (like ChatGPT or Gemini) to enable natural language grid control. Users can filter, sort, group, and manipulate data using conversational queries." + }, + { + "question": "What is the Server-Side Row Model?", + "answer": "The [Server-Side Row Model](./server-side-model/) loads data on-demand from your server as users scroll. It's designed for large datasets that cannot be loaded entirely into the browser, supporting server-side sorting, filtering, and grouping." + }, + { + "question": "How many rows can AG Grid Enterprise handle?", + "answer": "With the Server-Side Row Model, AG Grid Enterprise can handle virtually unlimited rows since data is loaded on-demand. The Client-Side Row Model can comfortably handle hundreds of thousands of rows with excellent performance." + }, + { + "question": "What support is included with an Enterprise licence?", + "answer": "Enterprise licences include access to our dedicated [support portal](https://ag-grid.zendesk.com/hc/en-us), monitored 365 days a year. You'll receive priority support from our engineering team, plus access to all updates and new features during your support period." + }, + { + "question": "Is the Enterprise licence perpetual?", + "answer": "Yes, AG Grid Enterprise licences are perpetual. You can use the version you licensed forever. The annual support and updates subscription is optional for renewal, but recommended to access new features and fixes." + }, + { + "question": "Can I upgrade from Community to Enterprise?", + "answer": "Yes, upgrading is simple. Just install `ag-grid-enterprise` alongside your existing AG Grid packages and add your licence key. All your existing code will continue to work, and Enterprise features become available immediately." + } + ] + }, + { + "type": "contact", + "variant": "sales", + "tag": "Get In Touch", + "heading": "Contact Our Sales Team", + "subHeading": "Get help with pricing, explore use-cases for your team, and more", + "features": [ + "Custom pricing for large teams", + "Dedicated technical support", + "Multi-year discounts available", + "Response within 24 hours", + "Demos & Technical Walkthroughs" + ] + } + ] +} diff --git a/documentation/ag-grid-docs/src/content/landing-pages/javascript-data-grid.json b/documentation/ag-grid-docs/src/content/landing-pages/javascript-data-grid.json new file mode 100644 index 00000000000..b8aef0e4981 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/landing-pages/javascript-data-grid.json @@ -0,0 +1,260 @@ +{ + "meta": { + "title": "AG Grid: A Fast, Powerful, and Flexible JavaScript Data Grid", + "description": "Explore AG Grid's fast, powerful, and customisable JavaScript Data Grid library. Add high-performance JavaScript Data Grids to your applications in minutes, for free." + }, + "framework": "typescript", + "packageName": "ag-grid-community", + "docsPath": "javascript-data-grid", + "analyticsPrefix": "javascript-data-grid", + + "sections": [ + { + "type": "hero", + "tag": "JavaScript Data Grid", + "heading": "Fast, Powerful and Flexible JavaScript Data Grids", + "subHeading": "JavaScript Data Grids", + "subHeadingHtml": "Add high-performance, feature rich, and fully customisable JavaScript Data Grids to your application in minutes, all for free.", + "showVersionBadge": true, + "primaryCta": { + "text": "Get Started" + }, + "secondaryCta": { + "text": "View All Demos", + "url": "./example" + }, + "demo": { + "gridHeight": 600 + } + }, + { + "type": "features", + "tag": "Why Use AG Grid to Build JavaScript Data Grids?", + "heading": "Amazingly Fast and Fully Customisable with an Unbeatable Feature Set", + "subHeading": "Get started in minutes and access 1000s of features without compromising on performance. Customise your JavaScript Data Grid with your own styles and components or upgrade to enterprise to use our advanced features.", + "items": [ + { + "id": "showing-data", + "title": "Showing Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "showing-data-example" + }, + "features": [ + { + "heading": "Value Mapping", + "detail": "Map data to columns using field or valueGetter. Format values with valueFormatter for display.", + "link": "./javascript-data-grid/column-definitions/" + }, + { + "heading": "Cell Components", + "detail": "Add buttons, checkboxes, or images to cells with custom cell renderers.", + "link": "./javascript-data-grid/component-cell-renderer/" + }, + { + "heading": "Column Resizing", + "detail": "Resize columns by dragging header edges. Use flex values for responsive column widths.", + "link": "./javascript-data-grid/column-sizing/" + } + ], + "docsLink": "./javascript-data-grid/key-features/#showing-data" + }, + { + "id": "working-with-data", + "title": "Working with Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "working-with-data-example" + }, + "features": [ + { + "heading": "Filtering", + "detail": "Built-in column filters with text, number, and date types. Add floating filters for quick access.", + "link": "./javascript-data-grid/filtering/" + }, + { + "heading": "Cell Editing", + "detail": "Enable inline editing with text, number, date, and select editors. Supports undo/redo.", + "link": "./javascript-data-grid/cell-editing/" + }, + { + "heading": "Row Selection & Pagination", + "detail": "Select single or multiple rows with checkbox selection. Paginate large datasets with configurable page sizes.", + "link": "./javascript-data-grid/row-selection/" + } + ], + "docsLink": "./javascript-data-grid/key-features/#working-with-data" + }, + { + "id": "themes-style", + "title": "Themes & Style", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "cell-row-style" + }, + "features": [ + { + "heading": "Theming", + "detail": "Choose from built-in themes (Quartz, Alpine, Balham) or create custom themes with the Theming API.", + "link": "./javascript-data-grid/theming/" + }, + { + "heading": "Cell Styles", + "detail": "Apply dynamic styles to cells based on values using cellClassRules for conditional formatting.", + "link": "./javascript-data-grid/cell-styles/" + }, + { + "heading": "Row Styles", + "detail": "Style entire rows based on data values with rowClassRules for visual indicators.", + "link": "./javascript-data-grid/row-styles/" + } + ], + "docsLink": "./javascript-data-grid/key-features/#themes--style" + }, + { + "id": "enterprise-features", + "title": "Enterprise Features", + "isEnterprise": true, + "example": { + "pageName": "key-features", + "exampleName": "enterprise-features-example" + }, + "features": [ + { + "heading": "Integrated Charts", + "detail": "Create and customize charts directly from grid data. Users can build their own visualizations.", + "link": "./javascript-data-grid/integrated-charts/" + }, + { + "heading": "Row Grouping & Aggregation", + "detail": "Group rows by columns with automatic aggregations. Users can drag columns to group dynamically.", + "link": "./javascript-data-grid/grouping/" + }, + { + "heading": "Tool Panels", + "detail": "Side panels for columns and filters let users customize their view without code changes.", + "link": "./javascript-data-grid/tool-panel/" + } + ], + "docsLink": "./javascript-data-grid/community-vs-enterprise/" + } + ] + }, + { + "type": "showcase", + "tag": "Where Can I See AG Grid JavaScript Data Grids Being Used?", + "heading": "Used in Every Industry, for All Types of Data", + "subHeading": "Trusted by 90% of Fortune 500 industries from Finance and AI, to DevTools and Aeronautics. Most of these uses are private, but we've hand-picked a few open-source examples:" + }, + { + "type": "customers", + "tag": "Who Builds JavaScript Data Grids with AG Grid?", + "headingHtml": "Loved By Developers
Trusted By The Worlds Largest Enterprises", + "subHeadingHtml": "Over 90% of the Fortune 500 build JavaScript Data Grids using AG Grid, with 1,000,000+ npm downloads per week and over 12,000 Stars on GitHub." + }, + { + "type": "examples", + "tag": "How Do I Build a JavaScript Data Grid with AG Grid?", + "heading": "Get Started with JavaScript Data Grid Examples", + "subHeading": "We have a range of examples, tutorials and documentation to help you start building your first JavaScript Data Grid with AG Grid.", + "showBackgroundGradient": true, + "items": [ + { + "img": "quick-start.png", + "imgAlt": "Quick start guide for creating a JavaScript Data Grid in minutes", + "title": "Quick Start", + "content": "Create your first JavaScript Data Grid and learn the basics in just a few minutes.", + "docs": "./javascript-data-grid/getting-started", + "demo": "./examples/getting-started/quick-start-example/vanilla/" + }, + { + "img": "beginner-tutorial.png", + "imgAlt": "Beginner tutorial for building a JavaScript Data Grid with key functionality", + "title": "Beginner Tutorial", + "content": "Learn key concepts by building a JavaScript Data Grid with common functionality & customisations.", + "docs": "./javascript-data-grid/deep-dive/", + "demo": "./examples/deep-dive/testing-example/vanilla" + }, + { + "img": "custom-themes.png", + "imgAlt": "Learn how to customise a JavaScript Data Grid with unique CSS themes", + "title": "Custom Styles", + "content": "Customise your JavaScript Data Grid with the Theming API to create a unique theme for your brand.", + "docs": "./javascript-data-grid/theming/", + "demo": "./examples/theming-colors/color-customisation/vanilla" + }, + { + "img": "integrated-charts.png", + "imgAlt": "Create and style Integrated Charts from your JavaScript Data Grid", + "title": "Integrated Charts", + "content": "Build & style charts programatically, or let users create their own from the grid.", + "docs": "./javascript-data-grid/integrated-charts/", + "demo": "./examples/integrated-charts-chart-tool-panels/chart-tool-panels/vanilla" + } + ] + }, + { + "type": "faq", + "tag": "JavaScript Data Grid FAQs", + "heading": "Frequently Asked Questions", + "subHeading": "Answers to some commonly asked questions when building JavaScript Data Grids with AG Grid", + "items": [ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create a JavaScript Data Grid for free?", + "answer": "Yes, you can create a JavaScript Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my JavaScript Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between JavaScript Data Grids and [JavaScript Charts](https://www.ag-grid.com/charts/javascript/quick-start/)." + }, + { + "question": "Can I customise the JavaScript Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "Does AG Grid work with any JavaScript framework?", + "answer": "Yes, AG Grid works with vanilla JavaScript and has dedicated wrappers for React, Angular, and Vue." + }, + { + "question": "Can I use AG Grid with TypeScript?", + "answer": "Yes, AG Grid is written in TypeScript and provides full type definitions out of the box for excellent TypeScript support." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or [request a trial licence](./community-vs-enterprise/#request-a-30-day-enterprise-bundle-trial-licence) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } + ] + }, + { + "type": "contact", + "tag": "Get In Touch", + "heading": "Contact Our Team", + "subHeading": "Have questions about AG Grid? Get in touch with our team", + "features": [ + "Expert technical guidance", + "Demos & technical walkthroughs", + "Licencing and pricing information", + "Response within 24 hours" + ] + } + ] +} diff --git a/documentation/ag-grid-docs/src/content/landing-pages/react-data-grid.json b/documentation/ag-grid-docs/src/content/landing-pages/react-data-grid.json new file mode 100644 index 00000000000..ec43f720f0b --- /dev/null +++ b/documentation/ag-grid-docs/src/content/landing-pages/react-data-grid.json @@ -0,0 +1,260 @@ +{ + "meta": { + "title": "AG Grid: A Fast, Powerful, and Flexible React Data Grid", + "description": "Explore AG Grid's fast, powerful, and customisable React Data Grid library. Add high-performance React Data Grids to your React applications in minutes, for free." + }, + "framework": "reactFunctionalTs", + "packageName": "ag-grid-react", + "docsPath": "react-data-grid", + "analyticsPrefix": "react-data-grid", + + "sections": [ + { + "type": "hero", + "tag": "React Data Grid", + "heading": "Fast, Powerful and Flexible React Data Grids", + "subHeading": "React Data Grids", + "subHeadingHtml": "Add high-performance, feature rich, and fully customisable React Data Grids to your application in minutes, all for free.", + "showVersionBadge": true, + "primaryCta": { + "text": "Get Started" + }, + "secondaryCta": { + "text": "View All Demos", + "url": "./example" + }, + "demo": { + "gridHeight": 600 + } + }, + { + "type": "features", + "tag": "Why Use AG Grid to Build React Data Grids?", + "heading": "Amazingly Fast and Fully Customisable with an Unbeatable Feature Set", + "subHeading": "Get started in minutes and access 1000s of features without compromising on performance. Customise your React Data Grid with your own styles and components or upgrade to enterprise to use our advanced features.", + "items": [ + { + "id": "showing-data", + "title": "Showing Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "showing-data-example" + }, + "features": [ + { + "heading": "Value Mapping", + "detail": "Map data to columns using field or valueGetter. Format values with valueFormatter for display.", + "link": "./react-data-grid/column-definitions/" + }, + { + "heading": "Cell Components", + "detail": "Add buttons, checkboxes, or images to cells with custom cell renderers.", + "link": "./react-data-grid/component-cell-renderer/" + }, + { + "heading": "Column Resizing", + "detail": "Resize columns by dragging header edges. Use flex values for responsive column widths.", + "link": "./react-data-grid/column-sizing/" + } + ], + "docsLink": "./react-data-grid/key-features/#showing-data" + }, + { + "id": "working-with-data", + "title": "Working with Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "working-with-data-example" + }, + "features": [ + { + "heading": "Filtering", + "detail": "Built-in column filters with text, number, and date types. Add floating filters for quick access.", + "link": "./react-data-grid/filtering/" + }, + { + "heading": "Cell Editing", + "detail": "Enable inline editing with text, number, date, and select editors. Supports undo/redo.", + "link": "./react-data-grid/cell-editing/" + }, + { + "heading": "Row Selection & Pagination", + "detail": "Select single or multiple rows with checkbox selection. Paginate large datasets with configurable page sizes.", + "link": "./react-data-grid/row-selection/" + } + ], + "docsLink": "./react-data-grid/key-features/#working-with-data" + }, + { + "id": "themes-style", + "title": "Themes & Style", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "cell-row-style" + }, + "features": [ + { + "heading": "Theming", + "detail": "Choose from built-in themes (Quartz, Alpine, Balham) or create custom themes with the Theming API.", + "link": "./react-data-grid/theming/" + }, + { + "heading": "Cell Styles", + "detail": "Apply dynamic styles to cells based on values using cellClassRules for conditional formatting.", + "link": "./react-data-grid/cell-styles/" + }, + { + "heading": "Row Styles", + "detail": "Style entire rows based on data values with rowClassRules for visual indicators.", + "link": "./react-data-grid/row-styles/" + } + ], + "docsLink": "./react-data-grid/key-features/#themes--style" + }, + { + "id": "enterprise-features", + "title": "Enterprise Features", + "isEnterprise": true, + "example": { + "pageName": "key-features", + "exampleName": "enterprise-features-example" + }, + "features": [ + { + "heading": "Integrated Charts", + "detail": "Create and customize charts directly from grid data. Users can build their own visualizations.", + "link": "./react-data-grid/integrated-charts/" + }, + { + "heading": "Row Grouping & Aggregation", + "detail": "Group rows by columns with automatic aggregations. Users can drag columns to group dynamically.", + "link": "./react-data-grid/grouping/" + }, + { + "heading": "Tool Panels", + "detail": "Side panels for columns and filters let users customize their view without code changes.", + "link": "./react-data-grid/tool-panel/" + } + ], + "docsLink": "./react-data-grid/community-vs-enterprise/" + } + ] + }, + { + "type": "showcase", + "tag": "Where Can I See AG Grid React Data Grids Being Used?", + "heading": "Used in Every Industry, for All Types of Data", + "subHeading": "Trusted by 90% of Fortune 500 industries from Finance and AI, to DevTools and Aeronautics. Most of these uses are private, but we've hand-picked a few open-source examples:" + }, + { + "type": "customers", + "tag": "Who Builds React Data Grids with AG Grid?", + "headingHtml": "Loved By Developers
Trusted By The Worlds Largest Enterprises", + "subHeadingHtml": "Over 90% of the Fortune 500 build React Data Grids using AG Grid, with 1,000,000+ npm downloads per week and over 12,000 Stars on GitHub." + }, + { + "type": "examples", + "tag": "How Do I Build a React Data Grid with AG Grid?", + "heading": "Get Started with React Data Grid Examples", + "subHeading": "We have a range of examples, tutorials and documentation to help you start building your first React Data Grid with AG Grid.", + "showBackgroundGradient": true, + "items": [ + { + "img": "quick-start.png", + "imgAlt": "Quick start guide for creating a React Data Grid in minutes", + "title": "Quick Start", + "content": "Create your first React Data Grid and learn the basics in just a few minutes.", + "docs": "./react-data-grid/getting-started", + "demo": "./examples/getting-started/quick-start-example/reactFunctionalTs/" + }, + { + "img": "beginner-tutorial.png", + "imgAlt": "Beginner tutorial for building a React Data Grid with key functionality", + "title": "Beginner Tutorial", + "content": "Learn key concepts by building a React Data Grid with common functionality & customisations.", + "docs": "./react-data-grid/deep-dive/", + "demo": "./examples/deep-dive/testing-example/reactFunctionalTs" + }, + { + "img": "custom-themes.png", + "imgAlt": "Learn how to customise a React Data Grid with unique CSS themes", + "title": "Custom Styles", + "content": "Customise your React Data Grid with the Theming API to create a unique theme for your brand.", + "docs": "./react-data-grid/theming/", + "demo": "./examples/theming-colors/color-customisation/reactFunctionalTs" + }, + { + "img": "integrated-charts.png", + "imgAlt": "Create and style Integrated Charts from your React Data Grid", + "title": "Integrated Charts", + "content": "Build & style charts programatically, or let users create their own from the table.", + "docs": "./react-data-grid/integrated-charts/", + "demo": "./examples/integrated-charts-chart-tool-panels/chart-tool-panels/reactFunctionalTs" + } + ] + }, + { + "type": "faq", + "tag": "React Data Grid FAQs", + "heading": "Frequently Asked Questions", + "subHeading": "Answers to some commonly asked questions when building React Data Grids with AG Grid", + "items": [ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create a React Data Grid for free?", + "answer": "Yes, you can create a React Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my React Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between React Data Grids and [React Charts](https://www.ag-grid.com/charts/react/quick-start/)." + }, + { + "question": "Can I customise the React Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "AG Grid vs. MUI", + "answer": "AG Grid is a feature-rich data grid offering advanced capabilities, while MUI (Material-UI) provides a simpler table component as part of its UI library." + }, + { + "question": "Why use AG Grid over TanStack React Table?", + "answer": "TanStack React Table is for building bespoke data grids from scratch, whereas AG Grid comes with a UI and broader feature set, which just requires configuration." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or [request a trial licence](./community-vs-enterprise/#request-a-30-day-enterprise-bundle-trial-licence) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } + ] + }, + { + "type": "contact", + "tag": "Get In Touch", + "heading": "Contact Our Team", + "subHeading": "Have questions about AG Grid? Get in touch with our team", + "features": [ + "Expert technical guidance", + "Demos & technical walkthroughs", + "Licencing and pricing information", + "Response within 24 hours" + ] + } + ] +} diff --git a/documentation/ag-grid-docs/src/content/landing-pages/react-table.json b/documentation/ag-grid-docs/src/content/landing-pages/react-table.json new file mode 100644 index 00000000000..bfd1c854b6f --- /dev/null +++ b/documentation/ag-grid-docs/src/content/landing-pages/react-table.json @@ -0,0 +1,260 @@ +{ + "meta": { + "title": "AG Grid: A Fast, Powerful, and Flexible React Data Table", + "description": "Explore AG Grid's fast, powerful, and customisable React Table library. Add high-performance React Data Tables to your React applications in minutes, for free." + }, + "framework": "reactFunctionalTs", + "packageName": "ag-grid-react", + "docsPath": "react-data-grid", + "analyticsPrefix": "react-table", + + "sections": [ + { + "type": "hero", + "tag": "React Table", + "heading": "Fast, Powerful and Flexible React Tables", + "subHeading": "React Data Tables", + "subHeadingHtml": "Add high-performance, feature rich, and fully customisable React Data Tables to your application in minutes, all for free.", + "showVersionBadge": true, + "primaryCta": { + "text": "Get Started" + }, + "secondaryCta": { + "text": "View All Demos", + "url": "./example" + }, + "demo": { + "gridHeight": 600 + } + }, + { + "type": "features", + "tag": "Why Use AG Grid to Build React Tables?", + "heading": "Amazingly Fast and Fully Customisable with an Unbeatable Feature Set", + "subHeading": "Get started in minutes and access 1000s of features without compromising on performance. Customise your React Table with your own styles and components or upgrade to enterprise to use our advanced features.", + "items": [ + { + "id": "showing-data", + "title": "Showing Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "showing-data-example" + }, + "features": [ + { + "heading": "Value Mapping", + "detail": "Map data to columns using field or valueGetter. Format values with valueFormatter for display.", + "link": "./react-data-grid/column-definitions/" + }, + { + "heading": "Cell Components", + "detail": "Add buttons, checkboxes, or images to cells with custom cell renderers.", + "link": "./react-data-grid/component-cell-renderer/" + }, + { + "heading": "Column Resizing", + "detail": "Resize columns by dragging header edges. Use flex values for responsive column widths.", + "link": "./react-data-grid/column-sizing/" + } + ], + "docsLink": "./react-data-grid/key-features/" + }, + { + "id": "working-with-data", + "title": "Working with Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "working-with-data-example" + }, + "features": [ + { + "heading": "Filtering", + "detail": "Built-in column filters with text, number, and date types. Add floating filters for quick access.", + "link": "./react-data-grid/filtering/" + }, + { + "heading": "Cell Editing", + "detail": "Enable inline editing with text, number, date, and select editors. Supports undo/redo.", + "link": "./react-data-grid/cell-editing/" + }, + { + "heading": "Row Selection & Pagination", + "detail": "Select single or multiple rows with checkbox selection. Paginate large datasets with configurable page sizes.", + "link": "./react-data-grid/row-selection/" + } + ], + "docsLink": "./react-data-grid/key-features/" + }, + { + "id": "themes-style", + "title": "Themes & Style", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "cell-row-style" + }, + "features": [ + { + "heading": "Theming", + "detail": "Choose from built-in themes (Quartz, Alpine, Balham) or create custom themes with the Theming API.", + "link": "./react-data-grid/theming/" + }, + { + "heading": "Cell Styles", + "detail": "Apply dynamic styles to cells based on values using cellClassRules for conditional formatting.", + "link": "./react-data-grid/cell-styles/" + }, + { + "heading": "Row Styles", + "detail": "Style entire rows based on data values with rowClassRules for visual indicators.", + "link": "./react-data-grid/row-styles/" + } + ], + "docsLink": "./react-data-grid/theming/" + }, + { + "id": "enterprise-features", + "title": "Enterprise Features", + "isEnterprise": true, + "example": { + "pageName": "key-features", + "exampleName": "enterprise-features-example" + }, + "features": [ + { + "heading": "Integrated Charts", + "detail": "Create and customize charts directly from grid data. Users can build their own visualizations.", + "link": "./react-data-grid/integrated-charts/" + }, + { + "heading": "Row Grouping & Aggregation", + "detail": "Group rows by columns with automatic aggregations. Users can drag columns to group dynamically.", + "link": "./react-data-grid/grouping/" + }, + { + "heading": "Tool Panels", + "detail": "Side panels for columns and filters let users customize their view without code changes.", + "link": "./react-data-grid/tool-panel/" + } + ], + "docsLink": "./react-data-grid/community-vs-enterprise/" + } + ] + }, + { + "type": "showcase", + "tag": "Where Can I See AG Grid React Tables Being Used?", + "heading": "Used in Every Industry, for All Types of Data", + "subHeading": "Trusted by 90% of Fortune 500 industries from Finance and AI, to DevTools and Aeronautics. Most of these uses are private, but we've hand-picked a few open-source examples:" + }, + { + "type": "customers", + "tag": "Who Builds React Tables with AG Grid?", + "headingHtml": "Loved By Developers
Trusted By The Worlds Largest Enterprises", + "subHeadingHtml": "Over 90% of the Fortune 500 build React Tables using AG Grid, with 1,000,000+ npm downloads per week and over 12,000 Stars on GitHub." + }, + { + "type": "examples", + "tag": "How Do I Build a React Table with AG Grid?", + "heading": "Get Started with React Table Examples", + "subHeading": "We have a range of examples, tutorials and documentation to help you start building your first React Table with AG Grid.", + "showBackgroundGradient": true, + "items": [ + { + "img": "quick-start.png", + "imgAlt": "Quick start guide for creating a React Table in minutes", + "title": "Quick Start", + "content": "Create your first React Table and learn the basics in just a few minutes.", + "docs": "./react-data-grid/getting-started", + "demo": "./examples/getting-started/quick-start-example/reactFunctionalTs/" + }, + { + "img": "beginner-tutorial.png", + "imgAlt": "Beginner tutorial for building a React Table with key functionality", + "title": "Beginner Tutorial", + "content": "Learn key concepts by building a React Table with common functionality & customisations.", + "docs": "./react-data-grid/deep-dive/", + "demo": "./examples/deep-dive/testing-example/reactFunctionalTs" + }, + { + "img": "custom-themes.png", + "imgAlt": "Learn how to customise a React Table with unique CSS themes", + "title": "Custom Styles", + "content": "Customise your React Table with the Theming API to create a unique theme for your brand.", + "docs": "./react-data-grid/theming/", + "demo": "./examples/theming-colors/color-customisation/reactFunctionalTs" + }, + { + "img": "integrated-charts.png", + "imgAlt": "Create and style Integrated Charts from your React Table", + "title": "Integrated Charts", + "content": "Build & style charts programmatically, or let users create their own from the table.", + "docs": "./react-data-grid/integrated-charts/", + "demo": "./examples/integrated-charts-chart-tool-panels/chart-tool-panels/reactFunctionalTs" + } + ] + }, + { + "type": "faq", + "tag": "React Table FAQs", + "heading": "Frequently Asked Questions", + "subHeading": "Answers to some commonly asked questions when building React Tables with AG Grid", + "items": [ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create a React Table for free?", + "answer": "Yes, you can create a React Table for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my React Tables be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between React Tables and [React Charts](https://www.ag-grid.com/charts/react/quick-start/)." + }, + { + "question": "Can I customise the React Table?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use our own components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "AG Grid vs. MUI", + "answer": "AG Grid is a feature-rich data grid offering advanced capabilities, while MUI (Material-UI) provides a simpler table component as part of its UI library." + }, + { + "question": "Why use AG Grid over TanStack React Table?", + "answer": "TanStack React Table is for building bespoke data grids from scratch, whereas AG Grid comes with a UI and broader feature set, which just requires configuration." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or request a trial licence at [info@ag-grid.com](mailto:info@ag-grid.com) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } + ] + }, + { + "type": "contact", + "tag": "Get In Touch", + "heading": "Contact Our Team", + "subHeading": "Have questions about AG Grid? Get in touch with our team", + "features": [ + "Expert technical guidance", + "Demos & technical walkthroughs", + "Licencing and pricing information", + "Response within 24 hours" + ] + } + ] +} diff --git a/documentation/ag-grid-docs/src/content/landing-pages/vue-data-grid.json b/documentation/ag-grid-docs/src/content/landing-pages/vue-data-grid.json new file mode 100644 index 00000000000..d4e8464beac --- /dev/null +++ b/documentation/ag-grid-docs/src/content/landing-pages/vue-data-grid.json @@ -0,0 +1,260 @@ +{ + "meta": { + "title": "AG Grid: A Fast, Powerful, and Flexible Vue Data Grid", + "description": "Explore AG Grid's fast, powerful, and customisable Vue Data Grid library. Add high-performance Vue Data Grids to your Vue 3 applications in minutes, for free." + }, + "framework": "vue3", + "packageName": "ag-grid-vue3", + "docsPath": "vue-data-grid", + "analyticsPrefix": "vue-data-grid", + + "sections": [ + { + "type": "hero", + "tag": "Vue Data Grid", + "heading": "Fast, Powerful and Flexible Vue Data Grids", + "subHeading": "Vue Data Grids", + "subHeadingHtml": "Add high-performance, feature rich, and fully customisable Vue Data Grids to your application in minutes, all for free.", + "showVersionBadge": true, + "primaryCta": { + "text": "Get Started" + }, + "secondaryCta": { + "text": "View All Demos", + "url": "./example" + }, + "demo": { + "gridHeight": 600 + } + }, + { + "type": "features", + "tag": "Why Use AG Grid to Build Vue Data Grids?", + "heading": "Amazingly Fast and Fully Customisable with an Unbeatable Feature Set", + "subHeading": "Get started in minutes and access 1000s of features without compromising on performance. Customise your Vue Data Grid with your own styles and components or upgrade to enterprise to use our advanced features.", + "items": [ + { + "id": "showing-data", + "title": "Showing Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "showing-data-example" + }, + "features": [ + { + "heading": "Value Mapping", + "detail": "Map data to columns using field or valueGetter. Format values with valueFormatter for display.", + "link": "./vue-data-grid/column-definitions/" + }, + { + "heading": "Cell Components", + "detail": "Add buttons, checkboxes, or images to cells with custom cell renderers.", + "link": "./vue-data-grid/component-cell-renderer/" + }, + { + "heading": "Column Resizing", + "detail": "Resize columns by dragging header edges. Use flex values for responsive column widths.", + "link": "./vue-data-grid/column-sizing/" + } + ], + "docsLink": "./vue-data-grid/key-features/#showing-data" + }, + { + "id": "working-with-data", + "title": "Working with Data", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "working-with-data-example" + }, + "features": [ + { + "heading": "Filtering", + "detail": "Built-in column filters with text, number, and date types. Add floating filters for quick access.", + "link": "./vue-data-grid/filtering/" + }, + { + "heading": "Cell Editing", + "detail": "Enable inline editing with text, number, date, and select editors. Supports undo/redo.", + "link": "./vue-data-grid/cell-editing/" + }, + { + "heading": "Row Selection & Pagination", + "detail": "Select single or multiple rows with checkbox selection. Paginate large datasets with configurable page sizes.", + "link": "./vue-data-grid/row-selection/" + } + ], + "docsLink": "./vue-data-grid/key-features/#working-with-data" + }, + { + "id": "themes-style", + "title": "Themes & Style", + "isEnterprise": false, + "example": { + "pageName": "key-features", + "exampleName": "cell-row-style" + }, + "features": [ + { + "heading": "Theming", + "detail": "Choose from built-in themes (Quartz, Alpine, Balham) or create custom themes with the Theming API.", + "link": "./vue-data-grid/theming/" + }, + { + "heading": "Cell Styles", + "detail": "Apply dynamic styles to cells based on values using cellClassRules for conditional formatting.", + "link": "./vue-data-grid/cell-styles/" + }, + { + "heading": "Row Styles", + "detail": "Style entire rows based on data values with rowClassRules for visual indicators.", + "link": "./vue-data-grid/row-styles/" + } + ], + "docsLink": "./vue-data-grid/key-features/#themes--style" + }, + { + "id": "enterprise-features", + "title": "Enterprise Features", + "isEnterprise": true, + "example": { + "pageName": "key-features", + "exampleName": "enterprise-features-example" + }, + "features": [ + { + "heading": "Integrated Charts", + "detail": "Create and customize charts directly from grid data. Users can build their own visualizations.", + "link": "./vue-data-grid/integrated-charts/" + }, + { + "heading": "Row Grouping & Aggregation", + "detail": "Group rows by columns with automatic aggregations. Users can drag columns to group dynamically.", + "link": "./vue-data-grid/grouping/" + }, + { + "heading": "Tool Panels", + "detail": "Side panels for columns and filters let users customize their view without code changes.", + "link": "./vue-data-grid/tool-panel/" + } + ], + "docsLink": "./vue-data-grid/community-vs-enterprise/" + } + ] + }, + { + "type": "showcase", + "tag": "Where Can I See AG Grid Vue Data Grids Being Used?", + "heading": "Used in Every Industry, for All Types of Data", + "subHeading": "Trusted by 90% of Fortune 500 industries from Finance and AI, to DevTools and Aeronautics. Most of these uses are private, but we've hand-picked a few open-source examples:" + }, + { + "type": "customers", + "tag": "Who Builds Vue Data Grids with AG Grid?", + "headingHtml": "Loved By Developers
Trusted By The Worlds Largest Enterprises", + "subHeadingHtml": "Over 90% of the Fortune 500 build Vue Data Grids using AG Grid, with 1,000,000+ npm downloads per week and over 12,000 Stars on GitHub." + }, + { + "type": "examples", + "tag": "How Do I Build a Vue Data Grid with AG Grid?", + "heading": "Get Started with Vue Data Grid Examples", + "subHeading": "We have a range of examples, tutorials and documentation to help you start building your first Vue Data Grid with AG Grid.", + "showBackgroundGradient": true, + "items": [ + { + "img": "quick-start.png", + "imgAlt": "Quick start guide for creating a Vue Data Grid in minutes", + "title": "Quick Start", + "content": "Create your first Vue Data Grid and learn the basics in just a few minutes.", + "docs": "./vue-data-grid/getting-started", + "demo": "./examples/getting-started/quick-start-example/vue3/" + }, + { + "img": "beginner-tutorial.png", + "imgAlt": "Beginner tutorial for building a Vue Data Grid with key functionality", + "title": "Beginner Tutorial", + "content": "Learn key concepts by building a Vue Data Grid with common functionality & customisations.", + "docs": "./vue-data-grid/deep-dive/", + "demo": "./examples/deep-dive/testing-example/vue3" + }, + { + "img": "custom-themes.png", + "imgAlt": "Learn how to customise a Vue Data Grid with unique CSS themes", + "title": "Custom Styles", + "content": "Customise your Vue Data Grid with the Theming API to create a unique theme for your brand.", + "docs": "./vue-data-grid/theming/", + "demo": "./examples/theming-colors/color-customisation/vue3" + }, + { + "img": "integrated-charts.png", + "imgAlt": "Create and style Integrated Charts from your Vue Data Grid", + "title": "Integrated Charts", + "content": "Build & style charts programatically, or let users create their own from the grid.", + "docs": "./vue-data-grid/integrated-charts/", + "demo": "./examples/integrated-charts-chart-tool-panels/chart-tool-panels/vue3" + } + ] + }, + { + "type": "faq", + "tag": "Vue Data Grid FAQs", + "heading": "Frequently Asked Questions", + "subHeading": "Answers to some commonly asked questions when building Vue Data Grids with AG Grid", + "items": [ + { + "question": "What is the difference between AG Grid Community and Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "Can I create a Vue Data Grid for free?", + "answer": "Yes, you can create a Vue Data Grid for free using our open-source library, [AG Grid Community](./getting-started/)." + }, + { + "question": "Will my Vue Data Grids be compatible with AG Charts?", + "answer": "Yes, AG Grid is designed to be compatible with AG Charts, allowing seamless integration between Vue Data Grids and [Vue Charts](https://www.ag-grid.com/charts/vue/quick-start/)." + }, + { + "question": "Can I customise the Vue Data Grid?", + "answer": "Yes, you can customise AG Grid [themes](./themes/) with CSS, create your own custom themes, and use your own Vue components in cells. Visit our [Theme Builder](/theme-builder/) to start customising themes." + }, + { + "question": "Does AG Grid support Vue 3?", + "answer": "Yes, AG Grid fully supports Vue 3 with the `ag-grid-vue3` package. It works seamlessly with Vue 3's Composition API and script setup syntax." + }, + { + "question": "Can I use AG Grid with Nuxt?", + "answer": "Yes, AG Grid works with Nuxt. Check our documentation for guidance on integrating AG Grid into your Nuxt application." + }, + { + "question": "How Many Rows Can AG Grid Handle?", + "answer": "AG Grid can easily handle millions of rows and thousands of columns, with no performance degradation, by using row & column virtualisation." + }, + { + "question": "What is AG Grid Enterprise?", + "answer": "[AG Grid Community](./getting-started/) is the free version with basic features, while [AG Grid Enterprise](/license-pricing/) offers additional advanced features and support." + }, + { + "question": "What is an AG Grid licence?", + "answer": "An AG Grid licence is required to use [AG Grid Enterprise](/license-pricing/) in production. Try it for free, or [request a trial licence](./community-vs-enterprise/#request-a-30-day-enterprise-bundle-trial-licence) to remove the watermark & console messages." + }, + { + "question": "How much does AG Grid cost?", + "answer": "[AG Grid Community](./getting-started/) is free forever. [AG Grid Enterprise](/license-pricing/) costs $999 USD per licence. Visit the [Pricing page](/license-pricing/) for more information." + } + ] + }, + { + "type": "contact", + "tag": "Get In Touch", + "heading": "Contact Our Team", + "subHeading": "Have questions about AG Grid? Get in touch with our team", + "features": [ + "Expert technical guidance", + "Demos & technical walkthroughs", + "Licencing and pricing information", + "Response within 24 hours" + ] + } + ] +} diff --git a/documentation/ag-grid-docs/src/content/react-landing-page/examples.json b/documentation/ag-grid-docs/src/content/react-landing-page/examples.json deleted file mode 100644 index b17cb02a07c..00000000000 --- a/documentation/ag-grid-docs/src/content/react-landing-page/examples.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "img": "quick-start.png", - "imgAlt": "Quick start guide for creating a React Table in minutes", - "title": "Quick Start", - "content": "Create your first React Table and learn the basics in just a few minutes.", - "docs": "./react-data-grid/getting-started", - "demo": "./examples/getting-started/quick-start-example/reactFunctionalTs/" - }, - { - "img": "beginner-tutorial.png", - "imgAlt": "Beginner tutorial for building a React Table with key functionality", - "title": "Beginner Tutorial", - "content": "Learn key concepts by building a React Table with common functionality & customisations.", - "docs": "./react-data-grid/deep-dive/", - "demo": "./examples/deep-dive/testing-example/reactFunctionalTs" - }, - { - "img": "custom-themes.png", - "imgAlt": "Learn how to customise a React Table with unique CSS themes", - "title": "Custom Styles", - "content": "Customise your React Table with the Theming API to create a unique theme for your brand.", - "docs": "./react-data-grid/theming/", - "demo": "./examples/theming-colors/color-customisation/reactFunctionalTs" - }, - { - "img": "integrated-charts.png", - "imgAlt": "Create and style Integrated Charts from your React Table", - "title": "Integrated Charts", - "content": "Build & style charts programatically, or let users create their own from the table.", - "docs": "./react-data-grid/integrated-charts/", - "demo": "./examples/integrated-charts-chart-tool-panels/chart-tool-panels/reactFunctionalTs" - } -] diff --git a/documentation/ag-grid-docs/src/pages-styles/enterprise.module.scss b/documentation/ag-grid-docs/src/pages-styles/enterprise.module.scss new file mode 100644 index 00000000000..1921c1fa88e --- /dev/null +++ b/documentation/ag-grid-docs/src/pages-styles/enterprise.module.scss @@ -0,0 +1,596 @@ +@use 'design-system' as *; +@use './framework-shared' as frameworkShared; + +.heroSection { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + max-width: calc(var(--layout-max-width-small) + var(--layout-horizontal-margins) * 2); + margin-right: auto; + margin-left: auto; + padding: 48px var(--layout-horizontal-margins) 96px; +} + +.blueRectangle { + position: absolute; + top: 56%; + left: 50%; + transform: translate(-50%, -50%); + width: 97.5%; + height: 62.5%; + background: linear-gradient(0deg, #dde9f9 -61.34%, #ffffff 89.79%); + z-index: -999; + border-radius: 8px; + border-left: 1px solid linear-gradient(0deg, #dde9f9 -61.34%, #0e449126 89.79%); + border-right: 1px solid linear-gradient(0deg, #dde9f9 -61.34%, #0e449126 89.79%); + border-bottom: 1px solid #0e449126; + + // Limit height on very tall screens to prevent overlap with customer logos + @media screen and (min-height: 1200px) { + max-height: 100px; + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + position: absolute; + bottom: 0; + width: 100%; + height: 100%; + background: linear-gradient(0deg, #dde9f9 -61.34%, #ffffff 89.79%); + z-index: -999; + border-radius: 8px; + border: none; + } + + #{$selector-darkmode} & { + background: linear-gradient(0deg, #29384b -61.34%, #141d2c 89.79%); + border-left: 1px solid linear-gradient(0deg, #29384b -61.34%, #f1bbde26 89.79%); + border-right: 1px solid linear-gradient(0deg, #29384b -61.34%, #f1bbde26 89.79%); + border-bottom: 1px solid #f1bbde26; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + background: linear-gradient(0deg, #29384b -61.34%, #141d2c 89.79%); + border: none; + } + } +} + +.heroSectionInner { + max-width: calc(var(--layout-max-width) + var(--layout-horizontal-margins) * 2); + + @media screen and (max-width: $breakpoint-landing-page-medium) { + max-width: 100%; + } +} + +.heroSectionContainer { + display: flex; + flex-direction: column; + flex-wrap: wrap; + justify-content: center; + gap: $spacing-size-8; + max-width: 100%; + + // Add padding on very tall screens to push customer logos below blue rectangle + @media screen and (min-height: 1200px) { + padding-bottom: $spacing-size-12; + } +} + +.heroSectionHeadingContainer { + display: flex; + flex-direction: column; + margin-top: $spacing-size-12; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + margin-top: $spacing-size-4; + } +} + +.heroSectionVersionTagContainer { + display: flex; + border-radius: var(--radius-4xl); + justify-content: center; + align-items: center; + cursor: pointer; + align-self: center; + background-color: var(--color-brand-200); + font-weight: var(--text-base); + + #{$selector-darkmode} & { + background-color: var(--color-bg-secondary); + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + width: 100%; + } +} + +.heroSectionVersion { + border: 2px solid var(--color-util-brand-200); + background: var(--color-white); + border-radius: var(--radius-4xl); + margin: 2px; + padding: 0px $spacing-size-2; + font-size: var(--text-fs-sm); + font-weight: var(--text-base); + color: var(--color-brand-500); + + #{$selector-darkmode} & { + color: var(--color-button-primary-bg); + background: var(--color-bg-primary); + border: 2px solid var(--color-bg-secondary); + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + display: none; + } +} + +.heroSectionFeatureHighlight { + display: flex; + align-items: center; + padding: 0px $spacing-size-2; + font-size: var(--text-fs-sm); + font-weight: var(--text-base); + color: var(--color-brand-500); + + @media screen and (max-width: $breakpoint-landing-page-medium) { + font-size: var(--text-fs-sm); + text-align: center; + padding: $spacing-size-1 $spacing-size-2; + } + + #{$selector-darkmode} & { + color: white; + } + + span { + @media screen and (min-width: $breakpoint-landing-page-medium) { + display: inline-block; + max-width: 66ch; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } +} + +.heroSectionFeatureArrow { + flex-shrink: 0; + height: 16px !important; + width: 16px !important; + margin-left: $spacing-size-2 !important; + fill: var(--color-brand-500) !important; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + margin-left: 0; + } + + #{$selector-darkmode} & { + fill: var(--color-button-primary-bg) !important; + } +} + +.heroSectionTag { + color: var(--color-util-brand-500); + font-size: 20px; + text-align: center; + line-height: 24px; + font-weight: var(--text-bold); + margin-top: $spacing-size-8; + margin-bottom: 0; + + #{$selector-darkmode} & { + color: var(--color-brand-100); + } +} + +.heroSectionHeading { + font-size: 72px; + text-align: center; + line-height: 77px; + margin-bottom: $spacing-size-8; + padding: 0px $spacing-size-64; + font-weight: var(--text-bold); + + @media screen and (max-width: $breakpoint-landing-page-medium) { + font-size: 52px; + padding: 0; + line-height: 55px; + } +} + +.heroSectionSubHeading { + @include frameworkShared.hero-section-subheading; +} + +.heroSectionButtonContainer { + @include frameworkShared.hero-section-button-container; +} + +.heroSectionCta1 { + color: var(--color-bg-primary); + background: var(--color-button-primary-bg); + align-content: center; + padding-bottom: 6px; + + &:hover { + color: var(--color-bg-primary) !important; + background: var(--color-button-primary-bg-hover); + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + text-align: center; + } +} + +.heroSectionCta2 { + min-width: 380px; + @include frameworkShared.hero-section-cta2; +} + +.heroSectionNote { + display: flex; + align-items: center; + justify-content: center; + gap: $spacing-size-2; + font-size: var(--text-fs-sm); + color: var(--color-fg-secondary); + text-align: center; + margin: 0; + padding: $spacing-size-2 $spacing-size-4; + background-color: var(--color-bg-secondary); + border-radius: var(--radius-md); +} + +.infoIcon { + flex-shrink: 0; + height: 16px !important; + width: 16px !important; + fill: var(--color-fg-secondary) !important; +} + +.heroSectionDemoContainer { + @include frameworkShared.hero-section-demo-container; +} + +.heroSectionViewAllDemosLink { + display: inline-flex; + align-items: center; + align-self: flex-end; + gap: $spacing-size-2; + width: fit-content; + padding: 0.5em 0.5em 0.5em 1em; + + :global(.icon) { + --icon-size: #{$spacing-size-4}; + + transform: translate(-3px, 1px); + transition: transform $transition-default-timing; + } + + &:hover :global(.icon) { + transform: translate(4px, 1px); + } +} + +.heroSectionDemosArrow { + height: 16px !important; + width: 16px !important; + margin-left: $spacing-size-2 !important; + fill: var(--color-link) !important; +} + +// Examples Section +.examplesSection { + --gradient-top-offset: 60%; + + background: var(--color-bg-secondary); +} + +.examplesSectionContainer { + --horizontal-gap: #{$spacing-size-4}; + + display: flex; + flex-direction: column; + gap: $spacing-size-8 var(--horizontal-gap); + + @media screen and (min-width: $breakpoint-landing-page-small) { + flex-direction: row; + } + + @media screen and (min-width: $breakpoint-landing-page-small) and (max-width: $breakpoint-landing-page-medium) { + flex-wrap: wrap; + } +} + +.examplesSectionCard { + border-radius: var(--radius-md); + border: 1px solid var(--color-border-secondary); + background-color: var(--color-bg-primary); + + @media screen and (min-width: $breakpoint-landing-page-small) { + width: calc(50% - var(--horizontal-gap) / 2); + } +} + +.examplesSectionCardImage { + width: 100%; + height: 200px; + object-fit: none; + object-position: 0 0; + border-right: 1px solid var(--color-gray-200); + border-radius: var(--radius-md) var(--radius-md) 0 0; + + #{$selector-darkmode} & { + filter: brightness(0.9); + mix-blend-mode: lighten; + } +} + +.examplesSectionCardDetails { + display: flex; + flex-grow: 1; + font-size: 16px; + flex-direction: column; + padding: $spacing-size-4 $spacing-size-4 0px; + border-top: 1px solid var(--color-border-secondary); +} + +.examplesSectionCardCta { + display: flex; + justify-content: flex-end; + gap: $spacing-size-4; + padding: $spacing-size-4; + border-radius: 0 0 var(--radius-md) var(--radius-md); +} + +// Pricing Section +.pricingContainer { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: $spacing-size-8; + width: 100%; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + grid-template-columns: 1fr; + } +} + +.pricingCard { + display: flex; + flex-direction: column; + padding: $spacing-size-8; + background: var(--color-bg-primary); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border-primary); + + h3 { + font-size: var(--text-fs-xl); + font-weight: var(--text-bold); + margin-bottom: $spacing-size-4; + } +} + +.pricingPrice { + font-size: 48px; + font-weight: var(--text-bold); + color: var(--color-brand-500); + margin-bottom: $spacing-size-1; + + #{$selector-darkmode} & { + color: var(--color-brand-200); + } +} + +.pricingPriceFree { + font-size: 48px; + font-weight: var(--text-bold); + color: var(--color-fg-primary); + margin-bottom: $spacing-size-1; +} + +.pricingPriceNote { + font-size: var(--text-fs-sm); + color: var(--color-fg-secondary); + margin-bottom: $spacing-size-6; +} + +.pricingFeatures { + list-style: none; + padding: 0; + margin: 0 0 $spacing-size-8 0; + flex-grow: 1; + + li { + padding: $spacing-size-2 0; + font-size: var(--text-fs-md); + color: var(--color-fg-primary); + display: flex; + align-items: center; + gap: $spacing-size-2; + + &::before { + content: ''; + display: inline-block; + width: 20px; + height: 20px; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230E4491'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E") + no-repeat center; + background-size: contain; + + #{$selector-darkmode} & { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23d4e3f8'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E") + no-repeat center; + background-size: contain; + } + } + } +} + +.pricingCta { + width: 100%; + text-align: center; +} + +.pricingTrialCta { + width: 100%; + text-align: center; + margin-top: $spacing-size-2; +} + +.trialCard { + display: flex; + flex-direction: column; + justify-content: center; + padding: $spacing-size-8; + background: linear-gradient(135deg, var(--color-brand-100) 0%, var(--color-brand-200) 100%); + border-radius: var(--radius-lg); + border: 2px solid var(--color-brand-300); + + #{$selector-darkmode} & { + background: linear-gradient(135deg, var(--color-util-gray-300) 0%, var(--color-util-gray-400) 100%); + border-color: var(--color-util-gray-200); + } + + h3 { + font-size: var(--text-fs-xl); + font-weight: var(--text-bold); + margin-bottom: $spacing-size-4; + color: var(--color-brand-700); + + #{$selector-darkmode} & { + color: var(--color-brand-300); + } + } + + p { + font-size: var(--text-fs-md); + color: var(--color-fg-secondary); + margin-bottom: $spacing-size-6; + flex-grow: 1; + } +} + +.trialCta { + width: 100%; + display: flex; + justify-content: center; +} + +// Integrated Charts Section +.integratedChartsContainer { + width: 100%; + min-height: 500px; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + min-height: 400px; + } +} + +// Theme Builder Section +.themeBuilderContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: $spacing-size-6; + width: 100%; +} + +.themeBuilderVideoWrapper { + width: 100%; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + border: 1px solid var(--color-border-primary); + overflow: hidden; + + div { + display: block !important; + margin-bottom: 0 !important; + } + + video { + display: block; + width: 100%; + } +} + +.themeBuilderCta { + display: inline-flex; + align-items: center; + gap: $spacing-size-2; +} + +// Contact Form Section +.contactSection { + display: flex; + flex-direction: column; + gap: $spacing-size-12; + width: 100%; + + @media screen and (min-width: $breakpoint-landing-page-medium) { + flex-direction: row; + gap: $spacing-size-16; + } +} + +.contactInfo { + flex: 1; + display: flex; + flex-direction: column; + gap: $spacing-size-8; +} + +.contactFeatures { + display: flex; + flex-direction: column; + gap: $spacing-size-4; +} + +.contactFeature { + display: flex; + align-items: center; + gap: $spacing-size-3; + font-size: var(--text-fs-lg); + color: var(--color-fg-primary); +} + +.contactFeatureIcon { + flex-shrink: 0; + width: 24px !important; + height: 24px !important; + fill: var(--color-util-brand-500) !important; + + #{$selector-darkmode} & { + fill: var(--color-util-brand-400) !important; + } +} + +.contactLogos { + margin-top: auto; + padding-top: $spacing-size-8; + border-top: 1px solid var(--color-border-secondary); +} + +.contactLogosLabel { + font-size: var(--text-fs-sm); + color: var(--color-fg-secondary); + margin: 0 0 $spacing-size-4 0; +} + +.contactFormWrapper { + flex: 1; + max-width: 500px; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + max-width: 100%; + } +} + +// Enterprise Trial Section +.enterpriseTrialSection { + margin-top: $spacing-size-12; + border-top: none !important; +} diff --git a/documentation/ag-grid-docs/src/pages-styles/react-table.module.scss b/documentation/ag-grid-docs/src/pages-styles/framework-landing-page.module.scss similarity index 68% rename from documentation/ag-grid-docs/src/pages-styles/react-table.module.scss rename to documentation/ag-grid-docs/src/pages-styles/framework-landing-page.module.scss index 930e392930f..68e043ad7ba 100644 --- a/documentation/ag-grid-docs/src/pages-styles/react-table.module.scss +++ b/documentation/ag-grid-docs/src/pages-styles/framework-landing-page.module.scss @@ -1,4 +1,5 @@ @use 'design-system' as *; +@use './framework-shared' as frameworkShared; .heroSection { display: flex; @@ -26,6 +27,11 @@ border-right: 1px solid linear-gradient(0deg, #dde9f9 -61.34%, #0e449126 89.79%); border-bottom: 1px solid #0e449126; + // Limit height on very tall screens to prevent overlap with customer logos + @media screen and (min-height: 1200px) { + max-height: 100px; + } + @media screen and (max-width: $breakpoint-landing-page-medium) { position: absolute; bottom: 0; @@ -64,6 +70,7 @@ justify-content: center; align-items: center; text-align: center; + max-width: 800px; margin-bottom: $spacing-size-12; gap: $spacing-size-2; } @@ -89,6 +96,11 @@ justify-content: center; gap: $spacing-size-8; max-width: 100%; + + // Add padding on very tall screens to push customer logos below blue rectangle + @media screen and (min-height: 1200px) { + padding-bottom: $spacing-size-12; + } } .heroSectionHeadingContainer { @@ -127,7 +139,7 @@ margin: 2px; padding: 0px $spacing-size-2; font-size: var(--text-fs-sm); - font-weight: var(--text-base); + font-weight: var(--text-bold); color: var(--color-brand-500); #{$selector-darkmode} & { @@ -187,13 +199,17 @@ } .heroSectionTag { - color: var(--color-brand-400); - font-size: 24px; + color: var(--color-util-brand-500); + font-size: 20px; text-align: center; line-height: 24px; font-weight: var(--text-bold); margin-top: $spacing-size-8; margin-bottom: 0; + + #{$selector-darkmode} & { + color: var(--color-brand-100); + } } .heroSectionHeading { @@ -209,31 +225,16 @@ padding: 0; line-height: 55px; } + + @include frameworkShared.framework-logo-hero-heading; } .heroSectionSubHeading { - font-size: var(--text-fs-lg); - font-weight: var(--text-base); - color: var(--color-text-primary); - margin: 0px $spacing-size-48; - text-align: center; - margin-bottom: 0px; - - @media screen and (max-width: $breakpoint-landing-page-medium) { - margin: 0; - padding: 0; - } + @include frameworkShared.hero-section-subheading; } .heroSectionButtonContainer { - display: flex; - justify-content: center; - min-height: 48px; - gap: $spacing-size-4; - - @media screen and (max-width: $breakpoint-landing-page-medium) { - flex-direction: column; - } + @include frameworkShared.hero-section-button-container; } .heroSectionCta1 { @@ -253,48 +254,71 @@ } .heroSectionCta2 { - display: flex; + @include frameworkShared.hero-section-cta2; + min-width: 315px; - align-items: center; +} + +.heroSectionDemoContainer { + @include frameworkShared.hero-section-demo-container; +} + +.heroLinks { + position: relative; + display: flex; gap: $spacing-size-2; - padding: $spacing-size-2; - border-radius: var(--radius-md); - background-color: var(--color-brand-200); - color: var(--color-gray-900); - font-family: var(--text-monospace-font-family); - justify-content: center; + align-items: center; + margin: $spacing-size-4 0 0; + justify-content: flex-end; - #{$selector-darkmode} & { - background-color: var(--color-button-tertiary-bg-hover); + :global(.button-tertiary):hover { + background-color: color-mix(in srgb, var(--color-button-tertiary-bg), transparent 33.3333%); } - @media screen and (max-width: $breakpoint-landing-page-medium) { - min-width: auto; - width: 100%; + a svg { + --icon-size: #{$spacing-size-6}; + + margin-left: -$spacing-size-1; + margin-right: $spacing-size-1; } } -.heroSectionDemoContainer { - display: flex; - flex-direction: column; - height: 600px; +.heroSectioncta1 { + display: inline-flex; + align-items: center; + gap: $spacing-size-2; + width: fit-content; + padding: 0.5em 0.5em 0.5em 1em; + + :global(.icon) { + --icon-size: #{$spacing-size-4}; + + transform: translate(-3px, 1px); + transition: transform $transition-default-timing; + } + + &:hover :global(.icon) { + transform: translate(4px, 1px); + } } .heroSectionViewAllDemosLink { - appearance: none; - padding: 0; - text-align: left; + display: inline-flex; align-self: flex-end; - font-weight: var(--text-regular); - color: var(--color-link); - transition: color 0.25s ease-in-out; - border: none; - background-color: transparent; - box-shadow: none; + align-items: center; + gap: $spacing-size-2; + width: fit-content; + padding: 0.5em 0.5em 0.5em 1em; - &:hover { - background: transparent; - color: var(--color-link) !important; + :global(.icon) { + --icon-size: #{$spacing-size-4}; + + transform: translate(-3px, 1px); + transition: transform $transition-default-timing; + } + + &:hover :global(.icon) { + transform: translate(4px, 1px); } } @@ -305,6 +329,11 @@ fill: var(--color-link) !important; } +// Examples Section +.examplesSection { + --gradient-top-offset: 50% !important; +} + .examplesSectionContainer { --horizontal-gap: #{$spacing-size-4}; @@ -323,11 +352,8 @@ .examplesSectionCard { border-radius: var(--radius-md); - background: var(--color-gray-50); - - #{$selector-darkmode} & { - background: var(--color-gray-800); - } + border: 1px solid var(--color-border-secondary); + background-color: var(--color-bg-primary); @media screen and (min-width: $breakpoint-landing-page-small) { width: calc(50% - var(--horizontal-gap) / 2); @@ -354,6 +380,7 @@ font-size: 16px; flex-direction: column; padding: $spacing-size-4 $spacing-size-4 0px; + border-top: 1px solid var(--color-border-secondary); } .examplesSectionCardCta { @@ -363,3 +390,84 @@ padding: $spacing-size-4; border-radius: 0 0 var(--radius-md) var(--radius-md); } + +// Contact Form Section +.contactSection { + display: flex; + flex-direction: column; + gap: $spacing-size-12; + width: 100%; + + @media screen and (min-width: $breakpoint-landing-page-medium) { + flex-direction: row; + gap: $spacing-size-16; + } +} + +.contactInfo { + flex: 1; + display: flex; + flex-direction: column; + gap: $spacing-size-8; +} + +.contactFeatures { + display: flex; + flex-direction: column; + gap: $spacing-size-4; +} + +.contactFeature { + display: flex; + align-items: center; + gap: $spacing-size-3; + font-size: var(--text-fs-lg); + color: var(--color-fg-primary); +} + +.contactFeatureIcon { + flex-shrink: 0; + width: 24px !important; + height: 24px !important; + fill: var(--color-util-brand-500) !important; + + #{$selector-darkmode} & { + fill: var(--color-util-brand-400) !important; + } +} + +.contactLogos { + margin-top: auto; + padding-top: $spacing-size-8; + border-top: 1px solid var(--color-border-secondary); +} + +.contactLogosLabel { + font-size: var(--text-fs-sm); + color: var(--color-fg-secondary); + margin: 0 0 $spacing-size-4 0; +} + +.contactFormWrapper { + flex: 1; + max-width: 500px; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + max-width: 100%; + } +} + +// Framework-specific headingContainer override +// Use descendant selector to target shared component's headingContainer on framework pages +:global { + header[class*='headingContainer'] { + max-width: 800px; + gap: 24px; + } + + // Style framework logos in section headings + h3[class*='heading'] { + max-width: 600px; + @include frameworkShared.framework-logo-heading; + } +} diff --git a/documentation/ag-grid-docs/src/pages-styles/framework-shared.scss b/documentation/ag-grid-docs/src/pages-styles/framework-shared.scss new file mode 100644 index 00000000000..a9f99b61fec --- /dev/null +++ b/documentation/ag-grid-docs/src/pages-styles/framework-shared.scss @@ -0,0 +1,151 @@ +// Shared SCSS for framework-related landing pages and enterprise hero: +// - Framework landing pages (e.g. /react-data-grid, /angular-data-grid, /vue-data-grid) +// via styles in framework-landing-page.module.scss +// - Enterprise landing page (/enterprise-data-grid) +// via styles in enterprise.module.scss + +@use 'design-system' as *; + +@mixin hero-section-subheading { + font-size: var(--text-fs-lg); + font-weight: var(--text-base); + color: var(--color-text-primary); + max-width: 600px; + margin: 16px auto; + line-height: 1.5; + text-align: center; + animation: subHeadingFadeInUp 0.7s ease 600ms; + opacity: 0; + animation-fill-mode: forwards; + + @media screen and (max-width: $breakpoint-landing-page-medium) { + margin: 0; + padding: 0; + } +} + +@keyframes subHeadingFadeInUp { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@mixin hero-section-cta2 { + display: flex; + align-items: center; + gap: $spacing-size-2; + padding: $spacing-size-2; + border-radius: var(--radius-md); + background-color: var(--color-brand-200); + color: var(--color-gray-900); + font-family: var(--text-monospace-font-family); + justify-content: center; + + #{$selector-darkmode} & { + background-color: var(--color-button-tertiary-bg-hover); + + svg:hover { + fill: var(--color-brand-100) !important; + } + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + min-width: auto; + width: 100%; + } +} + +@mixin hero-section-button-container { + display: flex; + justify-content: center; + min-height: 48px; + gap: $spacing-size-4; + animation: fadeIn 0.7s ease 1300ms; + opacity: 0; + animation-fill-mode: forwards; + + @media screen and (min-width: $breakpoint-landing-page-medium) { + margin-bottom: 48px; + } + + @media screen and (max-width: $breakpoint-landing-page-medium) { + flex-direction: column; + } +} + +@mixin hero-section-demo-container { + display: flex; + flex-direction: column; + height: 600px; + animation: fadeIn 0.7s ease 2000ms; + opacity: 0; + animation-fill-mode: forwards; + z-index: 0; + + // Ensure Finance component doesn't cause layout shift + > * { + opacity: 1; + } +} + +// Framework logo styling for headings +@mixin framework-logo-heading { + img { + display: inline-block; + vertical-align: middle; + height: 56px; + width: auto; + margin: 0 0.2em; + } + + span[style*='display: inline-block'] { + display: inline-block; + white-space: nowrap; + vertical-align: baseline; + } +} + +// Framework logo styling for hero section headings (larger logo) +@mixin framework-logo-hero-heading { + img { + display: inline-block; + vertical-align: middle; + height: 56px; + width: auto; + margin: 0 0.2em; + animation: logoFadeInScale 300ms ease; + opacity: 0; + animation-fill-mode: forwards; + } + + .frameworkLogoGroup { + display: inline-block; + white-space: nowrap; + vertical-align: baseline; + } +} + +@keyframes logoFadeInScale { + from { + opacity: 0; + transform: scale(0.7); + } + to { + opacity: 1; + transform: scale(1); + } +} diff --git a/documentation/ag-grid-docs/src/pages/index.astro b/documentation/ag-grid-docs/src/pages/index.astro index d3aff391140..21497e74d8e 100644 --- a/documentation/ag-grid-docs/src/pages/index.astro +++ b/documentation/ag-grid-docs/src/pages/index.astro @@ -9,7 +9,7 @@ import InstallText from '@components/framework-landing-pages/InstallText'; import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; import { Version } from '@ag-website-shared/components/whats-new/components/Version'; import { Icon } from '@ag-website-shared/components/icon/Icon'; -import Showcase from '../components/framework-landing-pages/react/sections/showcase/Showcase'; +import Showcase from '../components/showcase/Showcase'; import { parseVersion } from '@ag-website-shared/utils/parseVersion'; import whatsNewData from '@ag-website-shared/content/whats-new/data.json'; import { LandingPageSection } from '@ag-website-shared/components/landing-pages/LandingPageSection'; diff --git a/documentation/ag-grid-docs/src/pages/landing-pages/angular-data-grid.astro b/documentation/ag-grid-docs/src/pages/landing-pages/angular-data-grid.astro new file mode 100644 index 00000000000..312c5dd7d54 --- /dev/null +++ b/documentation/ag-grid-docs/src/pages/landing-pages/angular-data-grid.astro @@ -0,0 +1,18 @@ +--- +/** + * Angular Data Grid Landing Page + * + * Content is defined in: src/content/landing-pages/angular-data-grid.json + */ + +import LandingPage from '@components/landing-pages/LandingPage.astro'; +import { getEntry } from 'astro:content'; + +const contentEntry = await getEntry('landingPages', 'angular-data-grid'); +const versionsEntry = await getEntry('versions', 'ag-grid-versions'); + +const content = contentEntry?.data; +const versions = versionsEntry?.data; +--- + +{content && } diff --git a/documentation/ag-grid-docs/src/pages/landing-pages/enterprise-data-grid.astro b/documentation/ag-grid-docs/src/pages/landing-pages/enterprise-data-grid.astro new file mode 100644 index 00000000000..ac890bd43ca --- /dev/null +++ b/documentation/ag-grid-docs/src/pages/landing-pages/enterprise-data-grid.astro @@ -0,0 +1,18 @@ +--- +/** + * Enterprise Data Grid Landing Page + * + * Content is defined in: src/content/landing-pages/enterprise-data-grid.json + */ + +import LandingPage from '@components/landing-pages/LandingPage.astro'; +import { getEntry } from 'astro:content'; + +const contentEntry = await getEntry('landingPages', 'enterprise-data-grid'); +const versionsEntry = await getEntry('versions', 'ag-grid-versions'); + +const content = contentEntry?.data; +const versions = versionsEntry?.data; +--- + +{content && } diff --git a/documentation/ag-grid-docs/src/pages/landing-pages/javascript-data-grid.astro b/documentation/ag-grid-docs/src/pages/landing-pages/javascript-data-grid.astro new file mode 100644 index 00000000000..c6f7dd7b401 --- /dev/null +++ b/documentation/ag-grid-docs/src/pages/landing-pages/javascript-data-grid.astro @@ -0,0 +1,18 @@ +--- +/** + * JavaScript Data Grid Landing Page + * + * Content is defined in: src/content/landing-pages/javascript-data-grid.json + */ + +import LandingPage from '@components/landing-pages/LandingPage.astro'; +import { getEntry } from 'astro:content'; + +const contentEntry = await getEntry('landingPages', 'javascript-data-grid'); +const versionsEntry = await getEntry('versions', 'ag-grid-versions'); + +const content = contentEntry?.data; +const versions = versionsEntry?.data; +--- + +{content && } diff --git a/documentation/ag-grid-docs/src/pages/landing-pages/react-data-grid.astro b/documentation/ag-grid-docs/src/pages/landing-pages/react-data-grid.astro new file mode 100644 index 00000000000..a55874c45de --- /dev/null +++ b/documentation/ag-grid-docs/src/pages/landing-pages/react-data-grid.astro @@ -0,0 +1,18 @@ +--- +/** + * React Data Grid Landing Page + * + * Content is defined in: src/content/landing-pages/react-data-grid.json + */ + +import LandingPage from '@components/landing-pages/LandingPage.astro'; +import { getEntry } from 'astro:content'; + +const contentEntry = await getEntry('landingPages', 'react-data-grid'); +const versionsEntry = await getEntry('versions', 'ag-grid-versions'); + +const content = contentEntry?.data; +const versions = versionsEntry?.data; +--- + +{content && } diff --git a/documentation/ag-grid-docs/src/pages/landing-pages/vue-data-grid.astro b/documentation/ag-grid-docs/src/pages/landing-pages/vue-data-grid.astro new file mode 100644 index 00000000000..026b003af12 --- /dev/null +++ b/documentation/ag-grid-docs/src/pages/landing-pages/vue-data-grid.astro @@ -0,0 +1,18 @@ +--- +/** + * Vue Data Grid Landing Page + * + * Content is defined in: src/content/landing-pages/vue-data-grid.json + */ + +import LandingPage from '@components/landing-pages/LandingPage.astro'; +import { getEntry } from 'astro:content'; + +const contentEntry = await getEntry('landingPages', 'vue-data-grid'); +const versionsEntry = await getEntry('versions', 'ag-grid-versions'); + +const content = contentEntry?.data; +const versions = versionsEntry?.data; +--- + +{content && } diff --git a/documentation/ag-grid-docs/src/pages/react-table.astro b/documentation/ag-grid-docs/src/pages/react-table.astro index 0fddd679d25..10477a6df50 100644 --- a/documentation/ag-grid-docs/src/pages/react-table.astro +++ b/documentation/ag-grid-docs/src/pages/react-table.astro @@ -1,166 +1,18 @@ --- -import Layout from '../layouts/Layout.astro'; -import { getEntry, type CollectionEntry } from 'astro:content'; -import styles from '../pages-styles/react-table.module.scss'; -import Customers from '../components/framework-landing-pages/react/sections/customers/Customers'; -import { LandingPageFAQ } from '@ag-website-shared/components/landing-pages/LandingPageFAQ'; -import FeaturesSection from '../components/framework-landing-pages/react/sections/features/FeaturesSection'; -import Showcase from '../components/framework-landing-pages/react/sections/showcase/Showcase'; -import { LandingPageSection } from '@ag-website-shared/components/landing-pages/LandingPageSection'; -import GithubSlugger from 'github-slugger'; +/** + * React Table Landing Page + * + * Content is defined in: src/content/landing-pages/react-table.json + */ -import InstallText from '@components/framework-landing-pages/InstallTextReact'; -import { Icon } from '@ag-website-shared/components/icon/Icon'; -import { Finance } from 'src/components/demos/examples/finance'; -import { urlWithBaseUrl } from '@utils/urlWithBaseUrl'; +import LandingPage from '@components/landing-pages/LandingPage.astro'; +import { getEntry } from 'astro:content'; -const slugger = new GithubSlugger(); -const { data: faqData } = (await getEntry('faqs', 'react-landing-page')) as CollectionEntry<'faqs'>; -const { data: versionsData } = (await getEntry('versions', 'ag-grid-versions')) as CollectionEntry<'versions'>; -const { data: examples } = (await getEntry('reactLandingPage', 'examples')) as CollectionEntry<'reactLandingPage'>; -const latestVersionWithHighlightData = versionsData.find((version) => version.landingPageHighlight); ---- - - -
-
-
-
-
- - AG Grid v{latestVersionWithHighlightData?.version} - - { - latestVersionWithHighlightData?.landingPageHighlight && ( - {latestVersionWithHighlightData?.landingPageHighlight} - ) - } - - - -

React Table

-

Fast, Powerful and Flexible React Tables

-

- Add high-performance, feature rich, and fully customisable React Data Tables - to your application in minutes, all for free. -

-
-
- Get Started -
- -
-
-
- -
- View All Demos - -
-
-
- - - - - - - - +const contentEntry = await getEntry('landingPages', 'react-table'); +const versionsEntry = await getEntry('versions', 'ag-grid-versions'); - Trusted By The Worlds Largest Enterprises'} - subHeadingHtml={`Over 90% of the Fortune 500 build React Tables using AG Grid, with 1,000,000+ npm downloads per week and over 12,000 Stars on GitHub.`} - > - - - - -
- { - examples.map((card) => ( -
- {card.imgAlt} - -
-

{card.title}

-

{card.content}

-
- - -
- )) - } -
-
+const content = contentEntry?.data; +const versions = versionsEntry?.data; +--- - - - -
+{content && } diff --git a/documentation/ag-grid-docs/src/stores/darkmodeStore.ts b/documentation/ag-grid-docs/src/stores/darkmodeStore.ts index 150673eada1..fc860767780 100644 --- a/documentation/ag-grid-docs/src/stores/darkmodeStore.ts +++ b/documentation/ag-grid-docs/src/stores/darkmodeStore.ts @@ -23,7 +23,7 @@ const updateHtml = (darkmode: boolean | undefined) => { htmlEl.classList.add('no-transitions'); htmlEl.dataset.darkMode = darkmode === true ? 'true' : 'false'; htmlEl.dataset.agThemeMode = htmlEl.dataset.darkMode === 'true' ? 'dark-blue' : 'light'; - htmlEl.offsetHeight; // Trigger a reflow, flushing the CSS changes + void htmlEl.offsetHeight; // Trigger a reflow, flushing the CSS changes htmlEl.classList.remove('no-transitions'); const darkModeEvent = { type: 'color-scheme-change', darkmode }; diff --git a/documentation/ag-grid-docs/src/utils/framework-landing-page-utils.ts b/documentation/ag-grid-docs/src/utils/framework-landing-page-utils.ts new file mode 100644 index 00000000000..667de60ea83 --- /dev/null +++ b/documentation/ag-grid-docs/src/utils/framework-landing-page-utils.ts @@ -0,0 +1,107 @@ +/** + * Utility functions for framework landing pages + * Handles framework logo integration in headings + */ + +// Map frameworks to their logo and display name +// Only used for framework landing pages: react-data-grid, javascript-data-grid, vue-data-grid, angular-data-grid +export const frameworkLogoMap: Record = { + react: { logo: 'react.svg', name: 'React' }, + angular: { logo: 'angular.svg', name: 'Angular' }, + vue: { logo: 'vue.svg', name: 'Vue' }, + javascript: { logo: 'javascript.svg', name: 'JavaScript' }, +}; + +/** + * Normalize framework key from JSON content to frameworkLogoMap key + */ +export function normalizeFrameworkKey(frameworkKey: string | undefined): string { + if (!frameworkKey) { + return ''; + } + + // Check if the key exists directly in the map + if (frameworkLogoMap[frameworkKey]) { + return frameworkKey; + } + + // Map framework variants to base framework keys + const variantMapping: Record = { + vue3: 'vue', + reactfunctionalt: 'react', + typescript: 'javascript', + }; + + const normalized = frameworkKey.toLowerCase(); + if (variantMapping[frameworkKey] || variantMapping[normalized]) { + return variantMapping[frameworkKey] || variantMapping[normalized] || ''; + } + + // Check lowercase version as fallback + if (frameworkLogoMap[normalized]) { + return normalized; + } + + return frameworkKey; +} + +/** + * Generate heading HTML with framework logo + * Replaces framework name in heading template with logo image + text + */ +export function getHeadingWithLogo( + frameworkKey: string, + headingTemplate: string, + urlWithBaseUrl: (url: string) => string +): string { + const normalizedKey = normalizeFrameworkKey(frameworkKey); + const frameworkInfo = frameworkLogoMap[normalizedKey] || frameworkLogoMap['react']; + const logoUrl = urlWithBaseUrl(`/images/fw-logos/${frameworkInfo.logo}`); + const frameworkName = frameworkInfo.name; + + // Replace framework name with logo + text + const logoHtml = `${frameworkName} ${frameworkName}`; + let heading = headingTemplate.replace(frameworkName, logoHtml); + + // Add
before "Data Grids" or "Data Grid" + heading = heading.replace(/\s+(Data Grids?)/i, '
$1'); + + return heading; +} + +/** + * Parse heading and replace framework name with logo + text (for hero sections with 56px logo) + * Returns array of parts to render as JSX + */ +export function parseHeading( + heading: string, + frameworkKey: string +): Array<{ type: 'text' | 'logoGroup'; content?: string; logo?: string; name?: string }> { + const normalizedKey = normalizeFrameworkKey(frameworkKey); + const frameworkInfo = frameworkLogoMap[normalizedKey]; + if (!frameworkInfo) { + return [{ type: 'text', content: heading }]; + } + + const regex = new RegExp(`\\b${frameworkInfo.name}\\b`); + const parts = heading.split(regex); + + if (parts.length === 1) { + return [{ type: 'text', content: heading }]; + } + + const result: Array<{ type: 'text' | 'logoGroup'; content?: string; logo?: string; name?: string }> = []; + for (let i = 0; i < parts.length; i++) { + if (parts[i]) { + result.push({ type: 'text', content: parts[i] }); + } + if (i < parts.length - 1) { + result.push({ + type: 'logoGroup', + logo: frameworkInfo.logo, + name: frameworkInfo.name, + }); + } + } + return result; +} diff --git a/documentation/ag-grid-docs/src/utils/grid/generator-utils.ts b/documentation/ag-grid-docs/src/utils/grid/generator-utils.ts index 55c723f6eac..2b47767b507 100644 --- a/documentation/ag-grid-docs/src/utils/grid/generator-utils.ts +++ b/documentation/ag-grid-docs/src/utils/grid/generator-utils.ts @@ -15,11 +15,11 @@ type GeneratorState = 'started' | 'stopped'; export function createGenerator({ interval = 1000, callback }: { interval: number; callback: () => void }) { let currentInterval = interval; let state: GeneratorState = 'stopped'; - let timeout; + let timeout: ReturnType; const createTimeout = () => { return setTimeout(() => { - callback && callback(); + callback?.(); if (state !== 'stopped') { timeout = createTimeout(); diff --git a/eslint.config.mjs b/eslint.config.mjs index e6eca5bfba7..8a2dd20adf4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -94,6 +94,15 @@ export default [ varsIgnorePattern: '^_+$', }, ], + // Disallow using logical short-circuit expressions as control flow (eg. `a && fn()`). + '@typescript-eslint/no-unused-expressions': [ + 'error', + { + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + }, + ], 'no-undef': 'warn', 'no-lonely-if': 'error', curly: 'error', diff --git a/external/ag-website-shared/.gitrepo b/external/ag-website-shared/.gitrepo index 469929631f1..c3c0b52d90a 100644 --- a/external/ag-website-shared/.gitrepo +++ b/external/ag-website-shared/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:ag-grid/ag-website-shared.git branch = latest - commit = a701350c8fc83c8ad26587b4b69aebdee38ebac6 - parent = dc261a546090054552486f2b3a7519821ba4df14 + commit = 003c5fd1401692753d61d7000f9cb4e74acb3fd7 + parent = 62335acdb581e9c162bb4f0d4d2a0ded1ec2673e method = rebase cmdver = 0.4.9 diff --git a/external/ag-website-shared/src/components/icon/Icon.tsx b/external/ag-website-shared/src/components/icon/Icon.tsx index 8a99b187fa5..cd9806306a4 100644 --- a/external/ag-website-shared/src/components/icon/Icon.tsx +++ b/external/ag-website-shared/src/components/icon/Icon.tsx @@ -151,6 +151,7 @@ export const ICON_MAP = { tutorials: Tutorials, concepts: Concepts, alarm: CarbonIcon.Alarm, + terminal: CarbonIcon.Terminal, pricingFeatures: CarbonIcon.CicsProgram, support: CarbonIcon.Chat, ...SOCIALS_ICON_MAP, diff --git a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallText.module.scss b/external/ag-website-shared/src/components/install-text/InstallText.module.scss similarity index 98% rename from documentation/ag-grid-docs/src/components/framework-landing-pages/InstallText.module.scss rename to external/ag-website-shared/src/components/install-text/InstallText.module.scss index 02e69eab5e9..198367f9a9b 100644 --- a/documentation/ag-grid-docs/src/components/framework-landing-pages/InstallText.module.scss +++ b/external/ag-website-shared/src/components/install-text/InstallText.module.scss @@ -84,18 +84,16 @@ } .copyToClipboardIcon { - --icon-color: var(--color-white); opacity: 0.8; - width: var(--icon-size); height: var(--icon-size); } .installText { + color: var(--color-fg-primary); display: flex; align-items: center; gap: 8px; - color: var(--color-white); border-radius: 4px; font-family: var(--text-monospace-font-family); } diff --git a/external/ag-website-shared/src/components/install-text/InstallText.tsx b/external/ag-website-shared/src/components/install-text/InstallText.tsx new file mode 100644 index 00000000000..6c1dba098cb --- /dev/null +++ b/external/ag-website-shared/src/components/install-text/InstallText.tsx @@ -0,0 +1,50 @@ +import { Icon } from '@ag-website-shared/components/icon/Icon'; +import { useRef, useState } from 'react'; + +import styles from './InstallText.module.scss'; + +interface InstallTextProps { + packageName: string; +} + +export function InstallText({ packageName }: InstallTextProps) { + const [iconState, setIconState] = useState<'copy' | 'animating' | 'tick'>('copy'); + const installTextRef = useRef(null); + + const installCommand = `npm install ${packageName}`; + + const copyToClipboard = () => { + navigator.clipboard.writeText(installCommand).then(() => { + setIconState('animating'); + + setTimeout(() => { + setIconState('tick'); + + setTimeout(() => { + setIconState('copy'); + }, 2000); + }, 200); + }); + }; + + return ( +
+ + + $ + {installCommand} + + + + + {iconState === 'copy' && ( + + )} + {iconState === 'animating' &&
} + {iconState === 'tick' && ( + + )} +
+
+ ); +} diff --git a/external/ag-website-shared/src/components/install-text/index.ts b/external/ag-website-shared/src/components/install-text/index.ts new file mode 100644 index 00000000000..a67b37bbc89 --- /dev/null +++ b/external/ag-website-shared/src/components/install-text/index.ts @@ -0,0 +1 @@ +export { InstallText } from './InstallText'; diff --git a/external/ag-website-shared/src/components/landing-pages/sections/ContactSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/ContactSection.astro new file mode 100644 index 00000000000..1ed409eb3f8 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/ContactSection.astro @@ -0,0 +1,38 @@ +--- +import type { ContactSection, LandingPageContent } from '../types'; +import { LandingPageSection } from '../LandingPageSection'; +import { Icon } from '@ag-website-shared/components/icon/Icon'; +import { ContactForm } from '@ag-website-shared/components/contact-form/ContactForm'; + +interface Props { + section: ContactSection; + pageContent: LandingPageContent; + styles: Record; +} + +const { section, styles } = Astro.props; +--- + + +
+
+
+ { + section.features.map((feature) => ( +
+ + {feature} +
+ )) + } +
+
+

Trusted by millions of developers:

+ +
+
+
+ +
+
+
diff --git a/external/ag-website-shared/src/components/landing-pages/sections/CustomersSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/CustomersSection.astro new file mode 100644 index 00000000000..21afe61ab5f --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/CustomersSection.astro @@ -0,0 +1,20 @@ +--- +import type { CustomersSection, LandingPageContent } from '../types'; +import { LandingPageSection } from '../LandingPageSection'; + +interface Props { + section: CustomersSection; + pageContent: LandingPageContent; +} + +const { section } = Astro.props; +--- + + + + diff --git a/external/ag-website-shared/src/components/landing-pages/sections/ExamplesSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/ExamplesSection.astro new file mode 100644 index 00000000000..0af11de6795 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/ExamplesSection.astro @@ -0,0 +1,50 @@ +--- +import type { ExamplesSection, LandingPageContent } from '../types'; +import { LandingPageSection } from '../LandingPageSection'; + +interface Props { + section: ExamplesSection; + pageContent: LandingPageContent; + urlWithBaseUrl: (url: string) => string; + styles: Record; +} + +const { section, urlWithBaseUrl, styles } = Astro.props; +--- + + +
+ { + section.items.map((card) => ( +
+ {card.imgAlt} + +
+

{card.title}

+

{card.content}

+
+ + +
+ )) + } +
+
diff --git a/external/ag-website-shared/src/components/landing-pages/sections/FAQSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/FAQSection.astro new file mode 100644 index 00000000000..1ae2e1fde66 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/FAQSection.astro @@ -0,0 +1,19 @@ +--- +import type { FAQSection, LandingPageContent } from '../types'; +import { LandingPageSection } from '../LandingPageSection'; +import { LandingPageFAQ } from '../LandingPageFAQ'; + +interface Props { + section: FAQSection; + pageContent: LandingPageContent; +} + +const { section, pageContent } = Astro.props; + +// Transform items to the format expected by LandingPageFAQ +const faqData = section.items; +--- + + + + diff --git a/external/ag-website-shared/src/components/landing-pages/sections/FeaturesSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/FeaturesSection.astro new file mode 100644 index 00000000000..0b1bee81296 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/FeaturesSection.astro @@ -0,0 +1,39 @@ +--- +import type { FeaturesSection, LandingPageContent } from '../types'; +import { LandingPageSection } from '../LandingPageSection'; + +interface Props { + section: FeaturesSection; + pageContent: LandingPageContent; +} + +const { section } = Astro.props; +--- + + + + +
+ { + section.items.map((item) => ( +
+

{item.title}

+
    + {item.features.map((f) => ( +
  • + {f.heading}: {f.detail} +
  • + ))} +
+
+ )) + } +
+
+
diff --git a/external/ag-website-shared/src/components/landing-pages/sections/HeroSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/HeroSection.astro new file mode 100644 index 00000000000..f1d63516b75 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/HeroSection.astro @@ -0,0 +1,142 @@ +--- +/** + * HeroSection - Landing page hero section component + * + * All content comes from the JSON content file. No hardcoded text. + * + * Uses named slots to allow site-specific components to be passed in: + * - hero-demo: The main demo component (Finance grid, etc.) + * - hero-install: Package install text component + * - hero-trial-button: Trial button component (when primaryCta.useTrialButton is true) + * - hero-logos: Customer logos component + */ +import type { HeroSection, LandingPageContent } from '../types'; +import { Icon } from '@ag-website-shared/components/icon/Icon'; + +interface Props { + section: HeroSection; + pageContent: LandingPageContent; + latestVersion?: { + version: string; + landingPageHighlight?: string; + }; + urlWithBaseUrl: (url: string) => string; + styles: Record; +} + +const { section, pageContent, latestVersion, urlWithBaseUrl, styles } = Astro.props; + +// Resolve URLs with defaults +const primaryCtaUrl = section.primaryCta?.url + ? urlWithBaseUrl(section.primaryCta.url) + : urlWithBaseUrl(`./${pageContent.docsPath}/getting-started/`); + +const secondaryCtaUrl = section.secondaryCta?.url + ? urlWithBaseUrl(section.secondaryCta.url) + : urlWithBaseUrl('./example'); + +// Analytics event names +const primaryCtaAnalytics = section.primaryCta?.useTrialButton + ? `${pageContent.analyticsPrefix}-trial-cta` + : `${pageContent.analyticsPrefix}-get-started`; + +const secondaryCtaAnalytics = `${pageContent.analyticsPrefix}-view-demos`; + +// Product name for version badge (defaults to 'AG Grid' for backwards compatibility) +const productName = pageContent.productName ?? 'AG Grid'; + +// Whether to show customer logos (defaults to true) +const showCustomerLogos = section.showCustomerLogos !== false; +--- + +
+
+
+
+
+ { + section.showVersionBadge && latestVersion && ( + + + {productName} v{latestVersion.version} + + {latestVersion.landingPageHighlight && ( + + {latestVersion.landingPageHighlight} + + + )} + + ) + } +

{section.tag}

+ { + section.headingHtml ? ( +

+ ) : ( +

{section.heading}

+ ) + } + { + section.subHeadingHtml ? ( +

+ ) : ( +

{section.subHeading}

+ ) + } +
+
+ { + section.primaryCta?.useTrialButton ? ( + + ) : section.primaryCta ? ( + + {section.primaryCta.text} + + ) : null + } + { + pageContent.packageName && ( +
+ +
+ ) + } +
+
+ +
+ { + section.secondaryCta && ( + + {section.secondaryCta.text} + + + ) + } + {showCustomerLogos && } +
+
+
diff --git a/external/ag-website-shared/src/components/landing-pages/sections/ShowcaseSection.astro b/external/ag-website-shared/src/components/landing-pages/sections/ShowcaseSection.astro new file mode 100644 index 00000000000..4b52573e629 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/sections/ShowcaseSection.astro @@ -0,0 +1,15 @@ +--- +import type { ShowcaseSection, LandingPageContent } from '../types'; +import { LandingPageSection } from '../LandingPageSection'; + +interface Props { + section: ShowcaseSection; + pageContent: LandingPageContent; +} + +const { section } = Astro.props; +--- + + + + diff --git a/external/ag-website-shared/src/components/landing-pages/types.ts b/external/ag-website-shared/src/components/landing-pages/types.ts new file mode 100644 index 00000000000..36f6cf5d166 --- /dev/null +++ b/external/ag-website-shared/src/components/landing-pages/types.ts @@ -0,0 +1,221 @@ +// ============================================================================ +// Section Content Types +// ============================================================================ + +export interface HeroCta { + text: string; + url?: string; + /** If true, renders a trial button component instead of a regular link */ + useTrialButton?: boolean; +} + +export interface HeroGalleryExample { + /** Display title shown in the gallery */ + title: string; + /** Gallery example name or docs example reference */ + exampleName: string; + /** Optional: docs page name if this is a docs example (not gallery) */ + pageName?: string; + /** Thumbnail image URL for navigation */ + thumbnail?: string; +} + +export interface HeroSection { + type: 'hero'; + variant?: 'default' | 'enterprise'; + tag: string; + heading: string; + /** + * HTML heading with formatting. If provided, takes precedence over heading. + * Use for framework pages that need framework logos in headings. + */ + headingHtml?: string; + /** Plain text subheading */ + subHeading: string; + /** + * HTML subheading with formatting. If provided, takes precedence over subHeading. + * Use for framework pages that need: "Add high-performance..." template + */ + subHeadingHtml?: string; + showVersionBadge?: boolean; + /** Whether to show customer logos in the hero section (default: true) */ + showCustomerLogos?: boolean; + /** Primary CTA button (e.g., "Get Started" or "Start Free Trial") */ + primaryCta?: HeroCta; + /** Secondary CTA link below the demo (e.g., "View All Demos") */ + secondaryCta?: HeroCta; + /** Demo grid configuration (AG Grid specific) */ + demo?: { + enableRowGroup?: boolean; + gridHeight?: number; + }; + /** Gallery examples for sliding gallery (AG Charts specific) */ + galleryExamples?: HeroGalleryExample[]; + /** Height of the hero demo */ + demoHeight?: number; +} + +export interface FeatureItem { + id: string; + title: string; + isEnterprise?: boolean; + example: { + pageName: string; + exampleName: string; + }; + features: Array<{ + heading: string; + detail: string; + link?: string; + }>; + docsLink: string; +} + +export interface FeaturesSection { + type: 'features'; + tag: string; + heading: string; + headingHtml?: string; + subHeading: string; + items: FeatureItem[]; +} + +export interface ShowcaseSection { + type: 'showcase'; + tag: string; + heading: string; + subHeading: string; +} + +export interface CustomersSection { + type: 'customers'; + tag: string; + headingHtml: string; + subHeadingHtml: string; + displayLogos?: boolean; +} + +export interface ExampleItem { + img: string; + imgAlt: string; + title: string; + content: string; + docs: string; + demo: string; +} + +export interface ExamplesSection { + type: 'examples'; + tag: string; + heading: string; + subHeading: string; + showBackgroundGradient?: boolean; + items: ExampleItem[]; +} + +export interface FAQItem { + question: string; + answer: string; +} + +export interface FAQSection { + type: 'faq'; + tag: string; + heading: string; + subHeading: string; + items: FAQItem[]; +} + +export interface ContactSection { + type: 'contact'; + variant?: 'default' | 'sales'; + tag: string; + heading: string; + subHeading: string; + features: string[]; +} + +export interface IntegratedChartsSection { + type: 'integrated-charts'; + tag: string; + heading?: string; + headingHtml?: string; + subHeading?: string; + subHeadingHtml?: string; + showBackgroundGradient?: boolean; +} + +export interface ThemeBuilderSection { + type: 'theme-builder'; + tag: string; + heading: string; + subHeading: string; +} + +export interface ComparisonSection { + type: 'comparison'; + tag: string; + heading: string; + subHeading: string; + showBackgroundGradient?: boolean; +} + +export interface PricingCard { + title: string; + price: string; + priceNote: string; + features: string[]; + ctaText: string; + ctaUrl: string; + ctaId?: string; + isPrimary?: boolean; + showTrialButton?: boolean; +} + +export interface PricingSection { + type: 'pricing'; + tag: string; + heading: string; + subHeading: string; + showBackgroundGradient?: boolean; + cards: PricingCard[]; +} + +// Union of all section types +export type LandingPageSectionType = + | HeroSection + | FeaturesSection + | ShowcaseSection + | CustomersSection + | ExamplesSection + | FAQSection + | ContactSection + | IntegratedChartsSection + | ThemeBuilderSection + | ComparisonSection + | PricingSection; + +// ============================================================================ +// Landing Page Content +// ============================================================================ + +export interface LandingPageContent { + meta: { + title: string; + description: string; + }; + /** Product name for display (e.g., 'AG Grid', 'AG Charts') */ + productName?: string; + /** Framework identifier for examples (e.g., 'reactFunctionalTs', 'angular', 'vue3') */ + framework?: string; + packageName?: string; + docsPath: string; + analyticsPrefix: string; + sections: LandingPageSectionType[]; +} + +// ============================================================================ +// Helper type to extract section by type +// ============================================================================ + +export type ExtractSection = Extract; diff --git a/external/ag-website-shared/src/components/license-pricing/EnterpriseTrial.tsx b/external/ag-website-shared/src/components/license-pricing/EnterpriseTrial.tsx new file mode 100644 index 00000000000..f825a74afb6 --- /dev/null +++ b/external/ag-website-shared/src/components/license-pricing/EnterpriseTrial.tsx @@ -0,0 +1,75 @@ +import { Icon } from '@ag-website-shared/components/icon/Icon'; +import { gridUrlWithPrefix } from '@ag-website-shared/utils/gridUrlWithPrefix'; +import { useFrameworkFromStore } from '@utils/hooks/useFrameworkFromStore'; +import classnames from 'classnames'; +import type { FunctionComponent } from 'react'; + +import styles from './license-pricing.module.scss'; + +interface Props { + className?: string; +} + +export const EnterpriseTrial: FunctionComponent = ({ className }) => { + const framework = useFrameworkFromStore(); + + return ( +
+
+

+ + Enterprise Bundle Trial +

+ +

See the differences between our community and enterprise versions and request a trial license

+ + + Get a trial license + +
+ +
+ +
+
+ +

+ Two Week Trial +
+ Trial licences are valid for two weeks from the date of issue, or{' '} + contact us to extend. +

+
+ +
+ +
+ +

+ Suppresses Console Warnings +
+ Removes console errors and watermarks from AG Grid and AG Chart components. +

+
+ +
+ +
+ +

+ Access Support +
+ Access dedicated support from our engineering team via{' '} + Zendesk. +

+
+
+
+ ); +}; diff --git a/external/ag-website-shared/src/components/trial-licence-modal/TrialButton.tsx b/external/ag-website-shared/src/components/trial-licence-modal/TrialButton.tsx new file mode 100644 index 00000000000..14f2953022f --- /dev/null +++ b/external/ag-website-shared/src/components/trial-licence-modal/TrialButton.tsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react'; + +import { TrialLicenceModal } from './TrialLicenceModal'; + +interface TrialButtonProps { + id?: string; + className?: string; + children: React.ReactNode; +} + +export const TrialButton: React.FC = ({ id = 'request-trial-licence', className, children }) => { + const [isModalOpen, setIsModalOpen] = useState(false); + + return ( + <> + + setIsModalOpen(false)} /> + + ); +}; + +export default TrialButton; diff --git a/external/ag-website-shared/src/components/trial-licence-modal/TrialLicenceModal.module.scss b/external/ag-website-shared/src/components/trial-licence-modal/TrialLicenceModal.module.scss new file mode 100644 index 00000000000..9e752762854 --- /dev/null +++ b/external/ag-website-shared/src/components/trial-licence-modal/TrialLicenceModal.module.scss @@ -0,0 +1,94 @@ +@use 'design-system' as *; + +.backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + padding: $spacing-size-4; + + #{$selector-darkmode} & { + background: rgba(0, 0, 0, 0.7); + } +} + +.modal { + position: relative; + background: var(--color-bg-primary); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + max-width: 500px; + width: 100%; + max-height: 90vh; + overflow-y: auto; + animation: modalSlideIn 0.2s ease-out; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.closeButton { + position: absolute; + top: $spacing-size-4; + right: $spacing-size-4; + background: transparent; + border: 1px solid var(--color-button-secondary-border); + cursor: pointer; + padding: $spacing-size-2; + border-radius: var(--radius-md); + transition: + background-color 0.15s ease, + color 0.15s ease; + + &:hover { + background: var(--color-button-primary-bg-hover); + color: var(--color-fg-primary); + } + + svg { + width: 20px; + height: 20px; + fill: var(--color-button-secondary-border); + } +} + +.content { + padding: $spacing-size-8; + + @media screen and (max-width: $breakpoint-landing-page-small) { + padding: $spacing-size-6; + } +} + +.title { + font-size: var(--text-fs-2xl); + font-weight: var(--text-bold); + color: var(--color-fg-primary); + margin: 0 0 $spacing-size-2 0; + padding-right: $spacing-size-8; +} + +.description { + font-size: var(--text-fs-md); + color: var(--color-fg-secondary); + margin: 0 0 $spacing-size-6 0; + line-height: 1.5; +} + +.formContainer { + // The TrialLicenceForm component styles itself +} diff --git a/external/ag-website-shared/src/components/trial-licence-modal/TrialLicenceModal.tsx b/external/ag-website-shared/src/components/trial-licence-modal/TrialLicenceModal.tsx new file mode 100644 index 00000000000..6795dc6fedc --- /dev/null +++ b/external/ag-website-shared/src/components/trial-licence-modal/TrialLicenceModal.tsx @@ -0,0 +1,76 @@ +import { Icon } from '@ag-website-shared/components/icon/Icon'; +import { TrialLicenceForm } from '@ag-website-shared/components/trial-licence-form/TrialLicenceForm'; +import React, { useEffect, useRef } from 'react'; +import { createPortal } from 'react-dom'; + +import styles from './TrialLicenceModal.module.scss'; + +interface TrialLicenceModalProps { + isOpen: boolean; + onClose: () => void; +} + +export const TrialLicenceModal: React.FC = ({ isOpen, onClose }) => { + const modalRef = useRef(null); + + // Handle escape key + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, onClose]); + + // Prevent body scroll when modal is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [isOpen]); + + // Handle click outside modal + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + if (!isOpen) return null; + + // Use portal to render at document body level, escaping any stacking contexts + const modalContent = ( +
+
+ +
+

Request a Trial Licence

+

+ Get a free two-week Enterprise Bundle trial licence. This will remove the watermark and console + errors during your evaluation. +

+
+ +
+
+
+
+ ); + + // Check for SSR - document.body may not exist during server-side rendering + if (typeof document === 'undefined') return null; + + return createPortal(modalContent, document.body); +}; + +export default TrialLicenceModal; diff --git a/external/ag-website-shared/src/components/trial-licence-modal/index.ts b/external/ag-website-shared/src/components/trial-licence-modal/index.ts new file mode 100644 index 00000000000..7a746ffaa45 --- /dev/null +++ b/external/ag-website-shared/src/components/trial-licence-modal/index.ts @@ -0,0 +1,2 @@ +export { TrialButton } from './TrialButton'; +export { TrialLicenceModal } from './TrialLicenceModal'; diff --git a/packages/ag-grid-community/src/agStack/events/localEventService.ts b/packages/ag-grid-community/src/agStack/events/localEventService.ts index af09bc904fb..cc3900e9096 100644 --- a/packages/ag-grid-community/src/agStack/events/localEventService.ts +++ b/packages/ag-grid-community/src/agStack/events/localEventService.ts @@ -153,7 +153,12 @@ export class LocalEventService implements IEventEmitt const flush = () => { window.setTimeout(this.flushAsyncQueue.bind(this), 0); }; - this.frameworkOverrides ? this.frameworkOverrides.wrapIncoming(flush) : flush(); + const frameworkOverrides = this.frameworkOverrides; + if (frameworkOverrides) { + frameworkOverrides.wrapIncoming(flush); + } else { + flush(); + } // mark that it is scheduled this.scheduled = true; } diff --git a/packages/ag-grid-community/src/agStack/focus/agTabGuardFeature.ts b/packages/ag-grid-community/src/agStack/focus/agTabGuardFeature.ts index 585fc887a5d..dc20be1e2e9 100644 --- a/packages/ag-grid-community/src/agStack/focus/agTabGuardFeature.ts +++ b/packages/ag-grid-community/src/agStack/focus/agTabGuardFeature.ts @@ -72,9 +72,11 @@ export class AgTabGuardFeature< const compProxy: ITabGuard = { setTabIndex: (tabIndex) => { for (const tabGuard of tabGuards) { - tabIndex == null - ? tabGuard.removeAttribute('tabindex') - : tabGuard.setAttribute('tabindex', tabIndex); + if (tabIndex == null) { + tabGuard.removeAttribute('tabindex'); + } else { + tabGuard.setAttribute('tabindex', tabIndex); + } } }, }; diff --git a/packages/ag-grid-community/src/columns/baseColsService.ts b/packages/ag-grid-community/src/columns/baseColsService.ts index 9b83ad136ef..a0784029b1c 100644 --- a/packages/ag-grid-community/src/columns/baseColsService.ts +++ b/packages/ag-grid-community/src/columns/baseColsService.ts @@ -123,7 +123,9 @@ export abstract class BaseColsService extends BeanStub implements IColsService { columnCallback(column, added, source); } - autoGroupsNeedBuilding && this.colModel.refreshCols(false, source); + if (autoGroupsNeedBuilding) { + this.colModel.refreshCols(false, source); + } this.visibleCols.refresh(source); @@ -256,7 +258,11 @@ export abstract class BaseColsService extends BeanStub implements IColsService { if (include) { const useIndex = colIsNew ? index != null || initialIndex != null : index != null; - useIndex ? colsWithIndex.push(col) : colsWithValue.push(col); + if (useIndex) { + colsWithIndex.push(col); + } else { + colsWithValue.push(col); + } } } diff --git a/packages/ag-grid-community/src/columns/selectionColService.ts b/packages/ag-grid-community/src/columns/selectionColService.ts index 6667c2caf2c..9df5bedf7f7 100644 --- a/packages/ag-grid-community/src/columns/selectionColService.ts +++ b/packages/ag-grid-community/src/columns/selectionColService.ts @@ -247,7 +247,9 @@ export class SelectionColService extends BeanStub implements NamedBean, IColumnC default: cols = centerCols; } - cols && _removeFromArray(cols, column); + if (cols) { + _removeFromArray(cols, column); + } }; const rowNumbersCol = this.beans.rowNumbersSvc?.getColumn(ROW_NUMBERS_COLUMN_ID); diff --git a/packages/ag-grid-community/src/edit/editModelService.ts b/packages/ag-grid-community/src/edit/editModelService.ts index 5d7b6d6f13b..8f51e2a8d15 100644 --- a/packages/ag-grid-community/src/edit/editModelService.ts +++ b/packages/ag-grid-community/src/edit/editModelService.ts @@ -163,7 +163,10 @@ export class EditModelService extends BeanStub implements NamedBean, IEditModelS } public setEdit(position: Required, edit: Partial): Readonly { - (this.edits.size === 0 || !this.edits.has(position.rowNode)) && this.edits.set(position.rowNode, new Map()); + const edits = this.edits; + if (edits.size === 0 || !edits.has(position.rowNode)) { + edits.set(position.rowNode, new Map()); + } const currentEdit = this._getEdit(position); diff --git a/packages/ag-grid-community/src/edit/editService.ts b/packages/ag-grid-community/src/edit/editService.ts index 6f455a95ac7..627af749d12 100644 --- a/packages/ag-grid-community/src/edit/editService.ts +++ b/packages/ag-grid-community/src/edit/editService.ts @@ -271,7 +271,9 @@ export class EditService extends BeanStub implements NamedBean, IEditService { const res = this.shouldStartEditing(position, event, startedEdit, source); if (res === false && source !== 'api') { - this.isEditing(position) && this.stopEditing(); + if (this.isEditing(position)) { + this.stopEditing(); + } return; } @@ -757,13 +759,17 @@ export class EditService extends BeanStub implements NamedBean, IEditService { if (this.cellEditingInvalidCommitBlocks()) { (event as Event)?.preventDefault?.(); if (focus) { - !cellCtrl?.hasBrowserFocus() && cellCtrl?.focusCell(); + if (cellCtrl && !cellCtrl.hasBrowserFocus()) { + cellCtrl.focusCell(); + } cellCtrl?.comp?.getCellEditor()?.focusIn?.(); } return 'block-stop'; } - cellCtrl && this.revertSingleCellEdit(cellCtrl); + if (cellCtrl) { + this.revertSingleCellEdit(cellCtrl); + } return 'revert-continue'; } diff --git a/packages/ag-grid-community/src/edit/strategy/baseEditStrategy.ts b/packages/ag-grid-community/src/edit/strategy/baseEditStrategy.ts index 89d7269bc6f..db8d9edb161 100644 --- a/packages/ag-grid-community/src/edit/strategy/baseEditStrategy.ts +++ b/packages/ag-grid-community/src/edit/strategy/baseEditStrategy.ts @@ -163,9 +163,9 @@ export abstract class BaseEditStrategy extends BeanStub { if (actions.keep.length > 0) { for (const cell of actions.keep) { const cellCtrl = _getCellCtrl(this.beans, cell); - - if (!this.editSvc?.cellEditingInvalidCommitBlocks()) { - cellCtrl && this.editSvc.revertSingleCellEdit(cellCtrl); + const editSvc = this.editSvc; + if (!editSvc?.cellEditingInvalidCommitBlocks() && cellCtrl) { + editSvc.revertSingleCellEdit(cellCtrl); } } } diff --git a/packages/ag-grid-community/src/pinnedRowModel/staticPinnedRowModel.ts b/packages/ag-grid-community/src/pinnedRowModel/staticPinnedRowModel.ts index 42acd2ecc9d..5ea40295e2b 100644 --- a/packages/ag-grid-community/src/pinnedRowModel/staticPinnedRowModel.ts +++ b/packages/ag-grid-community/src/pinnedRowModel/staticPinnedRowModel.ts @@ -253,7 +253,9 @@ function forEach( ): void { cache.order.forEach((id, index) => { const node = getById(cache, id); - node && callback(node, index); + if (node) { + callback(node, index); + } }); } diff --git a/packages/ag-grid-community/src/rendering/cell/cellMouseListenerFeature.ts b/packages/ag-grid-community/src/rendering/cell/cellMouseListenerFeature.ts index 4a83832de63..7588abfd1cb 100644 --- a/packages/ag-grid-community/src/rendering/cell/cellMouseListenerFeature.ts +++ b/packages/ag-grid-community/src/rendering/cell/cellMouseListenerFeature.ts @@ -144,9 +144,10 @@ export class CellMouseListenerFeature extends BeanStub { if (editSvc?.shouldStartEditing(cellCtrl, event) && editModelSvc?.getState(cellCtrl) !== 'editing') { const editing = editSvc?.isEditing(); + const isRangeSelectionEnabledWhileEditing = editSvc?.isRangeSelectionEnabledWhileEditing(); const cellValidations = editModelSvc?.getCellValidationModel().getCellValidationMap().size ?? 0; const rowValidations = editModelSvc?.getRowValidationModel().getRowValidationMap().size ?? 0; - if (editing && (cellValidations > 0 || rowValidations > 0)) { + if (editing && (isRangeSelectionEnabledWhileEditing || cellValidations > 0 || rowValidations > 0)) { return; } diff --git a/packages/ag-grid-community/src/rendering/cellRenderers/skeletonCellRenderer.ts b/packages/ag-grid-community/src/rendering/cellRenderers/skeletonCellRenderer.ts index 30611fc6776..251facd08b9 100644 --- a/packages/ag-grid-community/src/rendering/cellRenderers/skeletonCellRenderer.ts +++ b/packages/ag-grid-community/src/rendering/cellRenderers/skeletonCellRenderer.ts @@ -19,8 +19,10 @@ export class SkeletonCellRenderer extends Component implements ILoadingCellRende if (params.deferRender) { this.setupLoading(params); + } else if (params.node.failedLoad) { + this.setupFailed(); } else { - params.node.failedLoad ? this.setupFailed() : this.setupLoading(params); + this.setupLoading(params); } } diff --git a/packages/ag-grid-community/src/rendering/row/rowCtrl.ts b/packages/ag-grid-community/src/rendering/row/rowCtrl.ts index 44e089b4d56..c707ba0f97d 100644 --- a/packages/ag-grid-community/src/rendering/row/rowCtrl.ts +++ b/packages/ag-grid-community/src/rendering/row/rowCtrl.ts @@ -1848,7 +1848,11 @@ export class RowCtrl extends BeanStub { private setRowTopStyle(topPx: string): void { for (const gui of this.allRowGuis) { - this.suppressRowTransform ? gui.rowComp.setTop(topPx) : gui.rowComp.setTransform(`translateY(${topPx})`); + if (this.suppressRowTransform) { + gui.rowComp.setTop(topPx); + } else { + gui.rowComp.setTransform(`translateY(${topPx})`); + } } } diff --git a/packages/ag-grid-community/src/styling/stylingUtils.ts b/packages/ag-grid-community/src/styling/stylingUtils.ts index b39520143b9..e064dfba139 100644 --- a/packages/ag-grid-community/src/styling/stylingUtils.ts +++ b/packages/ag-grid-community/src/styling/stylingUtils.ts @@ -42,7 +42,11 @@ export function processClassRules( } forEachSingleClass(className, (singleClass) => { - resultOfRule ? (classesToApply[singleClass] = true) : (classesToRemove[singleClass] = true); + if (resultOfRule) { + classesToApply[singleClass] = true; + } else { + classesToRemove[singleClass] = true; + } }); } } diff --git a/packages/ag-grid-enterprise/src/cellRenderers/loadingCellRenderer.ts b/packages/ag-grid-enterprise/src/cellRenderers/loadingCellRenderer.ts index 92019185433..122bd3e959a 100644 --- a/packages/ag-grid-enterprise/src/cellRenderers/loadingCellRenderer.ts +++ b/packages/ag-grid-enterprise/src/cellRenderers/loadingCellRenderer.ts @@ -18,7 +18,11 @@ export class LoadingCellRenderer extends Component implements ILoadingCellRender } public init(params: ILoadingCellRendererParams): void { - params.node.failedLoad ? this.setupFailed() : this.setupLoading(); + if (params.node.failedLoad) { + this.setupFailed(); + } else { + this.setupLoading(); + } } private setupFailed(): void { diff --git a/packages/ag-grid-enterprise/src/charts/chartComp/chartController.ts b/packages/ag-grid-enterprise/src/charts/chartComp/chartController.ts index a65dc6f93ca..96c3f5e1e39 100644 --- a/packages/ag-grid-enterprise/src/charts/chartComp/chartController.ts +++ b/packages/ag-grid-enterprise/src/charts/chartComp/chartController.ts @@ -143,7 +143,11 @@ export class ChartController extends BeanStub { // if the chart should be unlinked or chart ranges suppressed, remove all cell ranges; otherwise, set the chart range const removeChartCellRanges = chartModelParams.unlinkChart || chartModelParams.suppressChartRanges; - removeChartCellRanges ? this.rangeSvc?.setCellRanges([]) : this.setChartRange(); + if (removeChartCellRanges) { + this.rangeSvc?.setCellRanges([]); + } else { + this.setChartRange(); + } } public updateForGridChange(params?: { maintainColState?: boolean; setColsFromRange?: boolean }): void { diff --git a/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/cartesian/cartesianChartProxy.ts b/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/cartesian/cartesianChartProxy.ts index 4fd8524fb3e..a4b1f9015ec 100644 --- a/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/cartesian/cartesianChartProxy.ts +++ b/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/cartesian/cartesianChartProxy.ts @@ -210,7 +210,11 @@ export abstract class CartesianChartProxy< } private crossFilteringAddSelectedPoint(multiSelection: boolean, value: string): void { - multiSelection ? this.crossFilteringSelectedPoints.push(value) : (this.crossFilteringSelectedPoints = [value]); + if (multiSelection) { + this.crossFilteringSelectedPoints.push(value); + } else { + this.crossFilteringSelectedPoints = [value]; + } } protected isHorizontal(commonChartOptions: AgCartesianChartOptions): boolean { diff --git a/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/combo/comboChartProxy.ts b/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/combo/comboChartProxy.ts index 35c6b8b1214..65d9ee046cd 100644 --- a/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/combo/comboChartProxy.ts +++ b/packages/ag-grid-enterprise/src/charts/chartComp/chartProxies/combo/comboChartProxy.ts @@ -79,7 +79,11 @@ export class ComboChartProxy extends CartesianChartProxy<'line' | 'bar' | 'area' const colId = field.colId; const seriesChartType = seriesChartTypes.find((s) => s.colId === colId); if (seriesChartType) { - seriesChartType.secondaryAxis ? secondaryYKeys.push(colId) : primaryYKeys.push(colId); + if (seriesChartType.secondaryAxis) { + secondaryYKeys.push(colId); + } else { + primaryYKeys.push(colId); + } } } diff --git a/packages/ag-grid-enterprise/src/columnToolPanel/agPrimaryColsList.ts b/packages/ag-grid-enterprise/src/columnToolPanel/agPrimaryColsList.ts index dbcc91a98ce..3f0d5f8b9c8 100644 --- a/packages/ag-grid-enterprise/src/columnToolPanel/agPrimaryColsList.ts +++ b/packages/ag-grid-enterprise/src/columnToolPanel/agPrimaryColsList.ts @@ -586,7 +586,11 @@ export class AgPrimaryColsList extends Component { checked = column.isVisible(); } - checked ? checkedCount++ : uncheckedCount++; + if (checked) { + checkedCount++; + } else { + uncheckedCount++; + } }); if (checkedCount > 0 && uncheckedCount > 0) { diff --git a/packages/ag-grid-enterprise/src/filterToolPanel/agFiltersToolPanelList.ts b/packages/ag-grid-enterprise/src/filterToolPanel/agFiltersToolPanelList.ts index 07444b7a3c3..501c35f861e 100644 --- a/packages/ag-grid-enterprise/src/filterToolPanel/agFiltersToolPanelList.ts +++ b/packages/ag-grid-enterprise/src/filterToolPanel/agFiltersToolPanelList.ts @@ -98,7 +98,11 @@ export class AgFiltersToolPanelList extends Component { } public toggleExpanded(): void { - this.expanded ? this.collapse() : this.expand(); + if (this.expanded) { + this.collapse(); + } else { + this.expand(); + } } public expand(): void { diff --git a/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts b/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts index b37b576a30b..f12ef074d0b 100644 --- a/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts +++ b/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts @@ -20,7 +20,7 @@ export class FormulaCellEditor extends AgAbstractCellEditor { const { eventKey, cellStartedEdit } = params; - // Replicate the provided editors’ behavior: if we started from a printable key, seed with that; + // replicate the provided editors’ behaviour: if we started from a printable key, seed with that; // backspace/delete clears; otherwise use the existing value. let startValue: string | null | undefined; if (cellStartedEdit) { @@ -70,7 +70,7 @@ export class FormulaCellEditor extends AgAbstractCellEditor { const rawValue = this.eEditor.getCurrentValue(); const { value, parseValue } = this.params; - // Preserve formulas exactly as typed; otherwise delegate to the column parser so numbers/strings + // preserve formulas exactly as typed; otherwise delegate to the column parser so numbers/strings // commit in their intended type. if (typeof rawValue === 'string' && this.isFormulaText(rawValue)) { return rawValue; diff --git a/packages/ag-grid-enterprise/src/multiFilter/multiFilter.ts b/packages/ag-grid-enterprise/src/multiFilter/multiFilter.ts index b3e8ab9a0b1..242d06eb4c5 100644 --- a/packages/ag-grid-enterprise/src/multiFilter/multiFilter.ts +++ b/packages/ag-grid-enterprise/src/multiFilter/multiFilter.ts @@ -187,7 +187,11 @@ export class MultiFilter extends BaseMultiFilter implements const setFilterModel = (filter: IFilterComp, filterModel: any) => { return new AgPromise((resolve) => { const promise = filter.setModel(filterModel); - promise ? promise.then(() => resolve()) : resolve(); + if (promise) { + promise.then(resolve); + } else { + resolve(); + } }); }; diff --git a/packages/ag-grid-enterprise/src/pivot/pivotColDefService.ts b/packages/ag-grid-enterprise/src/pivot/pivotColDefService.ts index 832ef411786..c82c6d2483f 100644 --- a/packages/ag-grid-enterprise/src/pivot/pivotColDefService.ts +++ b/packages/ag-grid-enterprise/src/pivot/pivotColDefService.ts @@ -317,7 +317,11 @@ export class PivotColDefService extends BeanStub implements NamedBean, IPivotCol // add total colDef to group and pivot colDefs array const children = (groupDef as ColGroupDef).children; - insertAfter ? children.push(totalColDef) : children.unshift(totalColDef); + if (insertAfter) { + children.push(totalColDef); + } else { + children.unshift(totalColDef); + } pivotColumnDefs.push(totalColDef); } @@ -366,7 +370,11 @@ export class PivotColDefService extends BeanStub implements NamedBean, IPivotCol : colDef; pivotColumnDefs.push(colDef); - insertAtEnd ? pivotColumnGroupDefs.push(valueGroup) : pivotColumnGroupDefs.unshift(valueGroup); + if (insertAtEnd) { + pivotColumnGroupDefs.push(valueGroup); + } else { + pivotColumnGroupDefs.unshift(valueGroup); + } } } diff --git a/packages/ag-grid-enterprise/src/rangeSelection/rangeService.ts b/packages/ag-grid-enterprise/src/rangeSelection/rangeService.ts index d5764c5aa3f..0a3a8e3983d 100644 --- a/packages/ag-grid-enterprise/src/rangeSelection/rangeService.ts +++ b/packages/ag-grid-enterprise/src/rangeSelection/rangeService.ts @@ -199,7 +199,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, this.removeAllCellRanges(true); } - // The browser changes the Event target of cached events when working with the ShadowDOM + // the browser changes the event target of cached events when working with the Shadow DOM // so we need to retrieve the initial DragStartTarget. const startTarget = this.dragSvc.startTarget; @@ -318,7 +318,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, this.dispatchChangedEvent(false, true, id); } - // Called for both columns loaded & column visibility events + // Called for both columns loaded and column visibility events public onColumnsChanged(): void { // first move start column in last cell range (i.e. series chart range) this.refreshLastRangeStart(); @@ -341,7 +341,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, this.dispatchChangedEvent(false, true, cellRange.id); } } - // Remove empty cell ranges + // remove empty cell ranges const countBefore = this.cellRanges.length; this.cellRanges = this.cellRanges.filter((range) => range.columns.length > 0); if (countBefore > this.cellRanges.length) { @@ -604,7 +604,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, } if (stepsMoved !== stepCount) { - return; // Could not move the desired number of rows + return; // could not move the desired number of rows } const cellPosition = { @@ -662,7 +662,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, newColumns.push(col); } - // Only update if length actually changed + // only update if length actually changed if (newColumns.length === targetLength) { if (isRtlRange) { // before we add changes to the range, the @@ -788,7 +788,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, return; } - // Normalize the selection mode so explicit column lists are respected. + // normalise the selection mode so explicit column lists are respected. this.setSelectionMode(false); this.removeAllCellRanges(true); @@ -820,7 +820,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, this.cellRanges.push(cellRange); } - // Restore all-columns selection mode if any range spans all data columns. + // restore all-columns selection mode if any range spans all data columns. this.setSelectionMode(hasAllColumnsRange); this.dispatchChangedEvent(false, true); @@ -880,7 +880,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, return this.createPartialCellRangeFromRangeParams(params, false) as CellRange | undefined; } - // Range service can't normally support a range without columns, but charts can + // range service can't normally support a range without columns, but charts can public createPartialCellRangeFromRangeParams( params: CellRangeParams, allowEmptyColumns: boolean @@ -958,7 +958,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, return false; } if (len > 1) { - return true; // Assumes a cell range must contain at least one cell + return true; // assumes a cell range must contain at least one cell } // only one range, return true if range has more than one @@ -1109,7 +1109,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, } public intersectLastRange(fromMouseClick?: boolean) { - // when ranges are created due to a mouse click without drag (happens in cellMouseListener) + // When ranges are created due to a mouse click without drag (happens in cellMouseListener) // this method will be called with `fromMouseClick=true`. // Range selection while editing relies on overlapping ranges to preserve editor overlays. const { editingWithRanges, suppressMultiRanges } = this.getMultiRangeContext(); @@ -1129,17 +1129,17 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, const cols = range.columns; const intersectCols = cols.filter((col) => lastRange.columns.indexOf(col) === -1); if (intersectCols.length === cols.length) { - // No overlapping columns, retain previous range + // no overlapping columns, retain previous range newRanges.push(range); continue; } if (_isRowBefore(intersectionEndRow, startRow) || _isRowBefore(endRow, intersectionStartRow)) { - // No overlapping rows, retain previous range + // no overlapping rows, retain previous range newRanges.push(range); continue; } const rangeCountBefore = newRanges.length; - // Top + // top if (_isRowBefore(startRow, intersectionStartRow)) { const top: CellRange = { columns: [...cols], @@ -1149,7 +1149,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, }; newRanges.push(top); } - // Left & Right (not contiguous with columns) + // left & right (not contiguous with columns) if (intersectCols.length > 0) { const middle: CellRange = { columns: intersectCols, @@ -1161,7 +1161,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, }; newRanges.push(middle); } - // Bottom + // bottom if (_isRowBefore(intersectionEndRow, endRow)) { newRanges.push({ columns: [...cols], @@ -1171,14 +1171,14 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, }); } if (newRanges.length - rangeCountBefore === 1) { - // Only one range result from the intersection. - // Copy the source range's id, since essentially we just reduced it's size + // only one range results from the intersection. + // copy the source range's id, since essentially we just reduced its size. newRanges[newRanges.length - 1].id = range.id; } } this.cellRanges = newRanges; - // when this is called because of a clickEvent and the ranges were changed + // when this is called because of a click event and the ranges were changed // we need to force a dragEnd event to update the UI. if (fromMouseClick) { this.dispatchChangedEvent(false, true); @@ -1284,7 +1284,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, // as the user is dragging outside of the panel, the div starts to scroll, which in turn // means we are selecting more (or less) cells, but the mouse isn't moving, so we recalculate - // the selection my mimicking a new mouse event + // the selection by mimicking a new mouse event private onBodyScroll(): void { if (this.dragging && this.lastMouseEvent) { this.onDragging(this.lastMouseEvent); @@ -1442,19 +1442,19 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, */ public handleColumnSelection(clickedColumn: AgColumn | AgColumnGroup, event: MouseEvent | KeyboardEvent): void { const { gos, beans, columnRangeSelectionCtx: ctx, cellRanges } = this; - const enableColumnSelection = _getEnableColumnSelection(gos); - if (!enableColumnSelection) { + if (!_getEnableColumnSelection(gos)) { return; } - const { suppressMultiRanges } = this.getMultiRangeContext(); + const { suppressMultiRanges, editingWithRanges } = this.getMultiRangeContext(); const hasRanges = cellRanges.length > 0; const isMeta = event.ctrlKey || event.metaKey; + const allowToggle = !editingWithRanges || isMeta; const firstRow = _getFirstRow(beans); const lastRow = _getLastRow(beans); if (!firstRow || !lastRow) { - // No rows yet + // no rows yet return; } @@ -1463,7 +1463,7 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, } if (event.shiftKey) { - // doing range selection + // extend a column range from the stored root to the clicked column. const root = ctx.root; if (!root) { return; @@ -1481,44 +1481,35 @@ export class RangeService extends BeanStub implements NamedBean, IRangeService, } this.updateRangeRowBoundary({ cellRange: range, boundary: 'end', cellPosition: { column, ...lastRow } }); - } else if (clickedColumn.isColumn) { - if (hasRanges && (suppressMultiRanges || !isMeta)) { - this.removeAllCellRanges(true); - } - const foundRange = this.findContainingRange( - { columns: [clickedColumn], startRow: firstRow, endRow: lastRow }, - true - ); + return; + } - const lastCellRange = foundRange - ? this.deselectColumnsFromRange(foundRange, [clickedColumn]) - : this.selectColumns([clickedColumn], firstRow, lastRow); + // clicking a header selects or toggles a full-column range (all rows). + if (hasRanges && (suppressMultiRanges || (!isMeta && !editingWithRanges))) { + this.removeAllCellRanges(true); + } - if (lastCellRange) { - ctx.lastCellRange = lastCellRange; - } - ctx.root = clickedColumn; - } else { - if (hasRanges && (suppressMultiRanges || !isMeta)) { - this.removeAllCellRanges(true); - } - // clicked a column group so we want to select all leaf columns of the group - const leafCols = clickedColumn.getDisplayedLeafColumns(); - const foundRange = this.findContainingRange( - { columns: leafCols, startRow: firstRow, endRow: lastRow }, - true - ); + const toggleColumns = (columns: AgColumn[], root: AgColumn): void => { + const foundRange = this.findContainingRange({ columns, startRow: firstRow, endRow: lastRow }, true); - if (foundRange) { - this.deselectColumnsFromRange(foundRange, leafCols); - ctx.root = leafCols[0]; + if (foundRange && allowToggle) { + this.deselectColumnsFromRange(foundRange, columns); } else { - const addedRange = this.selectColumns(leafCols, firstRow, lastRow); - ctx.root = leafCols[0]; + const addedRange = this.selectColumns(columns, firstRow, lastRow); if (addedRange) { ctx.lastCellRange = addedRange; } } + + ctx.root = root; + }; + + if (clickedColumn.isColumn) { + toggleColumns([clickedColumn], clickedColumn); + } else { + // column groups select all leaf columns as a single range. + const leafCols = clickedColumn.getDisplayedLeafColumns(); + toggleColumns(leafCols, leafCols[0]); } } diff --git a/packages/ag-grid-enterprise/src/rowNumbers/rowNumbersService.ts b/packages/ag-grid-enterprise/src/rowNumbers/rowNumbersService.ts index 335f0fa53a8..d660f57216a 100644 --- a/packages/ag-grid-enterprise/src/rowNumbers/rowNumbersService.ts +++ b/packages/ag-grid-enterprise/src/rowNumbers/rowNumbersService.ts @@ -162,7 +162,7 @@ export class RowNumbersService extends BeanStub implements NamedBean, IRowNumber } public handleMouseDownOnCell(cellPosition: CellPosition, mouseEvent: MouseEvent): boolean { - // If click interaction can't produce an outcome (i.e. no cell selection, no row-resizing), do nothing + // if click interaction can't produce an outcome (i.e. no cell selection, no row-resizing), do nothing if ( !this.isIntegratedWithSelection || (mouseEvent.target as HTMLElement).classList.contains('ag-row-numbers-resizer') @@ -174,7 +174,7 @@ export class RowNumbersService extends BeanStub implements NamedBean, IRowNumber return false; } - // If we're not extending the range, focus the first cell + // if we're not extending the range, focus the first cell if (!mouseEvent.shiftKey && !_interpretAsRightClick(this.beans, mouseEvent)) { this.focusFirstRenderedCellAtRowPosition(cellPosition); } @@ -388,7 +388,7 @@ export class RowNumbersService extends BeanStub implements NamedBean, IRowNumber const node = params.node as RowNode | null; const isFormulasActive = this.beans.formula?.active; - // Rows that are in the pinned container take the row numbers of their pinned sibling rows + // rows that are in the pinned container take the row numbers of their pinned sibling rows const pinnedSibling = node?.pinnedSibling; if (node?.rowPinned && pinnedSibling) { const rowIndex = isFormulasActive ? pinnedSibling.formulaRowIndex : pinnedSibling.rowIndex; @@ -462,9 +462,16 @@ export class RowNumbersService extends BeanStub implements NamedBean, IRowNumber return [col]; } - // focus is disabled on the Row Numbers cells, when a click happens on it, + // focus is disabled on the row numbers cells, when a click happens on it, // it should focus the first cell of that row or first cell of the grid (from header). private focusFirstRenderedCellAtRowPosition(rowPosition?: RowPosition | null) { + const editSvc = this.beans.editSvc; + + if (editSvc?.isEditing() && editSvc.isRangeSelectionEnabledWhileEditing?.()) { + // let the formula editor keep focus when range selection is enabled during editing. + return; + } + if (!rowPosition) { rowPosition = _getFirstRow(this.beans); if (!rowPosition) { diff --git a/packages/ag-grid-enterprise/src/widgets/agFormulaInputField.ts b/packages/ag-grid-enterprise/src/widgets/agFormulaInputField.ts index abe2d3e3201..ba9451b3db3 100644 --- a/packages/ag-grid-enterprise/src/widgets/agFormulaInputField.ts +++ b/packages/ag-grid-enterprise/src/widgets/agFormulaInputField.ts @@ -26,7 +26,7 @@ type RangeInsertAction = 'insert' | 'replace' | 'none'; type TokenMatch = { ref: string; start: number; end: number; index: number }; type TokenInsertResult = { previousRef?: string; tokenIndex?: number | null }; -// Only insert new refs after operators/argument separators; otherwise treat clicks as normal edit completion. +// only insert new refs after operators/argument separators; otherwise treat clicks as normal edit completion. const TOKEN_INSERT_AFTER_CHARS = new Set(['=', '+', '-', '*', '/', '^', ',', '(', ';', '<', '>', '&']); export class AgFormulaInputField extends AgContentEditableField< @@ -39,18 +39,18 @@ export class AgFormulaInputField extends AgContentEditableField< string > { private currentValue: string = ''; - // Caret / token bookkeeping so range updates can re-render without losing position. + // caret / token bookkeeping so range updates can re-render without losing position. private selectionCaretOffset: number | null = null; private lastTokenValueOffset: number | null = null; private lastTokenValueLength: number | null = null; private lastTokenCaretOffset: number | null = null; private lastTokenRef?: string; private rangeSyncFeature?: FormulaInputRangeSyncFeature; - // Fallback color assignment per ref when a token index is unavailable. + // fallback color assignment per ref when a token index is unavailable. private readonly formulaColorByRef = new Map(); constructor() { - // Keep renderValueToElement false so we fully control DOM rendering. + // keep renderValueToElement false so we fully control DOM rendering. super({ renderValueToElement: false, className: 'ag-formula-input-field' } as any); this.registerCSS(agFormulaInputFieldCSS); } @@ -70,7 +70,7 @@ export class AgFormulaInputField extends AgContentEditableField< const { isFormula, hasFormulaPrefix } = this.getFormulaState(text); if (!isFormula) { - // Plain values: render as simple text with no token parsing or range syncing. + // plain values: render as simple text with no token parsing or range syncing. this.applyPlainValue(text, { silent, dispatch: true }); this.rangeSyncFeature?.onValueUpdated(text, hasFormulaPrefix); return this; @@ -82,7 +82,7 @@ export class AgFormulaInputField extends AgContentEditableField< } public getCurrentValue(): string { - // Validation can run before our input handler updates `currentValue`, so always + // validation can run before our input handler updates `currentValue`, so always // re-serialise the DOM to stay in sync with what the user currently sees. const liveValue = serializeContent(this.getContentElement()); @@ -293,16 +293,16 @@ export class AgFormulaInputField extends AgContentEditableField< const caretOffsets = this.getCaretOffsets(value); if (!caretOffsets) { - // Fall back to standard insert if we cannot resolve caret offsets. + // fall back to standard insert if we cannot resolve caret offsets. const { previousRef, tokenIndex } = this.insertOrReplaceToken(ref, true); return { action: 'insert', previousRef, tokenIndex }; } - // If the caret is inside/adjacent to a token, replace that token. + // if the caret is inside/adjacent to a token, replace that token. const tokenMatch = getTokenMatchAtOffset(value, caretOffsets.valueOffset); if (tokenMatch) { - // If the user is completing a partial range like "A1:", keep the range and insert the end ref. + // if the user is completing a partial range like "A1:", keep the range and insert the end ref. if (tokenMatch.ref.endsWith(':') && caretOffsets.valueOffset === tokenMatch.end) { const { previousRef, tokenIndex } = this.insertOrReplaceToken(ref, true); return { action: 'insert', previousRef, tokenIndex }; @@ -311,7 +311,7 @@ export class AgFormulaInputField extends AgContentEditableField< return { action: 'replace', previousRef, tokenIndex }; } - // Only insert new refs after operator-like chars; otherwise we end the edit on click. + // only insert new refs after operator-like chars; otherwise we end the edit on click. if (!shouldInsertTokenAtOffset(value, caretOffsets.valueOffset)) { return { action: 'none' }; } @@ -338,7 +338,7 @@ export class AgFormulaInputField extends AgContentEditableField< } private replaceTokenAtMatch(token: TokenMatch, nextRef: string): TokenInsertResult { - // Replace the exact token span so we don't accidentally touch adjacent text. + // replace the exact token span so we don't accidentally touch adjacent text. const value = this.getCurrentValue(); const updated = value.slice(0, token.start) + nextRef + value.slice(token.end); @@ -355,7 +355,7 @@ export class AgFormulaInputField extends AgContentEditableField< } private getValueOffsetFromCaret(caretOffset: number): number | null { - // Convert caret units (tokens count as 1) into value offsets (tokens count as their length). + // convert caret units (tokens count as 1) into value offsets (tokens count as their length). const container = this.getContentElement(); let caretRemaining = caretOffset; let valueOffset = 0; @@ -365,7 +365,7 @@ export class AgFormulaInputField extends AgContentEditableField< const valueLen = getNodeText(child).length; if (caretRemaining <= caretLen) { - // Tokens count as 1 caret unit but multiple value units. + // tokens count as 1 caret unit but multiple value units. return valueOffset + (caretLen === valueLen ? caretRemaining : 0); } @@ -377,7 +377,7 @@ export class AgFormulaInputField extends AgContentEditableField< } private getTokenInsertOffsets(isNew: boolean): { caretOffset: number; valueOffset: number } | null { - // Use cached offsets while dragging ranges so caret doesn't jump between events. + // use cached offsets while dragging ranges so caret doesn't jump between events. return this.getCaretOffsets(this.getCurrentValue(), { useCachedCaret: true, useCachedValueOffset: !isNew, @@ -391,7 +391,7 @@ export class AgFormulaInputField extends AgContentEditableField< useCachedValueOffset: false, } ): { caretOffset: number; valueOffset: number } | null { - // Snapshot the caret position in both caret units and raw string offsets. + // snapshot the caret position in both caret units and raw string offsets. const { beans } = this; const contentElement = this.getContentElement(); const caretOffset = options.useCachedCaret @@ -424,7 +424,7 @@ export class AgFormulaInputField extends AgContentEditableField< } private getFormulaState(text: string): { isFormula: boolean; hasFormulaPrefix: boolean } { - // Keep "=" as a plain value for commit/validation, but still enable range selection + // keep "=" as a plain value for commit/validation, but still enable range selection // when it appears so clicking a cell can insert a token. const hasFormulaPrefix = text.trimStart().startsWith('='); const isFormula = this.beans.formula?.isFormula(text) ?? hasFormulaPrefix; @@ -457,7 +457,7 @@ export class AgFormulaInputField extends AgContentEditableField< currentValue: params.currentValue ?? this.getCurrentValue(), caret: params.caret ?? undefined, }); - // We render tokens ourselves, so avoid the base class' setValue (which would re-render) + // we render tokens ourselves, so avoid the base class' setValue (which would re-render) // and delegate that task to setEditorValue to keep our cached value and the superclass in sync. this.setEditorValue(value, params.silent); if (params.dispatch) { @@ -546,11 +546,11 @@ const shouldUseTokenColors = (beans: BeanCollection): boolean => { return canCreateRanges; }; -// Walk the formula left-to-right, capture the first occurrence of each distinct ref, +// walk the formula left-to-right, capture the first occurrence of each distinct ref, // and assign colors in encounter order so token colors stay stable every time the // user re-enters the editor (A1 -> color1, next ref -> color2, etc.). const getOrderedRefs = (value: string): string[] => { - // Collect unique refs in their first-seen order to keep colors stable across re-entry. + // collect unique refs in their first-seen order to keep colors stable across re-entry. const refsInOrder: string[] = []; const seen = new Set(); for (const match of getRefTokenMatches(value)) { @@ -565,7 +565,7 @@ const getOrderedRefs = (value: string): string[] => { }; const getTokenMatchAtOffset = (value: string, offset: number): TokenMatch | null => { - // Locate the token (if any) that covers the given value offset. + // locate the token (if any) that covers the given value offset. for (const match of getRefTokenMatches(value)) { if (offset >= match.start && offset <= match.end) { return { ref: match.ref, start: match.start, end: match.end, index: match.index }; @@ -575,13 +575,13 @@ const getTokenMatchAtOffset = (value: string, offset: number): TokenMatch | null }; const shouldInsertTokenAtOffset = (value: string, offset: number): boolean => { - // Insert only after an operator or at the beginning to avoid hijacking plain values. + // insert only after an operator or at the beginning to avoid hijacking plain values. const previousChar = getPreviousNonSpaceChar(value, offset); return previousChar == null || TOKEN_INSERT_AFTER_CHARS.has(previousChar); }; const getPreviousNonSpaceChar = (value: string, offset: number): string | null => { - // Skip whitespace to detect the meaningful character before the caret. + // skip whitespace to detect the meaningful character before the caret. for (let i = offset - 1; i >= 0; i--) { const char = value[i]; if (char != null && char.trim() !== '') { @@ -597,7 +597,7 @@ const tokenize = ( value: string, getColorIndexForToken: (tokenIndex: number) => number | null ): Node[] => { - // Split the formula into text + token nodes while preserving operators for display. + // split the formula into text + token nodes while preserving operators for display. const nodes: Node[] = []; let lastIndex = 0; const matches = getRefTokenMatches(value); @@ -663,7 +663,7 @@ const renderFormula = (params: { getColorIndexForToken: (tokenIndex: number) => number | null; caret?: number | null; }): void => { - // Rebuild the DOM and restore the caret to the same logical position. + // rebuild the DOM and restore the caret to the same logical position. const { beans, contentElement, currentValue, value, getColorIndexForToken, caret } = params; const caretOffset = caret ?? getCaretOffset(beans, contentElement, currentValue); const maxCaret = value.length; @@ -679,7 +679,7 @@ const renderFormula = (params: { }; const getOffsetBeforeNode = (container: HTMLElement, node: Node, useValueLength: boolean = false): number | null => { - // Compute caret/value offsets before a specific node in the tokenized DOM. + // compute caret/value offsets before a specific node in the tokenised DOM. if (!container.contains(node)) { return null; } @@ -695,9 +695,9 @@ const getOffsetBeforeNode = (container: HTMLElement, node: Node, useValueLength: return null; }; -// Serialization helpers +// Serialisation helpers const serializeContent = (contentElement: HTMLElement): string => { - // Read the tokenized DOM back into the raw formula text. + // read the tokenised DOM back into the raw formula text. let output = ''; contentElement.childNodes.forEach((child) => { @@ -708,7 +708,7 @@ const serializeContent = (contentElement: HTMLElement): string => { }; const getNodeText = (node: Node): string => { - // Convert DOM nodes back into value text, undoing display-only operator substitutions. + // convert DOM nodes back into value text, undoing display-only operator substitutions. if (node.nodeType === Node.TEXT_NODE) { return formatForValue(node.textContent ?? ''); } @@ -723,7 +723,7 @@ const getNodeText = (node: Node): string => { }; const _getNodeTextLength = (node: Node): number => { - // Measure text length for caret math (tokens count as their displayed text). + // measure text length for caret math (tokens count as their displayed text). if (node.nodeType === Node.TEXT_NODE) { return node.textContent?.length ?? 0; } @@ -736,7 +736,7 @@ const _getNodeTextLength = (node: Node): number => { }; const findNodeAtOffset = (root: Node, offset: number): { node: Node | null; localOffset: number } => { - // Walk the tokenized tree and return the node/offset for a logical caret position. + // walk the tokenised tree and return the node/offset for a logical caret position. let remaining = offset; for (let i = 0; i < root.childNodes.length; i++) { @@ -759,7 +759,7 @@ const findNodeAtOffset = (root: Node, offset: number): { node: Node | null; loca }; const restoreCaret = (beans: BeanCollection, contentElement: HTMLElement, offset: number | null): void => { - // Place the DOM caret at a logical offset within the tokenized content. + // place the DOM caret at a logical offset within the tokenised content. if (offset == null) { return; } @@ -780,12 +780,12 @@ const restoreCaret = (beans: BeanCollection, contentElement: HTMLElement, offset try { selection.addRange(range); } catch { - // Ignore invalid ranges when the editor is detached from the document. + // ignore invalid ranges when the editor is detached from the document. } }; const getCaretOffset = (beans: BeanCollection, contentElement: HTMLElement, currentValue: string): number | null => { - // Translate the DOM selection into a caret offset that counts tokens as one unit. + // translate the DOM selection into a caret offset that counts tokens as one unit. const win = _getWindow(beans); const selection = win.getSelection(); @@ -799,7 +799,7 @@ const getCaretOffset = (beans: BeanCollection, contentElement: HTMLElement, curr return currentValue?.length ?? null; } - // If the caret is directly on the container (between child nodes), the range offset is a + // if the caret is directly on the container (between child nodes), the range offset is a // child index, so convert it to caret units by summing preceding child lengths. if (range.startContainer === contentElement) { let offset = 0; @@ -838,7 +838,7 @@ const getTokenIndex = (tokenEl: HTMLElement): number | null => { return Number.isFinite(parsed) ? parsed : null; }; -// Text formatting helpers +// text formatting helpers const formatForDisplay = (text: string): string => text.replace(/[/*]/g, (match) => DISPLAY_OPERATOR_LOOKUP[match] ?? match); diff --git a/packages/ag-grid-enterprise/src/widgets/formulaInputRangeSyncFeature.ts b/packages/ag-grid-enterprise/src/widgets/formulaInputRangeSyncFeature.ts index dc2cf837828..70684765c14 100644 --- a/packages/ag-grid-enterprise/src/widgets/formulaInputRangeSyncFeature.ts +++ b/packages/ag-grid-enterprise/src/widgets/formulaInputRangeSyncFeature.ts @@ -15,21 +15,21 @@ import { type TrackedRange = { ref: string; tokenIndex?: number | null }; export class FormulaInputRangeSyncFeature extends BeanStub { - // Local mirror of editSvc range selection state while formula editing is active. + // local mirror of editSvc range selection state while formula editing is active. private rangeSelectionEnabled = false; private editingCellRef?: string; private editingColumn?: Column; private editingRowIndex?: number; - // Refs found in the formula that should have matching grid ranges (counts handle duplicates). + // refs found in the formula that should have matching grid ranges (counts handle duplicates). private readonly trackedRangeRefs = new Map(); - // Ranges we are actively tracking and their current ref string. + // ranges we are actively tracking and their current ref string. private readonly trackedRanges = new Map(); - // Prevents our own range changes from re-entering the selection handler. + // prevents our own range changes from re-entering the selection handler. private suppressRangeEvents = false; - // Skips the synthetic refresh event we dispatch after re-tagging ranges. + // skips the synthetic refresh event we dispatch after re-tagging ranges. private ignoreNextRangeEvent = false; - // Avoids a value update loop when we re-render on enabling range selection. + // avoids a value update loop when we re-render on enabling range selection. private skipNextValueUpdate = false; constructor(private readonly field: AgFormulaInputField) { @@ -55,10 +55,10 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } if (hasFormulaPrefix) { - // Enable range selection once the user is building a formula (even if it is just "="). + // enable range selection once the user is building a formula (even if it is just "="). const newlyEnabled = this.enableRangeSelectionWhileEditing(); if (newlyEnabled) { - // Re-render with colors now that range selection is on. + // re-render with colors now that range selection is on. this.skipNextValueUpdate = true; this.field.setValue(value, true); } @@ -253,7 +253,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } const refTokens = getRefTokensFromText(text); - // Group token indices by ref so duplicates map to distinct ranges. + // group token indices by ref so duplicates map to distinct ranges. const desiredByRef = new Map(); for (const token of refTokens) { @@ -370,7 +370,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { !!latestRange && !this.trackedRanges.has(latestRange) && !!latestRef && latestRef !== this.editingCellRef; const shouldInsert = event.finished && (event.started || hasInsertCandidate); - // Re-tag ranges if their colors are out of sync with the formula tokens. + // re-tag ranges if their colors are out of sync with the formula tokens. const reTagged = this.ensureTrackedRangeColors(); if (this.suppressRangeEvents) { @@ -381,7 +381,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } if (event.started || hasInsertCandidate) { - // Remember caret so we can restore it after any selection-driven edits. + // remember caret so we can restore it after any selection-driven edits. this.field.rememberCaret(); } @@ -391,7 +391,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { return; } - // If an existing range was resized, update its token instead of inserting a new one. + // if an existing range was resized, update its token instead of inserting a new one. if (this.updateTrackedRangeTokens()) { return; } @@ -407,13 +407,17 @@ export class FormulaInputRangeSyncFeature extends BeanStub { const { action, previousRef, tokenIndex } = this.field.applyRangeInsert(ref); if (action === 'none') { - // Treat the click as an edit completion when not in a formula context. + // range selection while editing appends ranges, so collapse to the latest selection + // before stopping the edit to avoid leaving the previous cell highlighted. + this.keepLatestSelectionOnly(latestRange); + + // treat the click as edit completion when we are not inserting a token. this.beans.editSvc?.stopEditing(undefined, { source: 'edit' }); return; } if (action === 'replace' && previousRef === ref) { - // Clicking the same ref should not leave a duplicate range behind. + // clicking the same ref should not leave a duplicate range behind. this.discardLatestRangeForRef(ref); this.field.restoreCaretAfterToken(); this.refocusEditingCell(); @@ -422,7 +426,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { this.tagLatestRangeForRef(ref, tokenIndex); this.handleRangeTokenUpdate(previousRef, ref, true, action === 'insert', tokenIndex); - // Refresh token indices for existing ranges so their colors match the new token order. + // refresh token indices for existing ranges so their colors match the new token order. this.syncRangesFromFormula(this.field.getCurrentValue()); this.field.restoreCaretAfterToken(); this.refocusEditingCell(); @@ -430,7 +434,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } if (!event.started && !event.finished) { - // Drag updates should rewrite the active token as the range grows/shrinks. + // drag updates should rewrite the active token as the range grows/shrinks. const { previousRef, tokenIndex } = this.field.insertOrReplaceToken(ref, false); this.tagLatestRangeForRef(ref, tokenIndex); this.handleRangeTokenUpdate(previousRef, ref, false, false); @@ -446,6 +450,14 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } } + private keepLatestSelectionOnly(latestRange: CellRange | null): void { + if (!latestRange || this.getLiveRanges().length <= 1) { + return; + } + + this.setCellRangesSilently([latestRange]); + } + private handleRangeTokenUpdate( previousRef: string | undefined, ref: string, @@ -479,7 +491,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private addRangeForRef(ref: string, skipAddCellRange?: boolean, tokenIndex?: number | null): CellRange | undefined { - // Create or re-tag an existing range for the given ref. + // create or re-tag an existing range for the given ref. const rangeSvc = this.beans.rangeSvc; if (!rangeSvc) { @@ -526,7 +538,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private tagLatestRangeForRef(ref: string, tokenIndex?: number | null): void { - // The newest range is the one the user just clicked/dragged. + // the newest range is the one the user just clicked/dragged. const { trackedRanges } = this; const ranges = this.getLiveRanges(); @@ -605,7 +617,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private handleRemovedRangeTokens(): boolean { - // If a tracked range was removed via selection (e.g. Ctrl/Cmd click), drop its token. + // if a tracked range was removed via selection (e.g. Ctrl/Cmd click), drop its token. if (!this.beans.rangeSvc || this.trackedRanges.size === 0) { return false; } @@ -680,7 +692,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private refreshRangeStyling(): void { - // Trigger a lightweight refresh so overlays pick up any updated classes. + // trigger a lightweight refresh so overlays pick up any updated classes. const { eventSvc } = this.beans; if (!eventSvc) { return; @@ -696,7 +708,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private refocusEditingCell(): void { - // Keep focus on the edited cell so keyboard editing continues. + // keep focus on the edited cell so keyboard editing continues. const { focusSvc } = this.beans; if (!focusSvc || this.editingColumn == null || this.editingRowIndex == null) { return; @@ -726,7 +738,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private removeRangeForRef(ref: string | undefined, tokenIndex?: number | null): void { - // Drop ranges that no longer exist in the formula and clean our tracking maps. + // drop ranges that no longer exist in the formula and clean our tracking maps. if (!ref || !this.hasTrackedRef(ref)) { return; } @@ -765,7 +777,7 @@ export class FormulaInputRangeSyncFeature extends BeanStub { } private updateTrackedRangeTokens(): boolean { - // When a tracked range changes, update the corresponding token text. + // when a tracked range changes, update the corresponding token text. if (!this.beans.rangeSvc) { return false; } diff --git a/packages/ag-grid-react/src/reactUi/header/headerCellComp.tsx b/packages/ag-grid-react/src/reactUi/header/headerCellComp.tsx index 321e3e868df..083682823ea 100644 --- a/packages/ag-grid-react/src/reactUi/header/headerCellComp.tsx +++ b/packages/ag-grid-react/src/reactUi/header/headerCellComp.tsx @@ -58,7 +58,11 @@ const HeaderCellComp = ({ ctrl }: { ctrl: HeaderCellCtrl }) => { setUserStyles: (styles: HeaderStyle) => setUserStyles(styles), setAriaSort: (sort?: AriaSortState) => { if (eGui.current) { - sort ? _setAriaSort(eGui.current, sort) : _removeAriaSort(eGui.current); + if (sort) { + _setAriaSort(eGui.current, sort); + } else { + _removeAriaSort(eGui.current); + } } }, setUserCompDetails: (compDetails: UserCompDetails) => setUserCompDetails(compDetails), diff --git a/packages/ag-grid-react/src/reactUi/header/headerFilterCellComp.tsx b/packages/ag-grid-react/src/reactUi/header/headerFilterCellComp.tsx index 3ff2c0f3283..5cd5cb8dbdd 100644 --- a/packages/ag-grid-react/src/reactUi/header/headerFilterCellComp.tsx +++ b/packages/ag-grid-react/src/reactUi/header/headerFilterCellComp.tsx @@ -49,7 +49,7 @@ const HeaderFilterCellComp = ({ ctrl }: { ctrl: HeaderFilterCellCtrl }) => { return; } - userCompResolve.current && userCompResolve.current(value); + userCompResolve.current?.(value); }; const setRef = useCallback((eRef: HTMLDivElement | null) => { diff --git a/plugins/ag-grid-generate-code-reference-files/src/copySrcFilesForGeneration.js b/plugins/ag-grid-generate-code-reference-files/src/copySrcFilesForGeneration.js index d0f6286eb5a..95380883800 100644 --- a/plugins/ag-grid-generate-code-reference-files/src/copySrcFilesForGeneration.js +++ b/plugins/ag-grid-generate-code-reference-files/src/copySrcFilesForGeneration.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const fs = require('fs'); function copyFileWithTSNoCheck(sourceFile, destinationDir, destinationFile) { @@ -24,7 +25,6 @@ function copyFileWithTSNoCheck(sourceFile, destinationDir, destinationFile) { (err) => { if (err) { console.error(`Error writing file: ${err}`); - return; } } ); diff --git a/plugins/ag-grid-generate-code-reference-files/src/executors/generate/formatAST.js b/plugins/ag-grid-generate-code-reference-files/src/executors/generate/formatAST.js index 3fe5ecce463..41a0730f684 100644 --- a/plugins/ag-grid-generate-code-reference-files/src/executors/generate/formatAST.js +++ b/plugins/ag-grid-generate-code-reference-files/src/executors/generate/formatAST.js @@ -34,7 +34,7 @@ function findInNodeTree(ts) { // } const printer = (ts) => ts.createPrinter({ removeComments: true, omitTrailingSemicolon: true }); -function getFullJsDoc(ts) { +function getFullJsDoc(_ts) { return function (node) { if (node.jsDoc) { const result = node.jsDoc.map((j) => { @@ -46,7 +46,7 @@ function getFullJsDoc(ts) { }; } -function getJsDoc(ts) { +function getJsDoc(_ts) { return function (node) { if (node.jsDoc && node.jsDoc.length === 1) { const j = node.jsDoc[0]; diff --git a/plugins/ag-grid-generate-code-reference-files/src/executors/generate/generate-code-reference-files.ts b/plugins/ag-grid-generate-code-reference-files/src/executors/generate/generate-code-reference-files.ts index 1420600407d..5b734fb3064 100644 --- a/plugins/ag-grid-generate-code-reference-files/src/executors/generate/generate-code-reference-files.ts +++ b/plugins/ag-grid-generate-code-reference-files/src/executors/generate/generate-code-reference-files.ts @@ -112,7 +112,9 @@ function extractNestedTypes( if (ts.isPropertySignature(node)) { results[node.name.getText()] = getJsDoc(node); - node.type && extractNestedTypes(node.type, srcFile, includeQuestionMark, results, visited); + if (node.type) { + extractNestedTypes(node.type, srcFile, includeQuestionMark, results, visited); + } return; } diff --git a/plugins/ag-grid-task-autogen/src/generate-example-files.ts b/plugins/ag-grid-task-autogen/src/generate-example-files.ts index 2e9b52c83bc..c113f7e88e7 100644 --- a/plugins/ag-grid-task-autogen/src/generate-example-files.ts +++ b/plugins/ag-grid-task-autogen/src/generate-example-files.ts @@ -45,10 +45,14 @@ export const createDependencies: CreateDependencies = (opts, ctx) => { const result: ReturnType = []; for (const [name, config] of Object.entries(projects)) { - if (!config.tags?.includes('type:generated-example')) continue; + if (!config.tags?.includes('type:generated-example')) { + continue; + } const parent = config.tags?.find((t) => t.startsWith('scope:'))?.split(':')[1]; - if (!parent) continue; + if (!parent) { + continue; + } const dependency: RawProjectGraphDependency = { source: `${parent}`, diff --git a/testing/behavioural/src/selection/utils.ts b/testing/behavioural/src/selection/utils.ts index 90fef13081d..b8ec04ffe77 100644 --- a/testing/behavioural/src/selection/utils.ts +++ b/testing/behavioural/src/selection/utils.ts @@ -49,14 +49,22 @@ export class GridActions { selectRowsByIndex(indices: number[], click: boolean): void { for (const i of indices) { - click ? this.clickRowByIndex(i, { ctrlKey: true }) : this.toggleCheckboxByIndex(i); + if (click) { + this.clickRowByIndex(i, { ctrlKey: true }); + } else { + this.toggleCheckboxByIndex(i); + } } assertSelectedRowsByIndex(indices, this.api); } selectRowsById(ids: string[], click: boolean): void { for (const i of ids) { - click ? this.clickRowById(i, { ctrlKey: true }) : this.toggleCheckboxById(i); + if (click) { + this.clickRowById(i, { ctrlKey: true }); + } else { + this.toggleCheckboxById(i); + } } assertSelectedRowElementsById(ids, this.api); }