uuid: e0e6492f-9fa2-443f-a325-4f5eeb4312b8
langcode: en
status: true
dependencies: {  }
machineName: node_collection
name: 'Node Collection'
required: {  }
props:
  contentType:
    type: string
    title: 'Content Type'
    examples:
      - portfolio
  tag:
    type: string
    title: Tag
    examples:
      - Drupal
  tagField:
    type: string
    title: 'Tag Field Name'
    examples:
      - field_tags
  count:
    type: integer
    title: Count
    examples:
      - 3
  viewMode:
    type: string
    title: 'View Mode'
    examples:
      - teaser
  promoted:
    type: boolean
    title: Promoted
    examples:
      - false
  theme:
    type: string
    title: Theme
    enum:
      - inherit
      - white
      - light
      - dark
      - black
      - primary
      - secondary
    examples:
      - inherit
slots: {  }
js:
  original: |
    import { useEffect, useRef } from 'preact/hooks';
    import { JsonApiClient } from 'drupal-canvas';
    import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
    import useSWR from 'swr';

    const client = new JsonApiClient(
      window.drupalSettings?.canvasData?.v0?.baseUrl || window.location.origin,
    );

    export default function NodeCollection({
      contentType,
      tag,
      tagField,
      count,
      viewMode,
      promoted,
      theme,
    }) {
      const containerRef = useRef(null);
      const limit = Math.max(1, Math.min(6, count || 3));

      const params = new DrupalJsonApiParams()
        .addFilter('status', '1')
        .addSort('created', 'DESC')
        .addPageLimit(limit)
        .addFields(`node--${contentType}`, ['title', 'drupal_internal__nid']);

      if (promoted) {
        params.addFilter('promote', '1');
      }

      if (tag) {
        const fieldName = tagField || 'field_tags';
        params.addFilter(`${fieldName}.name`, tag, 'CONTAINS');
      }

      const { data, error, isLoading } = useSWR(
        [
          `node--${contentType}`,
          { queryString: params.getQueryString() },
        ],
        ([type, options]) => client.getCollection(type, options),
      );

      useEffect(() => {
        if (!data || !containerRef.current) return;

        const processHtmx = () => {
          if (window.htmx) {
            window.htmx.process(containerRef.current);
          }
        };

        if (window.htmx) {
          processHtmx();
        } else {
          const script = document.createElement('script');
          script.src = '/core/assets/vendor/htmx/htmx.min.js';
          script.onload = processHtmx;
          document.head.appendChild(script);
        }
      }, [data]);

      const nodeCount = data?.length || limit;
      const columnsLarge = Math.min(12, Math.max(3, Math.floor(12 / nodeCount)));
      const columnsMedium = Math.min(
        12,
        Math.max(6, Math.floor(12 / Math.ceil(nodeCount / 2))),
      );
      const themeClass = theme && theme !== 'inherit' ? ` theme--${theme}` : '';

      if (error) {
        return (
          <div className="node-collection__error">Failed to load content.</div>
        );
      }

      if (isLoading || !data) {
        return (
          <div className="node-collection-component">
            <div className="node-collection__loading">Loading...</div>
          </div>
        );
      }

      return (
        <div
          ref={containerRef}
          className={`node-collection-component${themeClass}`}
        >
          <div className="grid-wrapper">
            <div className="grid-wrapper__grid">
              {data.map((node) => (
                  <div
                    key={node.id}
                    className="grid-cell"
                    style={{
                      '--grid-cell-columns-small': '6',
                      '--grid-cell-columns-medium': String(columnsMedium),
                      '--grid-cell-columns-large': String(columnsLarge),
                    }}
                    data-hx-get={`/htmx/node/${node.drupal_internal__nid}/${viewMode || 'teaser'}`}
                    data-hx-trigger="load"
                    data-hx-swap="innerHTML"
                  />
                ))}
              </div>
            </div>
          </div>
        );
      }
  compiled: |
    import { jsx as _jsx } from "react/jsx-runtime";
    import { useEffect, useRef } from 'preact/hooks';
    import { JsonApiClient } from 'drupal-canvas';
    import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
    import useSWR from 'swr';
    const client = new JsonApiClient(window.drupalSettings?.canvasData?.v0?.baseUrl || window.location.origin);
    export default function NodeCollection({ contentType, tag, tagField, count, viewMode, promoted, theme }) {
        const containerRef = useRef(null);
        const limit = Math.max(1, Math.min(6, count || 3));
        const params = new DrupalJsonApiParams().addFilter('status', '1').addSort('created', 'DESC').addPageLimit(limit).addFields(`node--${contentType}`, ['title', 'drupal_internal__nid']);
        if (promoted) {
            params.addFilter('promote', '1');
        }
        if (tag) {
            const fieldName = tagField || 'field_tags';
            params.addFilter(`${fieldName}.name`, tag, 'CONTAINS');
        }
        const { data, error, isLoading } = useSWR([
            `node--${contentType}`,
            {
                queryString: params.getQueryString()
            }
        ], ([type, options])=>client.getCollection(type, options));
        useEffect(()=>{
            if (!data || !containerRef.current) return;
            const processHtmx = ()=>{
                if (window.htmx) {
                    window.htmx.process(containerRef.current);
                }
            };
            if (window.htmx) {
                processHtmx();
            } else {
                const script = document.createElement('script');
                script.src = '/core/assets/vendor/htmx/htmx.min.js';
                script.onload = processHtmx;
                document.head.appendChild(script);
            }
        }, [
            data
        ]);
        const nodeCount = data?.length || limit;
        const columnsLarge = Math.min(12, Math.max(3, Math.floor(12 / nodeCount)));
        const columnsMedium = Math.min(12, Math.max(6, Math.floor(12 / Math.ceil(nodeCount / 2))));
        const themeClass = theme && theme !== 'inherit' ? ` theme--${theme}` : '';
        if (error) {
            return /*#__PURE__*/ _jsx("div", {
                className: "node-collection__error",
                children: "Failed to load content."
            });
        }
        if (isLoading || !data) {
            return /*#__PURE__*/ _jsx("div", {
                className: "node-collection-component",
                children: /*#__PURE__*/ _jsx("div", {
                    className: "node-collection__loading",
                    children: "Loading..."
                })
            });
        }
        return /*#__PURE__*/ _jsx("div", {
            ref: containerRef,
            className: `node-collection-component${themeClass}`,
            children: /*#__PURE__*/ _jsx("div", {
                className: "grid-wrapper",
                children: /*#__PURE__*/ _jsx("div", {
                    className: "grid-wrapper__grid",
                    children: data.map((node)=>/*#__PURE__*/ _jsx("div", {
                            className: "grid-cell",
                            style: {
                                '--grid-cell-columns-small': '6',
                                '--grid-cell-columns-medium': String(columnsMedium),
                                '--grid-cell-columns-large': String(columnsLarge)
                            },
                            "data-hx-get": `/htmx/node/${node.drupal_internal__nid}/${viewMode || 'teaser'}`,
                            "data-hx-trigger": "load",
                            "data-hx-swap": "innerHTML"
                        }, node.id))
                })
            })
        });
    }
css:
  original: |
    .node-collection-component {
      display: block;
    }

    .node-collection__loading {
      font-size: 0.95rem;
      color: #6b7280;
    }

    .node-collection__error {
      font-size: 0.95rem;
      color: #dc2626;
    }

    /* Grid system from dripyard_base (needed because SDC CSS doesn't auto-load for JS components) */
    .node-collection-component .grid-wrapper {
      container-type: inline-size;
    }

    .node-collection-component .grid-wrapper__grid {
      display: grid;
      grid-template-columns: repeat(6, minmax(0, 1fr));
      column-gap: min(calc(100cqw / 12), 40px);
      row-gap: 40px;
    }

    @container (width > 600px) {
      .node-collection-component .grid-wrapper__grid {
        grid-template-columns: repeat(12, minmax(0, 1fr));
      }
    }

    .node-collection-component .grid-cell {
      grid-column: span var(--grid-cell-columns-small, 6);
      display: flex;
      flex-direction: column;
    }

    @container (width > 600px) {
      .node-collection-component .grid-cell {
        grid-column: span var(--grid-cell-columns-medium, var(--grid-cell-columns-small, 6));
      }
    }

    @container (width > 1200px) {
      .node-collection-component .grid-cell {
        grid-column: span var(--grid-cell-columns-large, var(--grid-cell-columns-medium, var(--grid-cell-columns-small, 6)));
      }
    }

    /* Apply theme surface styling to cards inside a themed wrapper */
    .node-collection-component[class*="theme"] .card {
      background-color: var(--theme-surface);
      border-radius: var(--radius-md);
      overflow: clip;
      color: var(--theme-text-color-loud);
    }

    .node-collection-component[class*="theme"] .card__bottom {
      padding: 0 var(--gap) var(--gap) var(--gap);
    }
  compiled: '.node-collection-component{display:block}.node-collection__loading{color:#6b7280;font-size:.95rem}.node-collection__error{color:#dc2626;font-size:.95rem}.node-collection-component .grid-wrapper{container-type:inline-size}.node-collection-component .grid-wrapper__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));column-gap:min(calc(100cqw / 12),40px);row-gap:40px}@container (width > 600px){.node-collection-component .grid-wrapper__grid{grid-template-columns:repeat(12,minmax(0,1fr))}}.node-collection-component .grid-cell{grid-column:span var(--grid-cell-columns-small,6);display:flex;flex-direction:column}@container (width > 600px){.node-collection-component .grid-cell{grid-column:span var(--grid-cell-columns-medium,var(--grid-cell-columns-small,6))}}@container (width > 1200px){.node-collection-component .grid-cell{grid-column:span var(--grid-cell-columns-large,var(--grid-cell-columns-medium,var(--grid-cell-columns-small,6)))}}.node-collection-component[class*="theme"] .card{background-color:var(--theme-surface);border-radius:var(--radius-md);overflow:clip;color:var(--theme-text-color-loud)}.node-collection-component[class*="theme"] .card__bottom{padding:0 var(--gap) var(--gap) var(--gap)}'
dataDependencies: {  }
