@lifesg/react-design-system 4.0.0-alpha.8 → 4.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion/accordion-item.js.map +1 -1
- package/accordion/accordion.js.map +1 -1
- package/alert/alert.js.map +1 -1
- package/cjs/accordion/accordion-item.js.map +1 -1
- package/cjs/accordion/accordion.js.map +1 -1
- package/cjs/alert/alert.js.map +1 -1
- package/cjs/countdown-timer/countdown-timer.js.map +1 -1
- package/cjs/data-table/data-table.js +1 -1
- package/cjs/data-table/data-table.js.map +1 -1
- package/cjs/date-range-input/date-range-input.js.map +1 -1
- package/cjs/e-signature/e-signature-canvas.js.map +1 -1
- package/cjs/error-display/inactivity-timer.js.map +1 -1
- package/cjs/external/dompurify/dist/purify.es.js +2 -2
- package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
- package/cjs/file-download/file-list-card/file-list-card.js.map +1 -1
- package/cjs/file-upload/custom-sensors.js.map +1 -1
- package/cjs/file-upload/file-list/file-list.js.map +1 -1
- package/cjs/filter/addons/filter-item-checkbox-helpers.js.map +1 -1
- package/cjs/filter/addons/filter-item-checkbox.js.map +1 -1
- package/cjs/filter/filter-item.js.map +1 -1
- package/cjs/filter/filter.js.map +1 -1
- package/cjs/form/form-textarea/form-textarea.js +1 -1
- package/cjs/form/form-textarea/form-textarea.js.map +1 -1
- package/cjs/fullscreen-image-carousel/stateful-image.js.map +1 -1
- package/cjs/histogram-slider/histogram-slider.js.map +1 -1
- package/cjs/input-range-slider/input-range-slider.js.map +1 -1
- package/cjs/input-slider/input-slider.js.map +1 -1
- package/cjs/input-textarea/textarea-counter.js.map +1 -1
- package/cjs/input-textarea/textarea.js +1 -1
- package/cjs/input-textarea/textarea.js.map +1 -1
- package/cjs/input-textarea/textarea.styles.js +1 -1
- package/cjs/input-textarea/textarea.styles.js.map +1 -1
- package/{input-textarea/textarea.styles_q77m5x.css → cjs/input-textarea/textarea.styles_6we7bs.css} +3 -1
- package/cjs/modal/index.js.map +1 -1
- package/cjs/modal-v2/modal-v2.js +1 -1
- package/cjs/modal-v2/modal-v2.js.map +1 -1
- package/cjs/modal-v2/slots/card.js +1 -1
- package/cjs/modal-v2/slots/card.js.map +1 -1
- package/cjs/modal-v2/slots/header.js +2 -0
- package/cjs/modal-v2/slots/header.js.map +1 -0
- package/cjs/modal-v2/slots/slot-styles.js +1 -1
- package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
- package/cjs/modal-v2/slots/{slot-styles_13sd376.css → slot-styles_10ut1v7.css} +5 -1
- package/cjs/navbar/navbar-items.js +1 -1
- package/cjs/navbar/navbar-items.js.map +1 -1
- package/cjs/navbar/navbar-items.styles.js +1 -1
- package/cjs/navbar/navbar-items.styles.js.map +1 -1
- package/cjs/navbar/{navbar-items.styles_1i3ltot.css → navbar-items.styles_1b1zx.css} +9 -8
- package/cjs/navbar/navbar-logo-data.js +1 -1
- package/cjs/navbar/navbar-logo-data.js.map +1 -1
- package/cjs/navbar/navbar.js.map +1 -1
- package/cjs/otp-input/otp-input.js.map +1 -1
- package/cjs/overlay/overlay.js.map +1 -1
- package/cjs/pagination/pagination.js.map +1 -1
- package/cjs/phone-number-input/phone-number-input.js.map +1 -1
- package/cjs/predictive-text-input/predictive-text-input.js.map +1 -1
- package/cjs/shared/dropdown-list/dropdown-list.js.map +1 -1
- package/cjs/shared/internal-calendar/calendar-manager.js.map +1 -1
- package/cjs/shared/standalone-date-input/standalone-date-input.js.map +1 -1
- package/cjs/theme/types.js +1 -1
- package/cjs/theme/types.js.map +1 -1
- package/cjs/time-slot-bar/time-slot-bar.js.map +1 -1
- package/cjs/time-slot-bar-week/time-slot-bar-week-days.js.map +1 -1
- package/cjs/timetable/timetable.js.map +1 -1
- package/cjs/toggle/toggle-icon.js +1 -1
- package/cjs/toggle/toggle-icon.js.map +1 -1
- package/cjs/toggle/toggle-icon.styles.js +1 -1
- package/cjs/toggle/toggle-icon.styles.js.map +1 -1
- package/cjs/toggle/{toggle-icon.styles_ba71y.css → toggle-icon.styles_o6dxr3.css} +3 -2
- package/cjs/toggle/toggle.js +1 -1
- package/cjs/toggle/toggle.js.map +1 -1
- package/cjs/toggle/toggle.styles.js +1 -1
- package/cjs/toggle/toggle.styles.js.map +1 -1
- package/cjs/toggle/toggle.styles_11tmfsr.css +41 -0
- package/cjs/unit-number/unit-number-input.js.map +1 -1
- package/cjs/util/is-styled-components.js.map +1 -1
- package/cjs/util/string-helper.js +1 -1
- package/cjs/util/string-helper.js.map +1 -1
- package/cjs/util/use-event-listener.js.map +1 -1
- package/cjs/util/use-state-actions.js.map +1 -1
- package/countdown-timer/countdown-timer.js.map +1 -1
- package/data-table/data-table.js +1 -1
- package/data-table/data-table.js.map +1 -1
- package/data-table/types.d.ts +2 -2
- package/date-range-input/date-range-input.js.map +1 -1
- package/docs/component-catalog.json +1 -1
- package/e-signature/e-signature-canvas.js.map +1 -1
- package/error-display/inactivity-timer.js.map +1 -1
- package/external/dompurify/dist/purify.es.js +2 -2
- package/external/dompurify/dist/purify.es.js.map +1 -1
- package/file-download/file-list-card/file-list-card.js.map +1 -1
- package/file-upload/custom-sensors.d.ts +2 -2
- package/file-upload/custom-sensors.js.map +1 -1
- package/file-upload/file-list/file-list.js.map +1 -1
- package/filter/addons/filter-item-checkbox-helpers.js.map +1 -1
- package/filter/addons/filter-item-checkbox.js.map +1 -1
- package/filter/filter-item.js.map +1 -1
- package/filter/filter.js.map +1 -1
- package/form/form-textarea/form-textarea.js +1 -1
- package/form/form-textarea/form-textarea.js.map +1 -1
- package/fullscreen-image-carousel/stateful-image.js.map +1 -1
- package/fullscreen-image-carousel/types.d.ts +2 -2
- package/histogram-slider/histogram-slider.js.map +1 -1
- package/input-range-slider/input-range-slider.js.map +1 -1
- package/input-slider/input-slider.js.map +1 -1
- package/input-textarea/textarea-counter.js.map +1 -1
- package/input-textarea/textarea.js +1 -1
- package/input-textarea/textarea.js.map +1 -1
- package/input-textarea/textarea.styles.d.ts +2 -0
- package/input-textarea/textarea.styles.js +1 -1
- package/input-textarea/textarea.styles.js.map +1 -1
- package/{cjs/input-textarea/textarea.styles_q77m5x.css → input-textarea/textarea.styles_6we7bs.css} +3 -1
- package/input-textarea/types.d.ts +7 -0
- package/modal/index.d.ts +4 -0
- package/modal/index.js.map +1 -1
- package/modal/types.d.ts +2 -0
- package/modal-v2/modal-v2.d.ts +6 -0
- package/modal-v2/modal-v2.js +1 -1
- package/modal-v2/modal-v2.js.map +1 -1
- package/modal-v2/slots/card.js +1 -1
- package/modal-v2/slots/card.js.map +1 -1
- package/modal-v2/slots/header.d.ts +5 -0
- package/modal-v2/slots/header.js +2 -0
- package/modal-v2/slots/header.js.map +1 -0
- package/modal-v2/slots/index.d.ts +1 -0
- package/modal-v2/slots/slot-styles.d.ts +4 -0
- package/modal-v2/slots/slot-styles.js +1 -1
- package/modal-v2/slots/slot-styles.js.map +1 -1
- package/modal-v2/slots/{slot-styles_13sd376.css → slot-styles_10ut1v7.css} +5 -1
- package/modal-v2/types.d.ts +21 -0
- package/navbar/navbar-items.js +1 -1
- package/navbar/navbar-items.js.map +1 -1
- package/navbar/navbar-items.styles.d.ts +2 -1
- package/navbar/navbar-items.styles.js +1 -1
- package/navbar/navbar-items.styles.js.map +1 -1
- package/navbar/{navbar-items.styles_1i3ltot.css → navbar-items.styles_1b1zx.css} +9 -8
- package/navbar/navbar-logo-data.js +1 -1
- package/navbar/navbar-logo-data.js.map +1 -1
- package/navbar/navbar.js.map +1 -1
- package/otp-input/otp-input.js.map +1 -1
- package/overlay/overlay.js.map +1 -1
- package/package.json +1 -1
- package/pagination/pagination.js.map +1 -1
- package/phone-number-input/phone-number-input.js.map +1 -1
- package/predictive-text-input/predictive-text-input.js.map +1 -1
- package/shared/dropdown-list/dropdown-list.js.map +1 -1
- package/shared/internal-calendar/calendar-manager.js.map +1 -1
- package/shared/standalone-date-input/standalone-date-input.js.map +1 -1
- package/theme/styles/a11y-playground.css +358 -87
- package/theme/styles/bookingsg.css +391 -85
- package/theme/styles/careercompass.css +396 -90
- package/theme/styles/ccube.css +402 -45
- package/theme/styles/imda.css +348 -85
- package/theme/styles/lifesg.css +377 -87
- package/theme/styles/mylegacy.css +382 -85
- package/theme/styles/oneservice.css +349 -86
- package/theme/styles/pa.css +172 -63
- package/theme/styles/rbs.css +403 -46
- package/theme/styles/sgw-digital-lobby.css +352 -87
- package/theme/styles/smgs.css +401 -95
- package/theme/styles/spf.css +208 -99
- package/theme/styles/supportgowhere.css +387 -90
- package/theme/styles/tote-board.css +816 -0
- package/theme/types.d.ts +1 -1
- package/theme/types.js +1 -1
- package/theme/types.js.map +1 -1
- package/time-slot-bar/time-slot-bar.js.map +1 -1
- package/time-slot-bar-week/time-slot-bar-week-days.js.map +1 -1
- package/timetable/timetable.js.map +1 -1
- package/toggle/toggle-icon.d.ts +2 -1
- package/toggle/toggle-icon.js +1 -1
- package/toggle/toggle-icon.js.map +1 -1
- package/toggle/toggle-icon.styles.d.ts +1 -0
- package/toggle/toggle-icon.styles.js +1 -1
- package/toggle/toggle-icon.styles.js.map +1 -1
- package/toggle/{toggle-icon.styles_ba71y.css → toggle-icon.styles_o6dxr3.css} +3 -2
- package/toggle/toggle.js +1 -1
- package/toggle/toggle.js.map +1 -1
- package/toggle/toggle.styles.d.ts +18 -15
- package/toggle/toggle.styles.js +1 -1
- package/toggle/toggle.styles.js.map +1 -1
- package/toggle/toggle.styles_11tmfsr.css +41 -0
- package/unit-number/unit-number-input.js.map +1 -1
- package/util/is-styled-components.js.map +1 -1
- package/util/string-helper.d.ts +2 -2
- package/util/string-helper.js +1 -1
- package/util/string-helper.js.map +1 -1
- package/util/use-event-listener.js.map +1 -1
- package/util/use-state-actions.js.map +1 -1
- package/cjs/form/form-textarea/form-textarea.styles.js +0 -2
- package/cjs/form/form-textarea/form-textarea.styles.js.map +0 -1
- package/cjs/form/form-textarea/form-textarea.styles_1kneyif.css +0 -3
- package/cjs/toggle/toggle.styles_1t5qrye.css +0 -38
- package/form/form-textarea/form-textarea.styles.d.ts +0 -3
- package/form/form-textarea/form-textarea.styles.js +0 -2
- package/form/form-textarea/form-textarea.styles.js.map +0 -1
- package/form/form-textarea/form-textarea.styles_1kneyif.css +0 -3
- package/theme/styles/default.css +0 -158
- package/toggle/toggle.styles_1t5qrye.css +0 -38
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-item.js","sources":["../../src/accordion/accordion-item.tsx"],"sourcesContent":["import { ChevronUpIcon } from \"@lifesg/react-icons/chevron-up\";\nimport { animated, useSpring } from \"@react-spring/web\";\nimport clsx from \"clsx\";\nimport type React from \"react\";\nimport {\n forwardRef,\n useContext,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { inertValue } from \"../shared/accessibility\";\nimport { useId } from \"../util\";\nimport { AccordionContext } from \"./accordion-context\";\nimport * as styles from \"./accordion-item.styles\";\nimport type {\n AccordionItemApi,\n AccordionItemHandle,\n AccordionItemProps,\n} from \"./types\";\n\nfunction Component(\n {\n title,\n children,\n type = \"default\",\n collapsible = true,\n className,\n id,\n expanded: expandedControlled,\n \"data-testid\": testId = \"accordion-item\",\n }: AccordionItemProps,\n ref: React.Ref<AccordionItemHandle>\n) {\n // =========================================================================\n // CONST, STATE, REF\n // =========================================================================\n const elementRef = useRef<HTMLDivElement>(null);\n const {\n expandAll,\n itemHeadingLevel,\n onItemStateChange,\n itemState,\n onItemDeregister,\n } = useContext(AccordionContext);\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const internalId = useId();\n const contentId = `${internalId}-content`;\n const { height, ref: resizeDetectorRef } = useResizeDetector();\n const expanded =\n itemState[internalId] ??\n (collapsible ? expandedControlled ?? expandAll : true); // the initial value\n\n useImperativeHandle(\n ref,\n () =>\n Object.assign<HTMLDivElement, AccordionItemApi>(\n elementRef.current!,\n {\n expand(): void {\n onItemStateChange(internalId, true);\n },\n collapse(): void {\n onItemStateChange(internalId, false);\n },\n isExpanded() {\n return expanded;\n },\n }\n ),\n [expanded]\n );\n\n // =========================================================================\n // EFFECTS\n // =========================================================================\n useEffect(() => {\n setHasFirstLoad(true);\n\n return () => onItemDeregister?.(internalId);\n }, []);\n\n useEffect(() => {\n onItemStateChange(\n internalId,\n collapsible ? expandedControlled ?? expandAll : true\n );\n }, [expandAll, expandedControlled, collapsible]);\n\n // =========================================================================\n // EVENT HANDLERS\n // =========================================================================\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n onItemStateChange(internalId, !expanded);\n };\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n // React spring animation configuration\n const resizeHeight = {\n height: expanded ? height : 0,\n };\n const expandableStyles = useSpring(resizeHeight);\n\n const renderContent = () => {\n return (\n <animated.div\n id={contentId}\n className={styles.expandable}\n style={hasFirstLoad ? expandableStyles : undefined}\n data-testid={`${testId}-expandable-container`}\n inert={inertValue(!expanded)}\n >\n <div\n ref={resizeDetectorRef}\n className={styles.contentContainer}\n data-testid=\"content-container\"\n >\n {children}\n </div>\n </animated.div>\n );\n };\n\n const renderTitleText = () => {\n if (typeof title !== \"string\") {\n return title;\n }\n\n return (\n <span\n data-testid={`${testId}-title`}\n className={clsx(\n styles.title,\n type === \"small\" && styles.titleSmall\n )}\n >\n {title}\n </span>\n );\n };\n\n const renderTitle = () => {\n return (\n <h3 aria-level={itemHeadingLevel}>\n <button\n data-testid={`${testId}-expand-collapse-button`}\n onClick={\n collapsible ? handleExpandCollapseClick : undefined\n }\n className={clsx(\n styles.expandCollapseButton,\n expanded && styles.expandCollapseButtonExpanded,\n collapsible && styles.expandCollapseButtonCollapsible\n )}\n aria-controls={contentId}\n aria-disabled={!collapsible} // remains focusable\n aria-expanded={expanded}\n >\n {renderTitleText()}\n {collapsible && (\n <span\n data-testid={`${testId}-expand-collapse-icon`}\n className={clsx(\n styles.iconContainer,\n expanded && styles.iconContainerExpanded\n )}\n >\n <ChevronUpIcon className={styles.chevronIcon} />\n </span>\n )}\n </button>\n </h3>\n );\n };\n\n return (\n <div\n data-testid={testId}\n className={clsx(\n styles.container,\n expanded && styles.containerExpanded,\n className\n )}\n id={id}\n ref={elementRef}\n >\n {renderTitle()}\n {renderContent()}\n </div>\n );\n}\n\nexport const AccordionItem = forwardRef<\n AccordionItemHandle,\n AccordionItemProps\n>(Component);\n"],"names":["AccordionItem","forwardRef","title","children","type","collapsible","className","id","expanded","expandedControlled","testId","ref","elementRef","useRef","expandAll","itemHeadingLevel","onItemStateChange","itemState","onItemDeregister","useContext","AccordionContext","hasFirstLoad","setHasFirstLoad","useState","internalId","useId","contentId","height","resizeDetectorRef","useResizeDetector","_a","useImperativeHandle","Object","assign","current","expand","collapse","isExpanded","useEffect","handleExpandCollapseClick","event","preventDefault","expandableStyles","useSpring","_jsxs","clsx","styles.container","styles.containerExpanded","_jsx","onClick","undefined","styles.expandCollapseButton","styles.expandCollapseButtonExpanded","styles.expandCollapseButtonCollapsible","styles.title","styles.titleSmall","styles.iconContainer","styles.iconContainerExpanded","ChevronUpIcon","styles.chevronIcon","animated","div","styles.expandable","style","inert","inertValue","styles.contentContainer"],"mappings":"
|
|
1
|
+
{"version":3,"file":"accordion-item.js","sources":["../../src/accordion/accordion-item.tsx"],"sourcesContent":["import { ChevronUpIcon } from \"@lifesg/react-icons/chevron-up\";\nimport { animated, useSpring } from \"@react-spring/web\";\nimport clsx from \"clsx\";\nimport type React from \"react\";\nimport {\n forwardRef,\n useContext,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { inertValue } from \"../shared/accessibility\";\nimport { useId } from \"../util\";\nimport { AccordionContext } from \"./accordion-context\";\nimport * as styles from \"./accordion-item.styles\";\nimport type {\n AccordionItemApi,\n AccordionItemHandle,\n AccordionItemProps,\n} from \"./types\";\n\nfunction Component(\n {\n title,\n children,\n type = \"default\",\n collapsible = true,\n className,\n id,\n expanded: expandedControlled,\n \"data-testid\": testId = \"accordion-item\",\n }: AccordionItemProps,\n ref: React.Ref<AccordionItemHandle>\n) {\n // =========================================================================\n // CONST, STATE, REF\n // =========================================================================\n const elementRef = useRef<HTMLDivElement>(null);\n const {\n expandAll,\n itemHeadingLevel,\n onItemStateChange,\n itemState,\n onItemDeregister,\n } = useContext(AccordionContext);\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const internalId = useId();\n const contentId = `${internalId}-content`;\n const { height, ref: resizeDetectorRef } = useResizeDetector();\n const expanded =\n itemState[internalId] ??\n (collapsible ? expandedControlled ?? expandAll : true); // the initial value\n\n useImperativeHandle(\n ref,\n () =>\n Object.assign<HTMLDivElement, AccordionItemApi>(\n elementRef.current!,\n {\n expand(): void {\n onItemStateChange(internalId, true);\n },\n collapse(): void {\n onItemStateChange(internalId, false);\n },\n isExpanded() {\n return expanded;\n },\n }\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [expanded]\n );\n\n // =========================================================================\n // EFFECTS\n // =========================================================================\n useEffect(() => {\n setHasFirstLoad(true);\n\n return () => onItemDeregister?.(internalId);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n onItemStateChange(\n internalId,\n collapsible ? expandedControlled ?? expandAll : true\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [expandAll, expandedControlled, collapsible]);\n\n // =========================================================================\n // EVENT HANDLERS\n // =========================================================================\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n onItemStateChange(internalId, !expanded);\n };\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n // React spring animation configuration\n const resizeHeight = {\n height: expanded ? height : 0,\n };\n const expandableStyles = useSpring(resizeHeight);\n\n const renderContent = () => {\n return (\n <animated.div\n id={contentId}\n className={styles.expandable}\n style={hasFirstLoad ? expandableStyles : undefined}\n data-testid={`${testId}-expandable-container`}\n inert={inertValue(!expanded)}\n >\n <div\n ref={resizeDetectorRef}\n className={styles.contentContainer}\n data-testid=\"content-container\"\n >\n {children}\n </div>\n </animated.div>\n );\n };\n\n const renderTitleText = () => {\n if (typeof title !== \"string\") {\n return title;\n }\n\n return (\n <span\n data-testid={`${testId}-title`}\n className={clsx(\n styles.title,\n type === \"small\" && styles.titleSmall\n )}\n >\n {title}\n </span>\n );\n };\n\n const renderTitle = () => {\n return (\n <h3 aria-level={itemHeadingLevel}>\n <button\n data-testid={`${testId}-expand-collapse-button`}\n onClick={\n collapsible ? handleExpandCollapseClick : undefined\n }\n className={clsx(\n styles.expandCollapseButton,\n expanded && styles.expandCollapseButtonExpanded,\n collapsible && styles.expandCollapseButtonCollapsible\n )}\n aria-controls={contentId}\n aria-disabled={!collapsible} // remains focusable\n aria-expanded={expanded}\n >\n {renderTitleText()}\n {collapsible && (\n <span\n data-testid={`${testId}-expand-collapse-icon`}\n className={clsx(\n styles.iconContainer,\n expanded && styles.iconContainerExpanded\n )}\n >\n <ChevronUpIcon className={styles.chevronIcon} />\n </span>\n )}\n </button>\n </h3>\n );\n };\n\n return (\n <div\n data-testid={testId}\n className={clsx(\n styles.container,\n expanded && styles.containerExpanded,\n className\n )}\n id={id}\n ref={elementRef}\n >\n {renderTitle()}\n {renderContent()}\n </div>\n );\n}\n\nexport const AccordionItem = forwardRef<\n AccordionItemHandle,\n AccordionItemProps\n>(Component);\n"],"names":["AccordionItem","forwardRef","title","children","type","collapsible","className","id","expanded","expandedControlled","testId","ref","elementRef","useRef","expandAll","itemHeadingLevel","onItemStateChange","itemState","onItemDeregister","useContext","AccordionContext","hasFirstLoad","setHasFirstLoad","useState","internalId","useId","contentId","height","resizeDetectorRef","useResizeDetector","_a","useImperativeHandle","Object","assign","current","expand","collapse","isExpanded","useEffect","handleExpandCollapseClick","event","preventDefault","expandableStyles","useSpring","_jsxs","clsx","styles.container","styles.containerExpanded","_jsx","onClick","undefined","styles.expandCollapseButton","styles.expandCollapseButtonExpanded","styles.expandCollapseButtonCollapsible","styles.title","styles.titleSmall","styles.iconContainer","styles.iconContainerExpanded","ChevronUpIcon","styles.chevronIcon","animated","div","styles.expandable","style","inert","inertValue","styles.contentContainer"],"mappings":"igCA0MaA,EAAgBC,GAlL7B,UACIC,MACIA,EAAKC,SACLA,EAAQC,KACRA,EAAO,UAASC,YAChBA,GAAc,EAAIC,UAClBA,EAASC,GACTA,EACAC,SAAUC,EACV,cAAeC,EAAS,kBAE5BC,SAKA,MAAMC,EAAaC,EAAuB,OACpCC,UACFA,EAASC,iBACTA,EAAgBC,kBAChBA,EAAiBC,UACjBA,EAASC,iBACTA,GACAC,EAAWC,IACRC,EAAcC,GAAmBC,GAAkB,GACpDC,EAAaC,IACbC,EAAY,GAAGF,aACfG,OAAEA,EAAQhB,IAAKiB,GAAsBC,IACrCrB,EACmB,QAArBsB,EAAAb,EAAUO,UAAW,IAAAM,EAAAA,GACpBzB,IAAcI,QAAAA,EAAsBK,GAEzCiB,EACIpB,GACA,IACIqB,OAAOC,OACHrB,EAAWsB,QACX,CACI,MAAAC,GACInB,EAAkBQ,GAAY,EAClC,EACA,QAAAY,GACIpB,EAAkBQ,GAAY,EAClC,EACAa,WAAU,IACC7B,KAKvB,CAACA,IAML8B,GAAU,KACNhB,GAAgB,GAET,IAAMJ,aAAgB,EAAhBA,EAAmBM,KAEjC,IAEHc,GAAU,KACNtB,EACIQ,GACAnB,IAAcI,QAAAA,EAAsBK,GACvC,GAEF,CAACA,EAAWL,EAAoBJ,IAMnC,MAAMkC,EAA6BC,IAC/BA,EAAMC,iBACNzB,EAAkBQ,GAAahB,EAAS,EAUtCkC,EAAmBC,EAHJ,CACjBhB,OAAQnB,EAAWmB,EAAS,IA4EhC,OACIiB,EAAA,MAAA,CAAA,cACiBlC,EACbJ,UAAWuC,EACPC,EACAtC,GAAYuC,EACZzC,GAEJC,GAAIA,EACJI,IAAKC,EAAUT,SAAA,CAzCf6C,EAAA,KAAA,CAAA,aAAgBjC,WACZ6B,EAAA,SAAA,CAAA,cACiB,GAAGlC,2BAChBuC,QACI5C,EAAckC,OAA4BW,EAE9C5C,UAAWuC,EACPM,EACA3C,GAAY4C,EACZ/C,GAAegD,mBAEJ3B,EAAS,iBACRrB,kBACDG,EAAQL,SAAA,CAhCd,iBAAVD,EACAA,EAIP8C,EAAA,OAAA,CAAA,cACiB,GAAGtC,UAChBJ,UAAWuC,EACPS,EACS,UAATlD,GAAoBmD,GACvBpD,SAEAD,IAuBIG,GACG2C,wBACiB,GAAGtC,yBAChBJ,UAAWuC,EACPW,EACAhD,GAAYiD,GACftD,SAED6C,EAACU,EAAa,CAACpD,UAAWqD,WA9D1CX,EAACY,EAASC,IAAG,CACTtD,GAAImB,EACJpB,UAAWwD,EACXC,MAAO1C,EAAeqB,OAAmBQ,EAAS,cACrC,GAAGxC,yBAChBsD,MAAOC,GAAYzD,GAASL,SAE5B6C,EAAA,MAAA,CACIrC,IAAKiB,EACLtB,UAAW4D,EAAuB,cACtB,oBAAmB/D,SAE9BA,QAyErB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","sources":["../../src/accordion/accordion.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport type React from \"react\";\nimport { useEffect, useState } from \"react\";\n\nimport { Button } from \"../button\";\nimport * as styles from \"./accordion.styles\";\nimport { AccordionContext } from \"./accordion-context\";\nimport { AccordionItem } from \"./accordion-item\";\nimport type { AccordionProps } from \"./types\";\n\nconst AccordionBase = ({\n children,\n title,\n enableExpandAll = true,\n initialDisplay = \"expand-all\",\n showTitleInMobile = false,\n id,\n \"data-testid\": testId,\n className,\n headingLevel = 2,\n onExpandCollapseChange,\n}: AccordionProps): JSX.Element => {\n const [expandAll, setExpandAll] = useState<boolean>(\n initialDisplay === \"expand-all\"\n );\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const [itemState, setItemState] = useState<Record<string, boolean>>({});\n\n useEffect(() => {\n setHasFirstLoad(true);\n }, []);\n\n useEffect(() => {\n if (hasFirstLoad) {\n onExpandCollapseChange?.(expandAll);\n }\n }, [expandAll, hasFirstLoad]);\n\n useEffect(() => {\n if (hasFirstLoad) {\n const itemStates = Object.values(itemState);\n if (itemStates.length > 0) {\n const allExpanded = itemStates.every((state) => state === true);\n const allCollapsed = itemStates.every(\n (state) => state === false\n );\n\n if (allExpanded && !expandAll) {\n setExpandAll(true);\n } else if (allCollapsed && expandAll) {\n setExpandAll(false);\n }\n }\n }\n }, [itemState, expandAll, hasFirstLoad]);\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n setExpandAll((prevExpandValue) => {\n const expandValue = !prevExpandValue;\n\n const updatedChildState = Object.keys(itemState).reduce(\n (acc, key) => {\n acc[key] = expandValue;\n return acc;\n },\n {} as typeof itemState\n );\n\n setItemState(updatedChildState);\n return expandValue;\n });\n };\n\n const onItemStateChange = (id: string, isExpanded: boolean) => {\n setItemState((prev) => ({ ...prev, [id]: isExpanded }));\n };\n\n const onItemDeregister = (id: string) => {\n setItemState((prev) => {\n const newState = { ...prev };\n delete newState[id];\n return newState;\n });\n };\n\n const renderCollapseExpandAll = () => {\n return (\n <Button\n className={styles.expandCollapseLink}\n data-testid=\"accordion-expand-collapse-button\"\n onClick={handleExpandCollapseClick}\n styleType=\"link\"\n type=\"button\"\n sizeType=\"small\"\n >\n {expandAll ? \"Hide all\" : \"Show all\"}\n </Button>\n );\n };\n\n const renderTitleWrapper = () => {\n if (!title && !enableExpandAll) {\n return null;\n }\n\n return (\n <div\n className={clsx(\n styles.titleWrapper,\n !showTitleInMobile &&\n !enableExpandAll &&\n styles.titleWrapperHidden\n )}\n >\n {title && (\n <h2\n className={clsx(\n styles.title,\n !showTitleInMobile && styles.titleHidden\n )}\n data-testid=\"accordion-title\"\n aria-level={headingLevel}\n >\n {title}\n </h2>\n )}\n {enableExpandAll && renderCollapseExpandAll()}\n </div>\n );\n };\n\n return (\n <AccordionContext.Provider\n value={{\n expandAll,\n itemHeadingLevel: headingLevel\n ? title\n ? headingLevel + 1\n : headingLevel\n : undefined,\n onItemStateChange,\n onItemDeregister,\n itemState,\n }}\n >\n <div\n id={id}\n data-testid={testId}\n className={clsx(styles.accordionWrapper, className)}\n >\n {renderTitleWrapper()}\n {children}\n </div>\n </AccordionContext.Provider>\n );\n};\n\n// @catalog\n/**\n * A vertically stacked set of collapsible content panels.\n *\n * Use `Accordion` to organise related content into independently expandable\n * sections with shared expand/collapse state. The container synchronises item\n * states and maintains correct heading-level hierarchy.\n *\n * Sub-components:\n * - `Accordion.Item` — a single collapsible panel with an imperative ref handle.\n */\nexport const Accordion = Object.assign(AccordionBase, {\n /**\n * Renders an individual collapsible section within an `Accordion`.\n *\n * Each item has a clickable header that toggles its content panel. The expanded\n * state can be individually controlled via the `expanded` prop, or left to the\n * parent `Accordion` to manage through its \"Show all\" / \"Hide all\" mechanism.\n * Exposes an imperative ref handle (`AccordionItemHandle`) for programmatic\n * expand/collapse control.\n */\n Item: AccordionItem,\n});\n"],"names":["Accordion","Object","assign","children","title","enableExpandAll","initialDisplay","showTitleInMobile","id","testId","className","headingLevel","onExpandCollapseChange","expandAll","setExpandAll","useState","hasFirstLoad","setHasFirstLoad","itemState","setItemState","useEffect","itemStates","values","length","allExpanded","every","state","allCollapsed","handleExpandCollapseClick","event","preventDefault","prevExpandValue","expandValue","updatedChildState","keys","reduce","acc","key","_jsx","AccordionContext","Provider","value","itemHeadingLevel","undefined","onItemStateChange","isExpanded","prev","onItemDeregister","newState","_jsxs","clsx","styles.accordionWrapper","styles.titleWrapper","styles.titleWrapperHidden","styles.title","styles.titleHidden","Button","styles.expandCollapseLink","onClick","styleType","type","sizeType","Item","AccordionItem"],"mappings":"waAUA,
|
|
1
|
+
{"version":3,"file":"accordion.js","sources":["../../src/accordion/accordion.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport type React from \"react\";\nimport { useEffect, useState } from \"react\";\n\nimport { Button } from \"../button\";\nimport * as styles from \"./accordion.styles\";\nimport { AccordionContext } from \"./accordion-context\";\nimport { AccordionItem } from \"./accordion-item\";\nimport type { AccordionProps } from \"./types\";\n\nconst AccordionBase = ({\n children,\n title,\n enableExpandAll = true,\n initialDisplay = \"expand-all\",\n showTitleInMobile = false,\n id,\n \"data-testid\": testId,\n className,\n headingLevel = 2,\n onExpandCollapseChange,\n}: AccordionProps): JSX.Element => {\n const [expandAll, setExpandAll] = useState<boolean>(\n initialDisplay === \"expand-all\"\n );\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const [itemState, setItemState] = useState<Record<string, boolean>>({});\n\n useEffect(() => {\n setHasFirstLoad(true);\n }, []);\n\n useEffect(() => {\n if (hasFirstLoad) {\n onExpandCollapseChange?.(expandAll);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [expandAll, hasFirstLoad]);\n\n useEffect(() => {\n if (hasFirstLoad) {\n const itemStates = Object.values(itemState);\n if (itemStates.length > 0) {\n const allExpanded = itemStates.every((state) => state === true);\n const allCollapsed = itemStates.every(\n (state) => state === false\n );\n\n if (allExpanded && !expandAll) {\n setExpandAll(true);\n } else if (allCollapsed && expandAll) {\n setExpandAll(false);\n }\n }\n }\n }, [itemState, expandAll, hasFirstLoad]);\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n setExpandAll((prevExpandValue) => {\n const expandValue = !prevExpandValue;\n\n const updatedChildState = Object.keys(itemState).reduce(\n (acc, key) => {\n acc[key] = expandValue;\n return acc;\n },\n {} as typeof itemState\n );\n\n setItemState(updatedChildState);\n return expandValue;\n });\n };\n\n const onItemStateChange = (id: string, isExpanded: boolean) => {\n setItemState((prev) => ({ ...prev, [id]: isExpanded }));\n };\n\n const onItemDeregister = (id: string) => {\n setItemState((prev) => {\n const newState = { ...prev };\n delete newState[id];\n return newState;\n });\n };\n\n const renderCollapseExpandAll = () => {\n return (\n <Button\n className={styles.expandCollapseLink}\n data-testid=\"accordion-expand-collapse-button\"\n onClick={handleExpandCollapseClick}\n styleType=\"link\"\n type=\"button\"\n sizeType=\"small\"\n >\n {expandAll ? \"Hide all\" : \"Show all\"}\n </Button>\n );\n };\n\n const renderTitleWrapper = () => {\n if (!title && !enableExpandAll) {\n return null;\n }\n\n return (\n <div\n className={clsx(\n styles.titleWrapper,\n !showTitleInMobile &&\n !enableExpandAll &&\n styles.titleWrapperHidden\n )}\n >\n {title && (\n <h2\n className={clsx(\n styles.title,\n !showTitleInMobile && styles.titleHidden\n )}\n data-testid=\"accordion-title\"\n aria-level={headingLevel}\n >\n {title}\n </h2>\n )}\n {enableExpandAll && renderCollapseExpandAll()}\n </div>\n );\n };\n\n return (\n <AccordionContext.Provider\n value={{\n expandAll,\n itemHeadingLevel: headingLevel\n ? title\n ? headingLevel + 1\n : headingLevel\n : undefined,\n onItemStateChange,\n onItemDeregister,\n itemState,\n }}\n >\n <div\n id={id}\n data-testid={testId}\n className={clsx(styles.accordionWrapper, className)}\n >\n {renderTitleWrapper()}\n {children}\n </div>\n </AccordionContext.Provider>\n );\n};\n\n// @catalog\n/**\n * A vertically stacked set of collapsible content panels.\n *\n * Use `Accordion` to organise related content into independently expandable\n * sections with shared expand/collapse state. The container synchronises item\n * states and maintains correct heading-level hierarchy.\n *\n * Sub-components:\n * - `Accordion.Item` — a single collapsible panel with an imperative ref handle.\n */\nexport const Accordion = Object.assign(AccordionBase, {\n /**\n * Renders an individual collapsible section within an `Accordion`.\n *\n * Each item has a clickable header that toggles its content panel. The expanded\n * state can be individually controlled via the `expanded` prop, or left to the\n * parent `Accordion` to manage through its \"Show all\" / \"Hide all\" mechanism.\n * Exposes an imperative ref handle (`AccordionItemHandle`) for programmatic\n * expand/collapse control.\n */\n Item: AccordionItem,\n});\n"],"names":["Accordion","Object","assign","children","title","enableExpandAll","initialDisplay","showTitleInMobile","id","testId","className","headingLevel","onExpandCollapseChange","expandAll","setExpandAll","useState","hasFirstLoad","setHasFirstLoad","itemState","setItemState","useEffect","itemStates","values","length","allExpanded","every","state","allCollapsed","handleExpandCollapseClick","event","preventDefault","prevExpandValue","expandValue","updatedChildState","keys","reduce","acc","key","_jsx","AccordionContext","Provider","value","itemHeadingLevel","undefined","onItemStateChange","isExpanded","prev","onItemDeregister","newState","_jsxs","clsx","styles.accordionWrapper","styles.titleWrapper","styles.titleWrapperHidden","styles.title","styles.titleHidden","Button","styles.expandCollapseLink","onClick","styleType","type","sizeType","Item","AccordionItem"],"mappings":"waAUA,MAgKaA,EAAYC,OAAOC,QAhKV,EAClBC,WAAQC,MACRA,EACAC,mBAAkB,EAClBC,iBAAiB,aACjBC,qBAAoB,EACpBC,KACA,cAAeC,EACfC,YACAC,eAAe,EACfC,6BAEA,MAAOC,EAAWC,GAAgBC,EACX,eAAnBT,IAEGU,EAAcC,GAAmBF,GAAkB,IACnDG,EAAWC,GAAgBJ,EAAkC,CAAA,GAEpEK,GAAU,KACNH,GAAgB,EAAK,GACtB,IAEHG,GAAU,KACFJ,IACAJ,SAAAA,EAAyBC,GAC7B,GAED,CAACA,EAAWG,IAEfI,GAAU,KACN,GAAIJ,EAAc,CACd,MAAMK,EAAapB,OAAOqB,OAAOJ,GACjC,GAAIG,EAAWE,OAAS,EAAG,CACvB,MAAMC,EAAcH,EAAWI,OAAOC,IAAoB,IAAVA,IAC1CC,EAAeN,EAAWI,OAC3BC,IAAoB,IAAVA,IAGXF,IAAgBX,EAChBC,GAAa,GACNa,GAAgBd,GACvBC,GAAa,EAErB,CACJ,IACD,CAACI,EAAWL,EAAWG,IAE1B,MAAMY,EAA6BC,IAC/BA,EAAMC,iBACNhB,GAAciB,IACV,MAAMC,GAAeD,EAEfE,EAAoBhC,OAAOiC,KAAKhB,GAAWiB,QAC7C,CAACC,EAAKC,KACFD,EAAIC,GAAOL,EACJI,IAEX,CAAA,GAIJ,OADAjB,EAAac,GACND,CAAW,GACpB,EA6DN,OACIM,EAACC,EAAiBC,SAAQ,CACtBC,MAAO,CACH5B,YACA6B,iBAAkB/B,EACZP,EACIO,EAAe,EACfA,OACJgC,EACNC,kBAnEc,CAACpC,EAAYqC,KACnC1B,GAAc2B,GAAS7C,OAAAC,OAAAD,OAAAC,OAAA,GAAM4C,GAAI,CAAEtC,CAACA,GAAKqC,KAAc,EAmE/CE,iBAhEcvC,IACtBW,GAAc2B,IACV,MAAME,EAAQ/C,OAAAC,OAAA,CAAA,EAAQ4C,GAEtB,cADOE,EAASxC,GACTwC,CAAQ,GACjB,EA4DM9B,aACHf,SAED8C,EAAA,MAAA,CACIzC,GAAIA,EAAE,cACOC,EACbC,UAAWwC,EAAKC,EAAyBzC,GAAUP,SAAA,CA/CtDC,GAAUC,EAKX4C,EAAA,MAAA,CACIvC,UAAWwC,EACPE,GACC7C,IACIF,GACDgD,GACPlD,SAAA,CAEAC,GACGkC,EAAA,KAAA,CACI5B,UAAWwC,EACPI,GACC/C,GAAqBgD,GACzB,cACW,kBAAiB,aACjB5C,EAAYR,SAEvBC,IAGRC,GAvCLiC,EAACkB,EAAM,CACH9C,UAAW+C,EAAyB,cACxB,mCACZC,QAAS9B,EACT+B,UAAU,OACVC,KAAK,SACLC,SAAS,iBAERhD,EAAY,WAAa,gBAOvB,KAiDFV,MAEmB,GAekB,CAUlD2D,KAAMC"}
|
package/alert/alert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","sources":["../../src/alert/alert.tsx"],"sourcesContent":["import { ChevronDownIcon } from \"@lifesg/react-icons\";\nimport { ArrowRightIcon } from \"@lifesg/react-icons/arrow-right\";\nimport { ExclamationCircleFillIcon } from \"@lifesg/react-icons/exclamation-circle-fill\";\nimport { ExclamationTriangleFillIcon } from \"@lifesg/react-icons/exclamation-triangle-fill\";\nimport { ICircleFillIcon } from \"@lifesg/react-icons/i-circle-fill\";\nimport { TickCircleFillIcon } from \"@lifesg/react-icons/tick-circle-fill\";\nimport clsx from \"clsx\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { Markup } from \"../markup\";\nimport { inertValue } from \"../shared/accessibility\";\nimport { useApplyStyle } from \"../theme\";\nimport { Typography } from \"../typography\";\nimport { useId } from \"../util\";\nimport * as styles from \"./alert.styles\";\nimport type { AlertProps } from \"./types\";\n\nconst TYPE_CLASS_MAP = {\n error: {\n wrapperTypeClass: styles.wrapperError,\n iconColorClass: styles.alertIconError,\n },\n success: {\n wrapperTypeClass: styles.wrapperSuccess,\n iconColorClass: styles.alertIconSuccess,\n },\n warning: {\n wrapperTypeClass: styles.wrapperWarning,\n iconColorClass: styles.alertIconWarning,\n },\n info: {\n wrapperTypeClass: styles.wrapperInfo,\n iconColorClass: styles.alertIconInfo,\n },\n description: {\n wrapperTypeClass: styles.wrapperDescription,\n iconColorClass: styles.alertIconDescription,\n },\n};\n\n// @catalog\n/**\n * Displays an inline contextual banner with a severity-coded message.\n *\n * Use `Alert` to surface feedback, status, or supplementary information without\n * interrupting the user's workflow. Supports five severity tones, an optional\n * icon, a call-to-action link, and a collapsible content area for lengthy\n * messages.\n */\nexport const Alert = ({\n type,\n className,\n children,\n actionLink,\n actionLinkIcon,\n sizeType = \"default\",\n showIcon = false,\n customIcon,\n maxCollapsedHeight,\n role,\n ...otherProps\n}: AlertProps): JSX.Element => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n const [showHiddenContent, setShowHiddenContent] = useState<boolean>(false);\n const [renderShowMore, setRenderShowMore] = useState<boolean>(false);\n const { height: contentHeight, ref: contentRef } =\n useResizeDetector<HTMLDivElement>();\n const contentId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n const isCollapsed = isContentOutsideCollapsibleZone();\n\n // =============================================================================\n // HELPERS\n // =============================================================================\n\n const getAlertLabel = () => {\n switch (type) {\n case \"success\":\n return \"Success\";\n case \"warning\":\n return \"Warning\";\n case \"error\":\n return \"Error\";\n case \"info\":\n return \"Information\";\n case \"description\":\n return \"Description\";\n default:\n return \"Alert\";\n }\n };\n\n const setCollapsedState = useCallback(() => {\n setShowHiddenContent(!maxCollapsedHeight);\n setRenderShowMore(isContentOutsideCollapsibleZone());\n }, [maxCollapsedHeight, contentHeight]);\n\n function isContentOutsideCollapsibleZone() {\n if (contentHeight && maxCollapsedHeight) {\n return contentHeight > maxCollapsedHeight;\n }\n return false;\n }\n\n const isInert = () => {\n return !!maxCollapsedHeight && !showHiddenContent;\n };\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useEffect(() => {\n setCollapsedState();\n }, [maxCollapsedHeight, contentHeight, setCollapsedState]);\n\n useApplyStyle(containerRef, {\n [styles.tokens.maxCollapsedHeight]:\n !showHiddenContent && isCollapsed && maxCollapsedHeight\n ? `${maxCollapsedHeight}px`\n : null,\n });\n\n // =============================================================================\n // RENDER FUNCTIONS\n // =============================================================================\n\n const renderShowMoreButton = () => (\n <button\n className={clsx(\n styles.showMoreButton,\n sizeType === \"small\"\n ? styles.showMoreButtonSmall\n : styles.showMoreButtonDefault\n )}\n type=\"button\"\n aria-expanded={showHiddenContent}\n aria-controls={contentId}\n onClick={() => setShowHiddenContent(!showHiddenContent)}\n >\n Show {showHiddenContent ? \"less\" : \"more\"}\n <ChevronDownIcon\n className={clsx(\n styles.chevronIcon,\n showHiddenContent\n ? styles.chevronIconExpanded\n : styles.chevronIconCollapsed\n )}\n />\n </button>\n );\n\n const renderLinkType = () => {\n if (actionLinkIcon) return actionLinkIcon;\n return <ArrowRightIcon />;\n };\n\n const renderLink = () => {\n if (!actionLink) return null;\n\n return (\n <Typography.LinkSM\n data-testid=\"action-link\"\n className={clsx(\n styles.actionLinkText,\n sizeType === \"small\"\n ? styles.actionLinkTextSmall\n : styles.actionLinkTextDefault\n )}\n underlineStyle=\"none\"\n {...actionLink}\n >\n {actionLink.children}\n {renderLinkType()}\n </Typography.LinkSM>\n );\n };\n\n const renderIcon = () => {\n if (type && customIcon) return customIcon;\n switch (type) {\n case \"success\":\n return <TickCircleFillIcon aria-hidden />;\n case \"warning\":\n return <ExclamationTriangleFillIcon aria-hidden />;\n case \"error\":\n return <ExclamationCircleFillIcon aria-hidden />;\n case \"info\":\n return <ICircleFillIcon aria-hidden />;\n case \"description\":\n return <ICircleFillIcon aria-hidden />;\n default:\n return null;\n }\n };\n\n const renderContent = () => {\n return (\n <div\n id={contentId}\n className={clsx(\n styles.textWrapperContainer,\n showHiddenContent &&\n actionLink &&\n styles.textWrapperContainerShowMoreWithLink,\n !showHiddenContent &&\n isCollapsed &&\n styles.textWrapperContainerCollapsed\n )}\n inert={inertValue(isInert())}\n ref={containerRef}\n >\n <Markup\n ref={contentRef}\n className={styles.textWrapper}\n baseTextSize={sizeType === \"small\" ? \"body-sm\" : \"body-md\"}\n >\n {children}\n </Markup>\n {renderLink()}\n </div>\n );\n };\n\n const {\n wrapperTypeClass = styles.wrapperWarning,\n iconColorClass = styles.alertIconWarning,\n } = TYPE_CLASS_MAP[type] || {};\n\n return (\n <div\n className={clsx(styles.wrapper, wrapperTypeClass, className)}\n aria-label={`${getAlertLabel()}`}\n data-testid={otherProps[\"data-testid\"]}\n role={role}\n >\n {showIcon && (\n <div\n className={clsx(\n styles.alertIconWrapper,\n sizeType === \"small\"\n ? styles.alertIconWrapperSmall\n : styles.alertIconWrapperDefault,\n iconColorClass\n )}\n >\n {renderIcon()}\n </div>\n )}\n {/* \n CSS-BASED TAB ORDER IMPLEMENTATION:\n - DOM order: button → link → content (natural tab order)\n - Visual order: content → button (via CSS order property)\n */}\n <div className={styles.contentContainer}>\n {renderShowMore && renderShowMoreButton()}\n {renderContent()}\n </div>\n </div>\n );\n};\n"],"names":["TYPE_CLASS_MAP","error","wrapperTypeClass","styles.wrapperError","iconColorClass","styles.alertIconError","success","styles.wrapperSuccess","styles.alertIconSuccess","warning","styles.wrapperWarning","styles.alertIconWarning","info","styles.wrapperInfo","styles.alertIconInfo","description","styles.wrapperDescription","styles.alertIconDescription","Alert","_a","type","className","children","actionLink","actionLinkIcon","sizeType","showIcon","customIcon","maxCollapsedHeight","role","otherProps","__rest","showHiddenContent","setShowHiddenContent","useState","renderShowMore","setRenderShowMore","height","contentHeight","ref","contentRef","useResizeDetector","contentId","useId","containerRef","useRef","isCollapsed","isContentOutsideCollapsibleZone","setCollapsedState","useCallback","useEffect","useApplyStyle","styles.tokens","_jsxs","clsx","styles.wrapper","getAlertLabel","_jsx","styles.alertIconWrapper","styles.alertIconWrapperSmall","styles.alertIconWrapperDefault","TickCircleFillIcon","ExclamationTriangleFillIcon","ExclamationCircleFillIcon","ICircleFillIcon","renderIcon","styles.contentContainer","styles.showMoreButton","styles.showMoreButtonSmall","styles.showMoreButtonDefault","onClick","ChevronDownIcon","styles.chevronIcon","styles.chevronIconExpanded","styles.chevronIconCollapsed","id","styles.textWrapperContainer","styles.textWrapperContainerShowMoreWithLink","styles.textWrapperContainerCollapsed","inert","inertValue","Markup","styles.textWrapper","baseTextSize","Typography","LinkSM","Object","assign","styles.actionLinkText","styles.actionLinkTextSmall","styles.actionLinkTextDefault","underlineStyle","ArrowRightIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"alert.js","sources":["../../src/alert/alert.tsx"],"sourcesContent":["import { ChevronDownIcon } from \"@lifesg/react-icons\";\nimport { ArrowRightIcon } from \"@lifesg/react-icons/arrow-right\";\nimport { ExclamationCircleFillIcon } from \"@lifesg/react-icons/exclamation-circle-fill\";\nimport { ExclamationTriangleFillIcon } from \"@lifesg/react-icons/exclamation-triangle-fill\";\nimport { ICircleFillIcon } from \"@lifesg/react-icons/i-circle-fill\";\nimport { TickCircleFillIcon } from \"@lifesg/react-icons/tick-circle-fill\";\nimport clsx from \"clsx\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { Markup } from \"../markup\";\nimport { inertValue } from \"../shared/accessibility\";\nimport { useApplyStyle } from \"../theme\";\nimport { Typography } from \"../typography\";\nimport { useId } from \"../util\";\nimport * as styles from \"./alert.styles\";\nimport type { AlertProps } from \"./types\";\n\nconst TYPE_CLASS_MAP = {\n error: {\n wrapperTypeClass: styles.wrapperError,\n iconColorClass: styles.alertIconError,\n },\n success: {\n wrapperTypeClass: styles.wrapperSuccess,\n iconColorClass: styles.alertIconSuccess,\n },\n warning: {\n wrapperTypeClass: styles.wrapperWarning,\n iconColorClass: styles.alertIconWarning,\n },\n info: {\n wrapperTypeClass: styles.wrapperInfo,\n iconColorClass: styles.alertIconInfo,\n },\n description: {\n wrapperTypeClass: styles.wrapperDescription,\n iconColorClass: styles.alertIconDescription,\n },\n};\n\n// @catalog\n/**\n * Displays an inline contextual banner with a severity-coded message.\n *\n * Use `Alert` to surface feedback, status, or supplementary information without\n * interrupting the user's workflow. Supports five severity tones, an optional\n * icon, a call-to-action link, and a collapsible content area for lengthy\n * messages.\n */\nexport const Alert = ({\n type,\n className,\n children,\n actionLink,\n actionLinkIcon,\n sizeType = \"default\",\n showIcon = false,\n customIcon,\n maxCollapsedHeight,\n role,\n ...otherProps\n}: AlertProps): JSX.Element => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n const [showHiddenContent, setShowHiddenContent] = useState<boolean>(false);\n const [renderShowMore, setRenderShowMore] = useState<boolean>(false);\n const { height: contentHeight, ref: contentRef } =\n useResizeDetector<HTMLDivElement>();\n const contentId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n const isCollapsed = isContentOutsideCollapsibleZone();\n\n // =============================================================================\n // HELPERS\n // =============================================================================\n\n const getAlertLabel = () => {\n switch (type) {\n case \"success\":\n return \"Success\";\n case \"warning\":\n return \"Warning\";\n case \"error\":\n return \"Error\";\n case \"info\":\n return \"Information\";\n case \"description\":\n return \"Description\";\n default:\n return \"Alert\";\n }\n };\n\n const setCollapsedState = useCallback(() => {\n setShowHiddenContent(!maxCollapsedHeight);\n setRenderShowMore(isContentOutsideCollapsibleZone());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [maxCollapsedHeight, contentHeight]);\n\n function isContentOutsideCollapsibleZone() {\n if (contentHeight && maxCollapsedHeight) {\n return contentHeight > maxCollapsedHeight;\n }\n return false;\n }\n\n const isInert = () => {\n return !!maxCollapsedHeight && !showHiddenContent;\n };\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useEffect(() => {\n setCollapsedState();\n }, [maxCollapsedHeight, contentHeight, setCollapsedState]);\n\n useApplyStyle(containerRef, {\n [styles.tokens.maxCollapsedHeight]:\n !showHiddenContent && isCollapsed && maxCollapsedHeight\n ? `${maxCollapsedHeight}px`\n : null,\n });\n\n // =============================================================================\n // RENDER FUNCTIONS\n // =============================================================================\n\n const renderShowMoreButton = () => (\n <button\n className={clsx(\n styles.showMoreButton,\n sizeType === \"small\"\n ? styles.showMoreButtonSmall\n : styles.showMoreButtonDefault\n )}\n type=\"button\"\n aria-expanded={showHiddenContent}\n aria-controls={contentId}\n onClick={() => setShowHiddenContent(!showHiddenContent)}\n >\n Show {showHiddenContent ? \"less\" : \"more\"}\n <ChevronDownIcon\n className={clsx(\n styles.chevronIcon,\n showHiddenContent\n ? styles.chevronIconExpanded\n : styles.chevronIconCollapsed\n )}\n />\n </button>\n );\n\n const renderLinkType = () => {\n if (actionLinkIcon) return actionLinkIcon;\n return <ArrowRightIcon />;\n };\n\n const renderLink = () => {\n if (!actionLink) return null;\n\n return (\n <Typography.LinkSM\n data-testid=\"action-link\"\n className={clsx(\n styles.actionLinkText,\n sizeType === \"small\"\n ? styles.actionLinkTextSmall\n : styles.actionLinkTextDefault\n )}\n underlineStyle=\"none\"\n {...actionLink}\n >\n {actionLink.children}\n {renderLinkType()}\n </Typography.LinkSM>\n );\n };\n\n const renderIcon = () => {\n if (type && customIcon) return customIcon;\n switch (type) {\n case \"success\":\n return <TickCircleFillIcon aria-hidden />;\n case \"warning\":\n return <ExclamationTriangleFillIcon aria-hidden />;\n case \"error\":\n return <ExclamationCircleFillIcon aria-hidden />;\n case \"info\":\n return <ICircleFillIcon aria-hidden />;\n case \"description\":\n return <ICircleFillIcon aria-hidden />;\n default:\n return null;\n }\n };\n\n const renderContent = () => {\n return (\n <div\n id={contentId}\n className={clsx(\n styles.textWrapperContainer,\n showHiddenContent &&\n actionLink &&\n styles.textWrapperContainerShowMoreWithLink,\n !showHiddenContent &&\n isCollapsed &&\n styles.textWrapperContainerCollapsed\n )}\n inert={inertValue(isInert())}\n ref={containerRef}\n >\n <Markup\n ref={contentRef}\n className={styles.textWrapper}\n baseTextSize={sizeType === \"small\" ? \"body-sm\" : \"body-md\"}\n >\n {children}\n </Markup>\n {renderLink()}\n </div>\n );\n };\n\n const {\n wrapperTypeClass = styles.wrapperWarning,\n iconColorClass = styles.alertIconWarning,\n } = TYPE_CLASS_MAP[type] || {};\n\n return (\n <div\n className={clsx(styles.wrapper, wrapperTypeClass, className)}\n aria-label={`${getAlertLabel()}`}\n data-testid={otherProps[\"data-testid\"]}\n role={role}\n >\n {showIcon && (\n <div\n className={clsx(\n styles.alertIconWrapper,\n sizeType === \"small\"\n ? styles.alertIconWrapperSmall\n : styles.alertIconWrapperDefault,\n iconColorClass\n )}\n >\n {renderIcon()}\n </div>\n )}\n {/* \n CSS-BASED TAB ORDER IMPLEMENTATION:\n - DOM order: button → link → content (natural tab order)\n - Visual order: content → button (via CSS order property)\n */}\n <div className={styles.contentContainer}>\n {renderShowMore && renderShowMoreButton()}\n {renderContent()}\n </div>\n </div>\n );\n};\n"],"names":["TYPE_CLASS_MAP","error","wrapperTypeClass","styles.wrapperError","iconColorClass","styles.alertIconError","success","styles.wrapperSuccess","styles.alertIconSuccess","warning","styles.wrapperWarning","styles.alertIconWarning","info","styles.wrapperInfo","styles.alertIconInfo","description","styles.wrapperDescription","styles.alertIconDescription","Alert","_a","type","className","children","actionLink","actionLinkIcon","sizeType","showIcon","customIcon","maxCollapsedHeight","role","otherProps","__rest","showHiddenContent","setShowHiddenContent","useState","renderShowMore","setRenderShowMore","height","contentHeight","ref","contentRef","useResizeDetector","contentId","useId","containerRef","useRef","isCollapsed","isContentOutsideCollapsibleZone","setCollapsedState","useCallback","useEffect","useApplyStyle","styles.tokens","_jsxs","clsx","styles.wrapper","getAlertLabel","_jsx","styles.alertIconWrapper","styles.alertIconWrapperSmall","styles.alertIconWrapperDefault","TickCircleFillIcon","ExclamationTriangleFillIcon","ExclamationCircleFillIcon","ICircleFillIcon","renderIcon","styles.contentContainer","styles.showMoreButton","styles.showMoreButtonSmall","styles.showMoreButtonDefault","onClick","ChevronDownIcon","styles.chevronIcon","styles.chevronIconExpanded","styles.chevronIconCollapsed","id","styles.textWrapperContainer","styles.textWrapperContainerShowMoreWithLink","styles.textWrapperContainerCollapsed","inert","inertValue","Markup","styles.textWrapper","baseTextSize","Typography","LinkSM","Object","assign","styles.actionLinkText","styles.actionLinkTextSmall","styles.actionLinkTextDefault","underlineStyle","ArrowRightIcon"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-item.js","sources":["../../../src/accordion/accordion-item.tsx"],"sourcesContent":["import { ChevronUpIcon } from \"@lifesg/react-icons/chevron-up\";\nimport { animated, useSpring } from \"@react-spring/web\";\nimport clsx from \"clsx\";\nimport type React from \"react\";\nimport {\n forwardRef,\n useContext,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { inertValue } from \"../shared/accessibility\";\nimport { useId } from \"../util\";\nimport { AccordionContext } from \"./accordion-context\";\nimport * as styles from \"./accordion-item.styles\";\nimport type {\n AccordionItemApi,\n AccordionItemHandle,\n AccordionItemProps,\n} from \"./types\";\n\nfunction Component(\n {\n title,\n children,\n type = \"default\",\n collapsible = true,\n className,\n id,\n expanded: expandedControlled,\n \"data-testid\": testId = \"accordion-item\",\n }: AccordionItemProps,\n ref: React.Ref<AccordionItemHandle>\n) {\n // =========================================================================\n // CONST, STATE, REF\n // =========================================================================\n const elementRef = useRef<HTMLDivElement>(null);\n const {\n expandAll,\n itemHeadingLevel,\n onItemStateChange,\n itemState,\n onItemDeregister,\n } = useContext(AccordionContext);\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const internalId = useId();\n const contentId = `${internalId}-content`;\n const { height, ref: resizeDetectorRef } = useResizeDetector();\n const expanded =\n itemState[internalId] ??\n (collapsible ? expandedControlled ?? expandAll : true); // the initial value\n\n useImperativeHandle(\n ref,\n () =>\n Object.assign<HTMLDivElement, AccordionItemApi>(\n elementRef.current!,\n {\n expand(): void {\n onItemStateChange(internalId, true);\n },\n collapse(): void {\n onItemStateChange(internalId, false);\n },\n isExpanded() {\n return expanded;\n },\n }\n ),\n [expanded]\n );\n\n // =========================================================================\n // EFFECTS\n // =========================================================================\n useEffect(() => {\n setHasFirstLoad(true);\n\n return () => onItemDeregister?.(internalId);\n }, []);\n\n useEffect(() => {\n onItemStateChange(\n internalId,\n collapsible ? expandedControlled ?? expandAll : true\n );\n }, [expandAll, expandedControlled, collapsible]);\n\n // =========================================================================\n // EVENT HANDLERS\n // =========================================================================\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n onItemStateChange(internalId, !expanded);\n };\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n // React spring animation configuration\n const resizeHeight = {\n height: expanded ? height : 0,\n };\n const expandableStyles = useSpring(resizeHeight);\n\n const renderContent = () => {\n return (\n <animated.div\n id={contentId}\n className={styles.expandable}\n style={hasFirstLoad ? expandableStyles : undefined}\n data-testid={`${testId}-expandable-container`}\n inert={inertValue(!expanded)}\n >\n <div\n ref={resizeDetectorRef}\n className={styles.contentContainer}\n data-testid=\"content-container\"\n >\n {children}\n </div>\n </animated.div>\n );\n };\n\n const renderTitleText = () => {\n if (typeof title !== \"string\") {\n return title;\n }\n\n return (\n <span\n data-testid={`${testId}-title`}\n className={clsx(\n styles.title,\n type === \"small\" && styles.titleSmall\n )}\n >\n {title}\n </span>\n );\n };\n\n const renderTitle = () => {\n return (\n <h3 aria-level={itemHeadingLevel}>\n <button\n data-testid={`${testId}-expand-collapse-button`}\n onClick={\n collapsible ? handleExpandCollapseClick : undefined\n }\n className={clsx(\n styles.expandCollapseButton,\n expanded && styles.expandCollapseButtonExpanded,\n collapsible && styles.expandCollapseButtonCollapsible\n )}\n aria-controls={contentId}\n aria-disabled={!collapsible} // remains focusable\n aria-expanded={expanded}\n >\n {renderTitleText()}\n {collapsible && (\n <span\n data-testid={`${testId}-expand-collapse-icon`}\n className={clsx(\n styles.iconContainer,\n expanded && styles.iconContainerExpanded\n )}\n >\n <ChevronUpIcon className={styles.chevronIcon} />\n </span>\n )}\n </button>\n </h3>\n );\n };\n\n return (\n <div\n data-testid={testId}\n className={clsx(\n styles.container,\n expanded && styles.containerExpanded,\n className\n )}\n id={id}\n ref={elementRef}\n >\n {renderTitle()}\n {renderContent()}\n </div>\n );\n}\n\nexport const AccordionItem = forwardRef<\n AccordionItemHandle,\n AccordionItemProps\n>(Component);\n"],"names":["AccordionItem","forwardRef","title","children","type","collapsible","className","id","expanded","expandedControlled","testId","ref","elementRef","useRef","expandAll","itemHeadingLevel","onItemStateChange","itemState","onItemDeregister","useContext","AccordionContext","hasFirstLoad","setHasFirstLoad","useState","internalId","useId","contentId","height","resizeDetectorRef","useResizeDetector","_a","useImperativeHandle","Object","assign","current","expand","collapse","isExpanded","useEffect","handleExpandCollapseClick","event","preventDefault","resizeHeight","expandableStyles","useSpring","_jsxs","clsx","styles.container","styles.containerExpanded","_jsx","onClick","undefined","styles.expandCollapseButton","styles.expandCollapseButtonExpanded","styles.expandCollapseButtonCollapsible","styles.title","styles.titleSmall","styles.iconContainer","styles.iconContainerExpanded","ChevronUpIcon","styles.chevronIcon","animated","div","styles.expandable","style","inert","inertValue","styles.contentContainer"],"mappings":"
|
|
1
|
+
{"version":3,"file":"accordion-item.js","sources":["../../../src/accordion/accordion-item.tsx"],"sourcesContent":["import { ChevronUpIcon } from \"@lifesg/react-icons/chevron-up\";\nimport { animated, useSpring } from \"@react-spring/web\";\nimport clsx from \"clsx\";\nimport type React from \"react\";\nimport {\n forwardRef,\n useContext,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { inertValue } from \"../shared/accessibility\";\nimport { useId } from \"../util\";\nimport { AccordionContext } from \"./accordion-context\";\nimport * as styles from \"./accordion-item.styles\";\nimport type {\n AccordionItemApi,\n AccordionItemHandle,\n AccordionItemProps,\n} from \"./types\";\n\nfunction Component(\n {\n title,\n children,\n type = \"default\",\n collapsible = true,\n className,\n id,\n expanded: expandedControlled,\n \"data-testid\": testId = \"accordion-item\",\n }: AccordionItemProps,\n ref: React.Ref<AccordionItemHandle>\n) {\n // =========================================================================\n // CONST, STATE, REF\n // =========================================================================\n const elementRef = useRef<HTMLDivElement>(null);\n const {\n expandAll,\n itemHeadingLevel,\n onItemStateChange,\n itemState,\n onItemDeregister,\n } = useContext(AccordionContext);\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const internalId = useId();\n const contentId = `${internalId}-content`;\n const { height, ref: resizeDetectorRef } = useResizeDetector();\n const expanded =\n itemState[internalId] ??\n (collapsible ? expandedControlled ?? expandAll : true); // the initial value\n\n useImperativeHandle(\n ref,\n () =>\n Object.assign<HTMLDivElement, AccordionItemApi>(\n elementRef.current!,\n {\n expand(): void {\n onItemStateChange(internalId, true);\n },\n collapse(): void {\n onItemStateChange(internalId, false);\n },\n isExpanded() {\n return expanded;\n },\n }\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [expanded]\n );\n\n // =========================================================================\n // EFFECTS\n // =========================================================================\n useEffect(() => {\n setHasFirstLoad(true);\n\n return () => onItemDeregister?.(internalId);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n onItemStateChange(\n internalId,\n collapsible ? expandedControlled ?? expandAll : true\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [expandAll, expandedControlled, collapsible]);\n\n // =========================================================================\n // EVENT HANDLERS\n // =========================================================================\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n onItemStateChange(internalId, !expanded);\n };\n\n // =========================================================================\n // RENDER FUNCTIONS\n // =========================================================================\n // React spring animation configuration\n const resizeHeight = {\n height: expanded ? height : 0,\n };\n const expandableStyles = useSpring(resizeHeight);\n\n const renderContent = () => {\n return (\n <animated.div\n id={contentId}\n className={styles.expandable}\n style={hasFirstLoad ? expandableStyles : undefined}\n data-testid={`${testId}-expandable-container`}\n inert={inertValue(!expanded)}\n >\n <div\n ref={resizeDetectorRef}\n className={styles.contentContainer}\n data-testid=\"content-container\"\n >\n {children}\n </div>\n </animated.div>\n );\n };\n\n const renderTitleText = () => {\n if (typeof title !== \"string\") {\n return title;\n }\n\n return (\n <span\n data-testid={`${testId}-title`}\n className={clsx(\n styles.title,\n type === \"small\" && styles.titleSmall\n )}\n >\n {title}\n </span>\n );\n };\n\n const renderTitle = () => {\n return (\n <h3 aria-level={itemHeadingLevel}>\n <button\n data-testid={`${testId}-expand-collapse-button`}\n onClick={\n collapsible ? handleExpandCollapseClick : undefined\n }\n className={clsx(\n styles.expandCollapseButton,\n expanded && styles.expandCollapseButtonExpanded,\n collapsible && styles.expandCollapseButtonCollapsible\n )}\n aria-controls={contentId}\n aria-disabled={!collapsible} // remains focusable\n aria-expanded={expanded}\n >\n {renderTitleText()}\n {collapsible && (\n <span\n data-testid={`${testId}-expand-collapse-icon`}\n className={clsx(\n styles.iconContainer,\n expanded && styles.iconContainerExpanded\n )}\n >\n <ChevronUpIcon className={styles.chevronIcon} />\n </span>\n )}\n </button>\n </h3>\n );\n };\n\n return (\n <div\n data-testid={testId}\n className={clsx(\n styles.container,\n expanded && styles.containerExpanded,\n className\n )}\n id={id}\n ref={elementRef}\n >\n {renderTitle()}\n {renderContent()}\n </div>\n );\n}\n\nexport const AccordionItem = forwardRef<\n AccordionItemHandle,\n AccordionItemProps\n>(Component);\n"],"names":["AccordionItem","forwardRef","title","children","type","collapsible","className","id","expanded","expandedControlled","testId","ref","elementRef","useRef","expandAll","itemHeadingLevel","onItemStateChange","itemState","onItemDeregister","useContext","AccordionContext","hasFirstLoad","setHasFirstLoad","useState","internalId","useId","contentId","height","resizeDetectorRef","useResizeDetector","_a","useImperativeHandle","Object","assign","current","expand","collapse","isExpanded","useEffect","handleExpandCollapseClick","event","preventDefault","resizeHeight","expandableStyles","useSpring","_jsxs","clsx","styles.container","styles.containerExpanded","_jsx","onClick","undefined","styles.expandCollapseButton","styles.expandCollapseButtonExpanded","styles.expandCollapseButtonCollapsible","styles.title","styles.titleSmall","styles.iconContainer","styles.iconContainerExpanded","ChevronUpIcon","styles.chevronIcon","animated","div","styles.expandable","style","inert","inertValue","styles.contentContainer"],"mappings":"moBA0MaA,EAAgBC,EAAAA,YAlL7B,UACIC,MACIA,EAAKC,SACLA,EAAQC,KACRA,EAAO,UAASC,YAChBA,GAAc,EAAIC,UAClBA,EAASC,GACTA,EACAC,SAAUC,EACV,cAAeC,EAAS,kBAE5BC,SAKA,MAAMC,EAAaC,EAAAA,OAAuB,OACpCC,UACFA,EAASC,iBACTA,EAAgBC,kBAChBA,EAAiBC,UACjBA,EAASC,iBACTA,GACAC,EAAAA,WAAWC,qBACRC,EAAcC,GAAmBC,EAAAA,UAAkB,GACpDC,EAAaC,EAAAA,QACbC,EAAY,GAAGF,aACfG,OAAEA,EAAQhB,IAAKiB,GAAsBC,EAAAA,oBACrCrB,EACmB,QAArBsB,EAAAb,EAAUO,UAAW,IAAAM,EAAAA,GACpBzB,IAAcI,QAAAA,EAAsBK,GAEzCiB,EAAAA,oBACIpB,GACA,IACIqB,OAAOC,OACHrB,EAAWsB,QACX,CACI,MAAAC,GACInB,EAAkBQ,GAAY,EAClC,EACA,QAAAY,GACIpB,EAAkBQ,GAAY,EAClC,EACAa,WAAU,IACC7B,KAKvB,CAACA,IAML8B,EAAAA,WAAU,KACNhB,GAAgB,GAET,IAAMJ,aAAgB,EAAhBA,EAAmBM,KAEjC,IAEHc,EAAAA,WAAU,KACNtB,EACIQ,GACAnB,IAAcI,QAAAA,EAAsBK,GACvC,GAEF,CAACA,EAAWL,EAAoBJ,IAMnC,MAAMkC,EAA6BC,IAC/BA,EAAMC,iBACNzB,EAAkBQ,GAAahB,EAAS,EAOtCkC,EAAe,CACjBf,OAAQnB,EAAWmB,EAAS,GAE1BgB,EAAmBC,EAAAA,UAAUF,GA0EnC,OACIG,EAAAA,KAAA,MAAA,CAAA,cACiBnC,EACbJ,UAAWwC,EAAAA,QACPC,YACAvC,GAAYwC,EAAAA,kBACZ1C,GAEJC,GAAIA,EACJI,IAAKC,EAAUT,SAAA,CAzCf8C,EAAAA,IAAA,KAAA,CAAA,aAAgBlC,WACZ8B,OAAA,SAAA,CAAA,cACiB,GAAGnC,2BAChBwC,QACI7C,EAAckC,OAA4BY,EAE9C7C,UAAWwC,EAAAA,QACPM,EAAAA,qBACA5C,GAAY6C,+BACZhD,GAAeiD,EAAAA,iDAEJ5B,EAAS,iBACRrB,kBACDG,EAAQL,SAAA,CAhCd,iBAAVD,EACAA,EAIP+C,EAAAA,IAAA,OAAA,CAAA,cACiB,GAAGvC,UAChBJ,UAAWwC,EAAAA,QACPS,EAAAA,MACS,UAATnD,GAAoBoD,EAAAA,YACvBrD,SAEAD,IAuBIG,GACG4C,EAAAA,0BACiB,GAAGvC,yBAChBJ,UAAWwC,UACPW,EAAAA,cACAjD,GAAYkD,EAAAA,uBACfvD,SAED8C,EAAAA,IAACU,EAAAA,cAAa,CAACrD,UAAWsD,EAAAA,qBA9D1CX,EAAAA,IAACY,EAAAA,SAASC,IAAG,CACTvD,GAAImB,EACJpB,UAAWyD,EAAAA,WACXC,MAAO3C,EAAesB,OAAmBQ,EAAS,cACrC,GAAGzC,yBAChBuD,MAAOC,EAAAA,YAAY1D,GAASL,SAE5B8C,EAAAA,IAAA,MAAA,CACItC,IAAKiB,EACLtB,UAAW6D,EAAAA,iBAAuB,cACtB,oBAAmBhE,SAE9BA,QAyErB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","sources":["../../../src/accordion/accordion.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport type React from \"react\";\nimport { useEffect, useState } from \"react\";\n\nimport { Button } from \"../button\";\nimport * as styles from \"./accordion.styles\";\nimport { AccordionContext } from \"./accordion-context\";\nimport { AccordionItem } from \"./accordion-item\";\nimport type { AccordionProps } from \"./types\";\n\nconst AccordionBase = ({\n children,\n title,\n enableExpandAll = true,\n initialDisplay = \"expand-all\",\n showTitleInMobile = false,\n id,\n \"data-testid\": testId,\n className,\n headingLevel = 2,\n onExpandCollapseChange,\n}: AccordionProps): JSX.Element => {\n const [expandAll, setExpandAll] = useState<boolean>(\n initialDisplay === \"expand-all\"\n );\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const [itemState, setItemState] = useState<Record<string, boolean>>({});\n\n useEffect(() => {\n setHasFirstLoad(true);\n }, []);\n\n useEffect(() => {\n if (hasFirstLoad) {\n onExpandCollapseChange?.(expandAll);\n }\n }, [expandAll, hasFirstLoad]);\n\n useEffect(() => {\n if (hasFirstLoad) {\n const itemStates = Object.values(itemState);\n if (itemStates.length > 0) {\n const allExpanded = itemStates.every((state) => state === true);\n const allCollapsed = itemStates.every(\n (state) => state === false\n );\n\n if (allExpanded && !expandAll) {\n setExpandAll(true);\n } else if (allCollapsed && expandAll) {\n setExpandAll(false);\n }\n }\n }\n }, [itemState, expandAll, hasFirstLoad]);\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n setExpandAll((prevExpandValue) => {\n const expandValue = !prevExpandValue;\n\n const updatedChildState = Object.keys(itemState).reduce(\n (acc, key) => {\n acc[key] = expandValue;\n return acc;\n },\n {} as typeof itemState\n );\n\n setItemState(updatedChildState);\n return expandValue;\n });\n };\n\n const onItemStateChange = (id: string, isExpanded: boolean) => {\n setItemState((prev) => ({ ...prev, [id]: isExpanded }));\n };\n\n const onItemDeregister = (id: string) => {\n setItemState((prev) => {\n const newState = { ...prev };\n delete newState[id];\n return newState;\n });\n };\n\n const renderCollapseExpandAll = () => {\n return (\n <Button\n className={styles.expandCollapseLink}\n data-testid=\"accordion-expand-collapse-button\"\n onClick={handleExpandCollapseClick}\n styleType=\"link\"\n type=\"button\"\n sizeType=\"small\"\n >\n {expandAll ? \"Hide all\" : \"Show all\"}\n </Button>\n );\n };\n\n const renderTitleWrapper = () => {\n if (!title && !enableExpandAll) {\n return null;\n }\n\n return (\n <div\n className={clsx(\n styles.titleWrapper,\n !showTitleInMobile &&\n !enableExpandAll &&\n styles.titleWrapperHidden\n )}\n >\n {title && (\n <h2\n className={clsx(\n styles.title,\n !showTitleInMobile && styles.titleHidden\n )}\n data-testid=\"accordion-title\"\n aria-level={headingLevel}\n >\n {title}\n </h2>\n )}\n {enableExpandAll && renderCollapseExpandAll()}\n </div>\n );\n };\n\n return (\n <AccordionContext.Provider\n value={{\n expandAll,\n itemHeadingLevel: headingLevel\n ? title\n ? headingLevel + 1\n : headingLevel\n : undefined,\n onItemStateChange,\n onItemDeregister,\n itemState,\n }}\n >\n <div\n id={id}\n data-testid={testId}\n className={clsx(styles.accordionWrapper, className)}\n >\n {renderTitleWrapper()}\n {children}\n </div>\n </AccordionContext.Provider>\n );\n};\n\n// @catalog\n/**\n * A vertically stacked set of collapsible content panels.\n *\n * Use `Accordion` to organise related content into independently expandable\n * sections with shared expand/collapse state. The container synchronises item\n * states and maintains correct heading-level hierarchy.\n *\n * Sub-components:\n * - `Accordion.Item` — a single collapsible panel with an imperative ref handle.\n */\nexport const Accordion = Object.assign(AccordionBase, {\n /**\n * Renders an individual collapsible section within an `Accordion`.\n *\n * Each item has a clickable header that toggles its content panel. The expanded\n * state can be individually controlled via the `expanded` prop, or left to the\n * parent `Accordion` to manage through its \"Show all\" / \"Hide all\" mechanism.\n * Exposes an imperative ref handle (`AccordionItemHandle`) for programmatic\n * expand/collapse control.\n */\n Item: AccordionItem,\n});\n"],"names":["Accordion","Object","assign","children","title","enableExpandAll","initialDisplay","showTitleInMobile","id","testId","className","headingLevel","onExpandCollapseChange","expandAll","setExpandAll","useState","hasFirstLoad","setHasFirstLoad","itemState","setItemState","useEffect","itemStates","values","length","allExpanded","every","state","allCollapsed","handleExpandCollapseClick","event","preventDefault","prevExpandValue","expandValue","updatedChildState","keys","reduce","acc","key","_jsx","AccordionContext","Provider","value","itemHeadingLevel","undefined","onItemStateChange","isExpanded","prev","onItemDeregister","newState","_jsxs","clsx","styles.accordionWrapper","styles.titleWrapper","styles.titleWrapperHidden","styles.title","styles.titleHidden","Button","styles.expandCollapseLink","onClick","styleType","type","sizeType","Item","AccordionItem"],"mappings":"iTAUA,
|
|
1
|
+
{"version":3,"file":"accordion.js","sources":["../../../src/accordion/accordion.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport type React from \"react\";\nimport { useEffect, useState } from \"react\";\n\nimport { Button } from \"../button\";\nimport * as styles from \"./accordion.styles\";\nimport { AccordionContext } from \"./accordion-context\";\nimport { AccordionItem } from \"./accordion-item\";\nimport type { AccordionProps } from \"./types\";\n\nconst AccordionBase = ({\n children,\n title,\n enableExpandAll = true,\n initialDisplay = \"expand-all\",\n showTitleInMobile = false,\n id,\n \"data-testid\": testId,\n className,\n headingLevel = 2,\n onExpandCollapseChange,\n}: AccordionProps): JSX.Element => {\n const [expandAll, setExpandAll] = useState<boolean>(\n initialDisplay === \"expand-all\"\n );\n const [hasFirstLoad, setHasFirstLoad] = useState<boolean>(false);\n const [itemState, setItemState] = useState<Record<string, boolean>>({});\n\n useEffect(() => {\n setHasFirstLoad(true);\n }, []);\n\n useEffect(() => {\n if (hasFirstLoad) {\n onExpandCollapseChange?.(expandAll);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [expandAll, hasFirstLoad]);\n\n useEffect(() => {\n if (hasFirstLoad) {\n const itemStates = Object.values(itemState);\n if (itemStates.length > 0) {\n const allExpanded = itemStates.every((state) => state === true);\n const allCollapsed = itemStates.every(\n (state) => state === false\n );\n\n if (allExpanded && !expandAll) {\n setExpandAll(true);\n } else if (allCollapsed && expandAll) {\n setExpandAll(false);\n }\n }\n }\n }, [itemState, expandAll, hasFirstLoad]);\n\n const handleExpandCollapseClick = (event: React.MouseEvent) => {\n event.preventDefault();\n setExpandAll((prevExpandValue) => {\n const expandValue = !prevExpandValue;\n\n const updatedChildState = Object.keys(itemState).reduce(\n (acc, key) => {\n acc[key] = expandValue;\n return acc;\n },\n {} as typeof itemState\n );\n\n setItemState(updatedChildState);\n return expandValue;\n });\n };\n\n const onItemStateChange = (id: string, isExpanded: boolean) => {\n setItemState((prev) => ({ ...prev, [id]: isExpanded }));\n };\n\n const onItemDeregister = (id: string) => {\n setItemState((prev) => {\n const newState = { ...prev };\n delete newState[id];\n return newState;\n });\n };\n\n const renderCollapseExpandAll = () => {\n return (\n <Button\n className={styles.expandCollapseLink}\n data-testid=\"accordion-expand-collapse-button\"\n onClick={handleExpandCollapseClick}\n styleType=\"link\"\n type=\"button\"\n sizeType=\"small\"\n >\n {expandAll ? \"Hide all\" : \"Show all\"}\n </Button>\n );\n };\n\n const renderTitleWrapper = () => {\n if (!title && !enableExpandAll) {\n return null;\n }\n\n return (\n <div\n className={clsx(\n styles.titleWrapper,\n !showTitleInMobile &&\n !enableExpandAll &&\n styles.titleWrapperHidden\n )}\n >\n {title && (\n <h2\n className={clsx(\n styles.title,\n !showTitleInMobile && styles.titleHidden\n )}\n data-testid=\"accordion-title\"\n aria-level={headingLevel}\n >\n {title}\n </h2>\n )}\n {enableExpandAll && renderCollapseExpandAll()}\n </div>\n );\n };\n\n return (\n <AccordionContext.Provider\n value={{\n expandAll,\n itemHeadingLevel: headingLevel\n ? title\n ? headingLevel + 1\n : headingLevel\n : undefined,\n onItemStateChange,\n onItemDeregister,\n itemState,\n }}\n >\n <div\n id={id}\n data-testid={testId}\n className={clsx(styles.accordionWrapper, className)}\n >\n {renderTitleWrapper()}\n {children}\n </div>\n </AccordionContext.Provider>\n );\n};\n\n// @catalog\n/**\n * A vertically stacked set of collapsible content panels.\n *\n * Use `Accordion` to organise related content into independently expandable\n * sections with shared expand/collapse state. The container synchronises item\n * states and maintains correct heading-level hierarchy.\n *\n * Sub-components:\n * - `Accordion.Item` — a single collapsible panel with an imperative ref handle.\n */\nexport const Accordion = Object.assign(AccordionBase, {\n /**\n * Renders an individual collapsible section within an `Accordion`.\n *\n * Each item has a clickable header that toggles its content panel. The expanded\n * state can be individually controlled via the `expanded` prop, or left to the\n * parent `Accordion` to manage through its \"Show all\" / \"Hide all\" mechanism.\n * Exposes an imperative ref handle (`AccordionItemHandle`) for programmatic\n * expand/collapse control.\n */\n Item: AccordionItem,\n});\n"],"names":["Accordion","Object","assign","children","title","enableExpandAll","initialDisplay","showTitleInMobile","id","testId","className","headingLevel","onExpandCollapseChange","expandAll","setExpandAll","useState","hasFirstLoad","setHasFirstLoad","itemState","setItemState","useEffect","itemStates","values","length","allExpanded","every","state","allCollapsed","handleExpandCollapseClick","event","preventDefault","prevExpandValue","expandValue","updatedChildState","keys","reduce","acc","key","_jsx","AccordionContext","Provider","value","itemHeadingLevel","undefined","onItemStateChange","isExpanded","prev","onItemDeregister","newState","_jsxs","clsx","styles.accordionWrapper","styles.titleWrapper","styles.titleWrapperHidden","styles.title","styles.titleHidden","Button","styles.expandCollapseLink","onClick","styleType","type","sizeType","Item","AccordionItem"],"mappings":"iTAUA,MAgKaA,EAAYC,OAAOC,QAhKV,EAClBC,WACAC,QACAC,mBAAkB,EAClBC,iBAAiB,aACjBC,qBAAoB,EACpBC,KACA,cAAeC,EACfC,YACAC,eAAe,EACfC,6BAEA,MAAOC,EAAWC,GAAgBC,EAAAA,SACX,eAAnBT,IAEGU,EAAcC,GAAmBF,EAAAA,UAAkB,IACnDG,EAAWC,GAAgBJ,EAAAA,SAAkC,CAAA,GAEpEK,EAAAA,WAAU,KACNH,GAAgB,EAAK,GACtB,IAEHG,EAAAA,WAAU,KACFJ,IACAJ,SAAAA,EAAyBC,GAC7B,GAED,CAACA,EAAWG,IAEfI,EAAAA,WAAU,KACN,GAAIJ,EAAc,CACd,MAAMK,EAAapB,OAAOqB,OAAOJ,GACjC,GAAIG,EAAWE,OAAS,EAAG,CACvB,MAAMC,EAAcH,EAAWI,OAAOC,IAAoB,IAAVA,IAC1CC,EAAeN,EAAWI,OAC3BC,IAAoB,IAAVA,IAGXF,IAAgBX,EAChBC,GAAa,GACNa,GAAgBd,GACvBC,GAAa,EAErB,CACJ,IACD,CAACI,EAAWL,EAAWG,IAE1B,MAAMY,EAA6BC,IAC/BA,EAAMC,iBACNhB,GAAciB,IACV,MAAMC,GAAeD,EAEfE,EAAoBhC,OAAOiC,KAAKhB,GAAWiB,QAC7C,CAACC,EAAKC,KACFD,EAAIC,GAAOL,EACJI,IAEX,CAAA,GAIJ,OADAjB,EAAac,GACND,CAAW,GACpB,EA6DN,OACIM,MAACC,EAAAA,iBAAiBC,SAAQ,CACtBC,MAAO,CACH5B,YACA6B,iBAAkB/B,EACZP,EACIO,EAAe,EACfA,OACJgC,EACNC,kBAnEc,CAACpC,EAAYqC,KACnC1B,GAAc2B,GAAS7C,OAAAC,OAAAD,OAAAC,OAAA,GAAM4C,GAAI,CAAEtC,CAACA,GAAKqC,KAAc,EAmE/CE,iBAhEcvC,IACtBW,GAAc2B,IACV,MAAME,EAAQ/C,OAAAC,OAAA,CAAA,EAAQ4C,GAEtB,cADOE,EAASxC,GACTwC,CAAQ,GACjB,EA4DM9B,aACHf,SAED8C,EAAAA,KAAA,MAAA,CACIzC,GAAIA,EAAE,cACOC,EACbC,UAAWwC,EAAAA,QAAKC,mBAAyBzC,GAAUP,SAAA,CA/CtDC,GAAUC,EAKX4C,EAAAA,KAAA,MAAA,CACIvC,UAAWwC,EAAAA,QACPE,EAAAA,cACC7C,IACIF,GACDgD,EAAAA,oBACPlD,SAAA,CAEAC,GACGkC,EAAAA,IAAA,KAAA,CACI5B,UAAWwC,EAAAA,QACPI,EAAAA,OACC/C,GAAqBgD,EAAAA,aACzB,cACW,kBAAiB,aACjB5C,EAAYR,SAEvBC,IAGRC,GAvCLiC,EAAAA,IAACkB,EAAAA,OAAM,CACH9C,UAAW+C,EAAAA,mBAAyB,cACxB,mCACZC,QAAS9B,EACT+B,UAAU,OACVC,KAAK,SACLC,SAAS,iBAERhD,EAAY,WAAa,gBAOvB,KAiDFV,MAEmB,GAekB,CAUlD2D,KAAMC,EAAAA"}
|
package/cjs/alert/alert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","sources":["../../../src/alert/alert.tsx"],"sourcesContent":["import { ChevronDownIcon } from \"@lifesg/react-icons\";\nimport { ArrowRightIcon } from \"@lifesg/react-icons/arrow-right\";\nimport { ExclamationCircleFillIcon } from \"@lifesg/react-icons/exclamation-circle-fill\";\nimport { ExclamationTriangleFillIcon } from \"@lifesg/react-icons/exclamation-triangle-fill\";\nimport { ICircleFillIcon } from \"@lifesg/react-icons/i-circle-fill\";\nimport { TickCircleFillIcon } from \"@lifesg/react-icons/tick-circle-fill\";\nimport clsx from \"clsx\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { Markup } from \"../markup\";\nimport { inertValue } from \"../shared/accessibility\";\nimport { useApplyStyle } from \"../theme\";\nimport { Typography } from \"../typography\";\nimport { useId } from \"../util\";\nimport * as styles from \"./alert.styles\";\nimport type { AlertProps } from \"./types\";\n\nconst TYPE_CLASS_MAP = {\n error: {\n wrapperTypeClass: styles.wrapperError,\n iconColorClass: styles.alertIconError,\n },\n success: {\n wrapperTypeClass: styles.wrapperSuccess,\n iconColorClass: styles.alertIconSuccess,\n },\n warning: {\n wrapperTypeClass: styles.wrapperWarning,\n iconColorClass: styles.alertIconWarning,\n },\n info: {\n wrapperTypeClass: styles.wrapperInfo,\n iconColorClass: styles.alertIconInfo,\n },\n description: {\n wrapperTypeClass: styles.wrapperDescription,\n iconColorClass: styles.alertIconDescription,\n },\n};\n\n// @catalog\n/**\n * Displays an inline contextual banner with a severity-coded message.\n *\n * Use `Alert` to surface feedback, status, or supplementary information without\n * interrupting the user's workflow. Supports five severity tones, an optional\n * icon, a call-to-action link, and a collapsible content area for lengthy\n * messages.\n */\nexport const Alert = ({\n type,\n className,\n children,\n actionLink,\n actionLinkIcon,\n sizeType = \"default\",\n showIcon = false,\n customIcon,\n maxCollapsedHeight,\n role,\n ...otherProps\n}: AlertProps): JSX.Element => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n const [showHiddenContent, setShowHiddenContent] = useState<boolean>(false);\n const [renderShowMore, setRenderShowMore] = useState<boolean>(false);\n const { height: contentHeight, ref: contentRef } =\n useResizeDetector<HTMLDivElement>();\n const contentId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n const isCollapsed = isContentOutsideCollapsibleZone();\n\n // =============================================================================\n // HELPERS\n // =============================================================================\n\n const getAlertLabel = () => {\n switch (type) {\n case \"success\":\n return \"Success\";\n case \"warning\":\n return \"Warning\";\n case \"error\":\n return \"Error\";\n case \"info\":\n return \"Information\";\n case \"description\":\n return \"Description\";\n default:\n return \"Alert\";\n }\n };\n\n const setCollapsedState = useCallback(() => {\n setShowHiddenContent(!maxCollapsedHeight);\n setRenderShowMore(isContentOutsideCollapsibleZone());\n }, [maxCollapsedHeight, contentHeight]);\n\n function isContentOutsideCollapsibleZone() {\n if (contentHeight && maxCollapsedHeight) {\n return contentHeight > maxCollapsedHeight;\n }\n return false;\n }\n\n const isInert = () => {\n return !!maxCollapsedHeight && !showHiddenContent;\n };\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useEffect(() => {\n setCollapsedState();\n }, [maxCollapsedHeight, contentHeight, setCollapsedState]);\n\n useApplyStyle(containerRef, {\n [styles.tokens.maxCollapsedHeight]:\n !showHiddenContent && isCollapsed && maxCollapsedHeight\n ? `${maxCollapsedHeight}px`\n : null,\n });\n\n // =============================================================================\n // RENDER FUNCTIONS\n // =============================================================================\n\n const renderShowMoreButton = () => (\n <button\n className={clsx(\n styles.showMoreButton,\n sizeType === \"small\"\n ? styles.showMoreButtonSmall\n : styles.showMoreButtonDefault\n )}\n type=\"button\"\n aria-expanded={showHiddenContent}\n aria-controls={contentId}\n onClick={() => setShowHiddenContent(!showHiddenContent)}\n >\n Show {showHiddenContent ? \"less\" : \"more\"}\n <ChevronDownIcon\n className={clsx(\n styles.chevronIcon,\n showHiddenContent\n ? styles.chevronIconExpanded\n : styles.chevronIconCollapsed\n )}\n />\n </button>\n );\n\n const renderLinkType = () => {\n if (actionLinkIcon) return actionLinkIcon;\n return <ArrowRightIcon />;\n };\n\n const renderLink = () => {\n if (!actionLink) return null;\n\n return (\n <Typography.LinkSM\n data-testid=\"action-link\"\n className={clsx(\n styles.actionLinkText,\n sizeType === \"small\"\n ? styles.actionLinkTextSmall\n : styles.actionLinkTextDefault\n )}\n underlineStyle=\"none\"\n {...actionLink}\n >\n {actionLink.children}\n {renderLinkType()}\n </Typography.LinkSM>\n );\n };\n\n const renderIcon = () => {\n if (type && customIcon) return customIcon;\n switch (type) {\n case \"success\":\n return <TickCircleFillIcon aria-hidden />;\n case \"warning\":\n return <ExclamationTriangleFillIcon aria-hidden />;\n case \"error\":\n return <ExclamationCircleFillIcon aria-hidden />;\n case \"info\":\n return <ICircleFillIcon aria-hidden />;\n case \"description\":\n return <ICircleFillIcon aria-hidden />;\n default:\n return null;\n }\n };\n\n const renderContent = () => {\n return (\n <div\n id={contentId}\n className={clsx(\n styles.textWrapperContainer,\n showHiddenContent &&\n actionLink &&\n styles.textWrapperContainerShowMoreWithLink,\n !showHiddenContent &&\n isCollapsed &&\n styles.textWrapperContainerCollapsed\n )}\n inert={inertValue(isInert())}\n ref={containerRef}\n >\n <Markup\n ref={contentRef}\n className={styles.textWrapper}\n baseTextSize={sizeType === \"small\" ? \"body-sm\" : \"body-md\"}\n >\n {children}\n </Markup>\n {renderLink()}\n </div>\n );\n };\n\n const {\n wrapperTypeClass = styles.wrapperWarning,\n iconColorClass = styles.alertIconWarning,\n } = TYPE_CLASS_MAP[type] || {};\n\n return (\n <div\n className={clsx(styles.wrapper, wrapperTypeClass, className)}\n aria-label={`${getAlertLabel()}`}\n data-testid={otherProps[\"data-testid\"]}\n role={role}\n >\n {showIcon && (\n <div\n className={clsx(\n styles.alertIconWrapper,\n sizeType === \"small\"\n ? styles.alertIconWrapperSmall\n : styles.alertIconWrapperDefault,\n iconColorClass\n )}\n >\n {renderIcon()}\n </div>\n )}\n {/* \n CSS-BASED TAB ORDER IMPLEMENTATION:\n - DOM order: button → link → content (natural tab order)\n - Visual order: content → button (via CSS order property)\n */}\n <div className={styles.contentContainer}>\n {renderShowMore && renderShowMoreButton()}\n {renderContent()}\n </div>\n </div>\n );\n};\n"],"names":["TYPE_CLASS_MAP","error","wrapperTypeClass","styles.wrapperError","iconColorClass","styles.alertIconError","success","styles.wrapperSuccess","styles.alertIconSuccess","warning","styles.wrapperWarning","styles.alertIconWarning","info","styles.wrapperInfo","styles.alertIconInfo","description","styles.wrapperDescription","styles.alertIconDescription","_a","type","className","children","actionLink","actionLinkIcon","sizeType","showIcon","customIcon","maxCollapsedHeight","role","otherProps","__rest","showHiddenContent","setShowHiddenContent","useState","renderShowMore","setRenderShowMore","height","contentHeight","ref","contentRef","useResizeDetector","contentId","useId","containerRef","useRef","isCollapsed","isContentOutsideCollapsibleZone","setCollapsedState","useCallback","useEffect","useApplyStyle","styles.tokens","_jsxs","clsx","styles.wrapper","getAlertLabel","_jsx","styles.alertIconWrapper","styles.alertIconWrapperSmall","styles.alertIconWrapperDefault","TickCircleFillIcon","ExclamationTriangleFillIcon","ExclamationCircleFillIcon","ICircleFillIcon","renderIcon","styles.contentContainer","styles.showMoreButton","styles.showMoreButtonSmall","styles.showMoreButtonDefault","onClick","ChevronDownIcon","styles.chevronIcon","styles.chevronIconExpanded","styles.chevronIconCollapsed","id","styles.textWrapperContainer","styles.textWrapperContainerShowMoreWithLink","styles.textWrapperContainerCollapsed","inert","inertValue","Markup","styles.textWrapper","baseTextSize","Typography","LinkSM","Object","assign","styles.actionLinkText","styles.actionLinkTextSmall","styles.actionLinkTextDefault","underlineStyle","ArrowRightIcon"],"mappings":"suCAkBA,MAAMA,EAAiB,CACnBC,MAAO,CACHC,iBAAkBC,EAAAA,aAClBC,eAAgBC,EAAAA,gBAEpBC,QAAS,CACLJ,iBAAkBK,EAAAA,eAClBH,eAAgBI,EAAAA,kBAEpBC,QAAS,CACLP,iBAAkBQ,EAAAA,eAClBN,eAAgBO,EAAAA,kBAEpBC,KAAM,CACFV,iBAAkBW,EAAAA,YAClBT,eAAgBU,EAAAA,eAEpBC,YAAa,CACTb,iBAAkBc,EAAAA,mBAClBZ,eAAgBa,EAAAA,qCAaFC,IAAA,IAAAC,KAClBA,EAAIC,UACJA,EAASC,SACTA,EAAQC,WACRA,EAAUC,eACVA,EAAcC,SACdA,EAAW,UAASC,SACpBA,GAAW,EAAKC,WAChBA,EAAUC,mBACVA,EAAkBC,KAClBA,GAAIV,EACDW,EAAUC,EAAAA,OAAAZ,EAXK,CAAA,OAAA,YAAA,WAAA,aAAA,iBAAA,WAAA,WAAA,aAAA,qBAAA,SAgBlB,MAAOa,EAAmBC,GAAwBC,EAAAA,UAAkB,IAC7DC,EAAgBC,GAAqBF,EAAAA,UAAkB,IACtDG,OAAQC,EAAeC,IAAKC,GAChCC,EAAAA,oBACEC,EAAYC,EAAAA,QACZC,EAAeC,EAAAA,OAAuB,MACtCC,EAAcC,IAuBdC,EAAoBC,EAAAA,aAAY,KAClChB,GAAsBL,GACtBQ,EAAkBW,IAAkC,GACrD,CAACnB,EAAoBU,IAExB,SAASS,IACL,SAAIT,IAAiBV,IACVU,EAAgBV,CAG/B,CAUAsB,EAAAA,WAAU,KACNF,GAAmB,GACpB,CAACpB,EAAoBU,EAAeU,IAEvCG,EAAAA,cAAcP,EAAc,CACxB,CAACQ,SAAcxB,qBACVI,GAAqBc,GAAelB,EAC/B,GAAGA,MACH,OAOd,MAiGMzB,iBACFA,EAAmBQ,EAAAA,eAAqBN,eACxCA,EAAiBO,EAAAA,kBACjBX,EAAemB,IAAS,CAAA,EAE5B,OACIiC,EAAAA,KAAA,MAAA,CACIhC,UAAWiC,EAAAA,QAAKC,EAAAA,QAAgBpD,EAAkBkB,GAAU,aAChD,GA7JE,MAClB,OAAQD,GACJ,IAAK,UACD,MAAO,UACX,IAAK,UACD,MAAO,UACX,IAAK,QACD,MAAO,QACX,IAAK,OACD,MAAO,cACX,IAAK,cACD,MAAO,cACX,QACI,MAAO,UAgJIoC,KAAiB,cACnB1B,EAAW,eACxBD,KAAMA,EAAIP,SAAA,CAETI,GACG+B,EAAAA,IAAA,MAAA,CACIpC,UAAWiC,EAAAA,QACPI,EAAAA,iBACa,UAAbjC,EACMkC,EAAAA,sBACAC,0BACNvD,GACHiB,SAlEE,MACf,GAAIF,GAAQO,EAAY,OAAOA,EAC/B,OAAQP,GACJ,IAAK,UACD,OAAOqC,EAAAA,IAACI,EAAAA,mBAAkB,CAAA,eAAA,IAC9B,IAAK,UACD,OAAOJ,EAAAA,IAACK,EAAAA,4BAA2B,CAAA,eAAA,IACvC,IAAK,QACD,OAAOL,EAAAA,IAACM,EAAAA,0BAAyB,CAAA,eAAA,IACrC,IAAK,OAEL,IAAK,cACD,OAAON,EAAAA,IAACO,EAAAA,gBAAe,CAAA,eAAA,IAC3B,QACI,OAAO,OAsDFC,KAQTZ,EAAAA,KAAA,MAAA,CAAKhC,UAAW6C,mBAAuB5C,SAAA,CAClCa,GA/HTkB,EAAAA,KAAA,SAAA,CACIhC,UAAWiC,EAAAA,QACPa,EAAAA,eACa,UAAb1C,EACM2C,EAAAA,oBACAC,EAAAA,uBAEVjD,KAAK,SAAQ,gBACEY,EAAiB,gBACjBU,EACf4B,QAAS,IAAMrC,GAAsBD,GAAkBV,SAAA,CAAA,QAEjDU,EAAoB,OAAS,OACnCyB,EAAAA,IAACc,kBAAe,CACZlD,UAAWiC,EAAAA,QACPkB,EAAAA,YACAxC,EACMyC,EAAAA,oBACAC,6BAoDdrB,EAAAA,KAAA,MAAA,CACIsB,GAAIjC,EACJrB,UAAWiC,EAAAA,QACPsB,EAAAA,qBACA5C,GACIT,GACAsD,EAAAA,sCACH7C,GACGc,GACAgC,EAAAA,+BAERC,MAAOC,EAAAA,aAxGNpD,IAAuBI,GAyGxBO,IAAKK,EAAYtB,SAAA,CAEjBmC,MAACwB,EAAAA,QACG1C,IAAKC,EACLnB,UAAW6D,EAAAA,YACXC,aAA2B,UAAb1D,EAAuB,UAAY,UAASH,SAEzDA,IA3DRC,EAGD8B,OAAC+B,EAAAA,WAAWC,OAAMC,OAAAC,OAAA,CAAA,cACF,cACZlE,UAAWiC,UACPkC,EAAAA,eACa,UAAb/D,EACMgE,EAAAA,oBACAC,EAAAA,uBAEVC,eAAe,QACXpE,EAAU,CAAAD,SAAA,CAEbC,EAAWD,SAnBhBE,GACGiC,EAAAA,IAACmC,EAAAA,eAAc,QAIE,aAoGlB"}
|
|
1
|
+
{"version":3,"file":"alert.js","sources":["../../../src/alert/alert.tsx"],"sourcesContent":["import { ChevronDownIcon } from \"@lifesg/react-icons\";\nimport { ArrowRightIcon } from \"@lifesg/react-icons/arrow-right\";\nimport { ExclamationCircleFillIcon } from \"@lifesg/react-icons/exclamation-circle-fill\";\nimport { ExclamationTriangleFillIcon } from \"@lifesg/react-icons/exclamation-triangle-fill\";\nimport { ICircleFillIcon } from \"@lifesg/react-icons/i-circle-fill\";\nimport { TickCircleFillIcon } from \"@lifesg/react-icons/tick-circle-fill\";\nimport clsx from \"clsx\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useResizeDetector } from \"react-resize-detector\";\n\nimport { Markup } from \"../markup\";\nimport { inertValue } from \"../shared/accessibility\";\nimport { useApplyStyle } from \"../theme\";\nimport { Typography } from \"../typography\";\nimport { useId } from \"../util\";\nimport * as styles from \"./alert.styles\";\nimport type { AlertProps } from \"./types\";\n\nconst TYPE_CLASS_MAP = {\n error: {\n wrapperTypeClass: styles.wrapperError,\n iconColorClass: styles.alertIconError,\n },\n success: {\n wrapperTypeClass: styles.wrapperSuccess,\n iconColorClass: styles.alertIconSuccess,\n },\n warning: {\n wrapperTypeClass: styles.wrapperWarning,\n iconColorClass: styles.alertIconWarning,\n },\n info: {\n wrapperTypeClass: styles.wrapperInfo,\n iconColorClass: styles.alertIconInfo,\n },\n description: {\n wrapperTypeClass: styles.wrapperDescription,\n iconColorClass: styles.alertIconDescription,\n },\n};\n\n// @catalog\n/**\n * Displays an inline contextual banner with a severity-coded message.\n *\n * Use `Alert` to surface feedback, status, or supplementary information without\n * interrupting the user's workflow. Supports five severity tones, an optional\n * icon, a call-to-action link, and a collapsible content area for lengthy\n * messages.\n */\nexport const Alert = ({\n type,\n className,\n children,\n actionLink,\n actionLinkIcon,\n sizeType = \"default\",\n showIcon = false,\n customIcon,\n maxCollapsedHeight,\n role,\n ...otherProps\n}: AlertProps): JSX.Element => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n const [showHiddenContent, setShowHiddenContent] = useState<boolean>(false);\n const [renderShowMore, setRenderShowMore] = useState<boolean>(false);\n const { height: contentHeight, ref: contentRef } =\n useResizeDetector<HTMLDivElement>();\n const contentId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n const isCollapsed = isContentOutsideCollapsibleZone();\n\n // =============================================================================\n // HELPERS\n // =============================================================================\n\n const getAlertLabel = () => {\n switch (type) {\n case \"success\":\n return \"Success\";\n case \"warning\":\n return \"Warning\";\n case \"error\":\n return \"Error\";\n case \"info\":\n return \"Information\";\n case \"description\":\n return \"Description\";\n default:\n return \"Alert\";\n }\n };\n\n const setCollapsedState = useCallback(() => {\n setShowHiddenContent(!maxCollapsedHeight);\n setRenderShowMore(isContentOutsideCollapsibleZone());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [maxCollapsedHeight, contentHeight]);\n\n function isContentOutsideCollapsibleZone() {\n if (contentHeight && maxCollapsedHeight) {\n return contentHeight > maxCollapsedHeight;\n }\n return false;\n }\n\n const isInert = () => {\n return !!maxCollapsedHeight && !showHiddenContent;\n };\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useEffect(() => {\n setCollapsedState();\n }, [maxCollapsedHeight, contentHeight, setCollapsedState]);\n\n useApplyStyle(containerRef, {\n [styles.tokens.maxCollapsedHeight]:\n !showHiddenContent && isCollapsed && maxCollapsedHeight\n ? `${maxCollapsedHeight}px`\n : null,\n });\n\n // =============================================================================\n // RENDER FUNCTIONS\n // =============================================================================\n\n const renderShowMoreButton = () => (\n <button\n className={clsx(\n styles.showMoreButton,\n sizeType === \"small\"\n ? styles.showMoreButtonSmall\n : styles.showMoreButtonDefault\n )}\n type=\"button\"\n aria-expanded={showHiddenContent}\n aria-controls={contentId}\n onClick={() => setShowHiddenContent(!showHiddenContent)}\n >\n Show {showHiddenContent ? \"less\" : \"more\"}\n <ChevronDownIcon\n className={clsx(\n styles.chevronIcon,\n showHiddenContent\n ? styles.chevronIconExpanded\n : styles.chevronIconCollapsed\n )}\n />\n </button>\n );\n\n const renderLinkType = () => {\n if (actionLinkIcon) return actionLinkIcon;\n return <ArrowRightIcon />;\n };\n\n const renderLink = () => {\n if (!actionLink) return null;\n\n return (\n <Typography.LinkSM\n data-testid=\"action-link\"\n className={clsx(\n styles.actionLinkText,\n sizeType === \"small\"\n ? styles.actionLinkTextSmall\n : styles.actionLinkTextDefault\n )}\n underlineStyle=\"none\"\n {...actionLink}\n >\n {actionLink.children}\n {renderLinkType()}\n </Typography.LinkSM>\n );\n };\n\n const renderIcon = () => {\n if (type && customIcon) return customIcon;\n switch (type) {\n case \"success\":\n return <TickCircleFillIcon aria-hidden />;\n case \"warning\":\n return <ExclamationTriangleFillIcon aria-hidden />;\n case \"error\":\n return <ExclamationCircleFillIcon aria-hidden />;\n case \"info\":\n return <ICircleFillIcon aria-hidden />;\n case \"description\":\n return <ICircleFillIcon aria-hidden />;\n default:\n return null;\n }\n };\n\n const renderContent = () => {\n return (\n <div\n id={contentId}\n className={clsx(\n styles.textWrapperContainer,\n showHiddenContent &&\n actionLink &&\n styles.textWrapperContainerShowMoreWithLink,\n !showHiddenContent &&\n isCollapsed &&\n styles.textWrapperContainerCollapsed\n )}\n inert={inertValue(isInert())}\n ref={containerRef}\n >\n <Markup\n ref={contentRef}\n className={styles.textWrapper}\n baseTextSize={sizeType === \"small\" ? \"body-sm\" : \"body-md\"}\n >\n {children}\n </Markup>\n {renderLink()}\n </div>\n );\n };\n\n const {\n wrapperTypeClass = styles.wrapperWarning,\n iconColorClass = styles.alertIconWarning,\n } = TYPE_CLASS_MAP[type] || {};\n\n return (\n <div\n className={clsx(styles.wrapper, wrapperTypeClass, className)}\n aria-label={`${getAlertLabel()}`}\n data-testid={otherProps[\"data-testid\"]}\n role={role}\n >\n {showIcon && (\n <div\n className={clsx(\n styles.alertIconWrapper,\n sizeType === \"small\"\n ? styles.alertIconWrapperSmall\n : styles.alertIconWrapperDefault,\n iconColorClass\n )}\n >\n {renderIcon()}\n </div>\n )}\n {/* \n CSS-BASED TAB ORDER IMPLEMENTATION:\n - DOM order: button → link → content (natural tab order)\n - Visual order: content → button (via CSS order property)\n */}\n <div className={styles.contentContainer}>\n {renderShowMore && renderShowMoreButton()}\n {renderContent()}\n </div>\n </div>\n );\n};\n"],"names":["TYPE_CLASS_MAP","error","wrapperTypeClass","styles.wrapperError","iconColorClass","styles.alertIconError","success","styles.wrapperSuccess","styles.alertIconSuccess","warning","styles.wrapperWarning","styles.alertIconWarning","info","styles.wrapperInfo","styles.alertIconInfo","description","styles.wrapperDescription","styles.alertIconDescription","_a","type","className","children","actionLink","actionLinkIcon","sizeType","showIcon","customIcon","maxCollapsedHeight","role","otherProps","__rest","showHiddenContent","setShowHiddenContent","useState","renderShowMore","setRenderShowMore","height","contentHeight","ref","contentRef","useResizeDetector","contentId","useId","containerRef","useRef","isCollapsed","isContentOutsideCollapsibleZone","setCollapsedState","useCallback","useEffect","useApplyStyle","styles.tokens","_jsxs","clsx","styles.wrapper","getAlertLabel","_jsx","styles.alertIconWrapper","styles.alertIconWrapperSmall","styles.alertIconWrapperDefault","TickCircleFillIcon","ExclamationTriangleFillIcon","ExclamationCircleFillIcon","ICircleFillIcon","renderIcon","styles.contentContainer","styles.showMoreButton","styles.showMoreButtonSmall","styles.showMoreButtonDefault","onClick","ChevronDownIcon","styles.chevronIcon","styles.chevronIconExpanded","styles.chevronIconCollapsed","id","styles.textWrapperContainer","styles.textWrapperContainerShowMoreWithLink","styles.textWrapperContainerCollapsed","inert","inertValue","Markup","styles.textWrapper","baseTextSize","Typography","LinkSM","Object","assign","styles.actionLinkText","styles.actionLinkTextSmall","styles.actionLinkTextDefault","underlineStyle","ArrowRightIcon"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"countdown-timer.js","sources":["../../../src/countdown-timer/countdown-timer.tsx"],"sourcesContent":["import { ClockIcon } from \"@lifesg/react-icons\";\nimport clsx from \"clsx\";\nimport throttle from \"lodash/throttle\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useInView } from \"react-intersection-observer\";\n\nimport { inertValue, VisuallyHidden } from \"../shared/accessibility\";\nimport {\n formatUnitValue,\n useApplyStyle,\n useMaxWidthMediaQuery,\n} from \"../theme\";\nimport { useIsMounted } from \"../util\";\nimport { TimeHelper } from \"../util/time-helper\";\nimport * as styles from \"./countdown-timer.styles\";\nimport type { CountdownTimerProps } from \"./types\";\nimport { useTimer } from \"./use-timer\";\n\n// @catalog\n/**\n * Displays a live countdown of time remaining until a deadline.\n *\n * Use when a time-limited interaction — such as a session timeout or a timed\n * form — needs a persistent visual reminder.\n */\nexport const CountdownTimer = ({\n className,\n align = \"right\",\n timer,\n timestamp,\n notifyTimer,\n offset,\n mobileOffset,\n show,\n fixed = true,\n reminderInterval = 120,\n \"data-testid\": testId,\n onFinish,\n onNotify,\n onTick,\n ...otherProps\n}: CountdownTimerProps) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const fixedCountdownRef = useRef<HTMLDivElement>(null);\n\n const isNotified = useRef<boolean>(false);\n const [offsetY, setOffsetY] = useState<number>(0);\n const [clientRectRight, setClientRectRight] = useState<number>(0);\n const [clientRectX, setClientRectX] = useState<number>(0);\n const [isPlaying, setIsPlaying] = useState(false);\n const [announcement, setAnnouncement] = useState<string>(\"\");\n\n const isMounted = useIsMounted();\n const [remainingSeconds, initialSeconds] = useTimer(\n timer,\n timestamp,\n isPlaying\n );\n const { ref: stickyRef, inView } = useInView({\n threshold: 1,\n rootMargin: `${offsetY * -1}px 0px 0px 0px`,\n initialInView: true,\n });\n const isVisible = !fixed || inView;\n const warn =\n typeof notifyTimer === \"number\" && remainingSeconds <= notifyTimer;\n\n const isMobile = useMaxWidthMediaQuery(\"sm\");\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useApplyStyle(fixedCountdownRef, {\n [styles.tokens.fixedCountdown.top]: formatUnitValue(offsetY, \"px\"),\n [styles.tokens.fixedCountdown.left]: formatUnitValue(\n getOffsetLeft(),\n \"px\"\n ),\n [styles.tokens.fixedCountdown.right]: formatUnitValue(\n getOffsetRight(),\n \"px\"\n ),\n });\n\n useEffect(() => {\n setIsPlaying(show);\n }, [show]);\n\n useEffect(() => {\n if (remainingSeconds === 0) {\n performOnFinishHandler();\n } else if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ) {\n performOnTickHandler();\n performOnNotifyHandler();\n }\n }, [remainingSeconds]);\n\n useEffect(() => {\n const y = getOffsetY();\n setOffsetY(y);\n }, [isMobile, offset?.top, mobileOffset?.top]);\n\n useEffect(() => {\n if (wrapperRef.current) {\n const handleResize = throttle(handleWindowResize, 300);\n handleWindowResize();\n\n window.addEventListener(\"resize\", handleResize);\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n };\n }\n }, [wrapperRef.current]);\n\n // Announcements\n useEffect(() => {\n const timeElapsed = initialSeconds - remainingSeconds;\n if (timeElapsed === 0) return;\n // Polite notification\n if (timeElapsed % reminderInterval === 0) {\n if (\n typeof notifyTimer !== \"number\" ||\n remainingSeconds > notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }\n\n // Assertive notification\n if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds === notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }, [remainingSeconds, initialSeconds, reminderInterval, notifyTimer]);\n\n useEffect(() => {\n // reset\n isNotified.current = false;\n }, [initialSeconds, show]);\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handleWindowResize = () => {\n if (!wrapperRef.current) return;\n\n const clientRect = wrapperRef.current.getBoundingClientRect();\n\n setClientRectX(clientRect.x);\n setClientRectRight(clientRect.right);\n };\n\n const handleOnFocus = () => {\n const focusMessage = getAccessibleTimeText(remainingSeconds);\n setAnnouncement(focusMessage);\n };\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n\n const performOnTickHandler = () => {\n if (onTick) {\n onTick(remainingSeconds);\n }\n };\n\n const performOnNotifyHandler = () => {\n if (onNotify && !isNotified.current) {\n isNotified.current = true;\n onNotify();\n }\n };\n\n const performOnFinishHandler = () => {\n setIsPlaying(false);\n\n if (onFinish) onFinish();\n };\n\n function getOffsetY() {\n const desktopTop = offset?.top ?? 168;\n const mobileTop = mobileOffset?.top ?? 80;\n const offsetY = isMobile ? mobileTop : desktopTop;\n\n return offsetY;\n }\n\n function getOffsetLeft() {\n if (!isMounted || align !== \"left\") return undefined;\n return offset?.left ?? clientRectX;\n }\n\n function getOffsetRight() {\n if (!isMounted || align !== \"right\") return undefined;\n return (\n offset?.right ??\n Math.floor(document.body.clientWidth - clientRectRight)\n );\n }\n\n const getAccessibleTimeText = (seconds: number): string => {\n const { minutes, seconds: secs } = TimeHelper.toMinutesSeconds(seconds);\n\n const minuteText = minutes === 1 ? \"minute\" : \"minutes\";\n const secondText = secs === 1 ? \"second\" : \"seconds\";\n\n if (minutes > 0 && secs > 0) {\n return `Time left: ${minutes} ${minuteText} and ${secs} ${secondText}`;\n } else if (minutes > 0) {\n return `Time left: ${minutes} ${minuteText}`;\n } else {\n return `Time left: ${secs} ${secondText}`;\n }\n };\n // =============================================================================\n // RENDER FUNCTION\n // =============================================================================\n\n const renderPeriodicAndWarningAnnouncement = () => (\n <VisuallyHidden\n aria-live={\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ? \"assertive\"\n : \"polite\"\n }\n aria-atomic=\"true\"\n >\n {announcement}\n </VisuallyHidden>\n );\n\n const renderTimer = () => {\n const { minutes, seconds } =\n TimeHelper.toMinutesSeconds(remainingSeconds);\n const m = minutes !== 1 ? \"mins\" : \"min\";\n const s = seconds !== 1 ? \"secs\" : \"sec\";\n return (\n <>\n <ClockIcon className={styles.timerIcon} data-warn={!!warn} />\n <div className={styles.timeLeft}>Time left:</div>\n <div\n className={styles.timer}\n aria-label={getAccessibleTimeText(remainingSeconds)}\n >\n {minutes} {m} {String(seconds).padStart(2, \"0\")} {s}\n </div>\n {renderPeriodicAndWarningAnnouncement()}\n </>\n );\n };\n\n const renderInlineCountdown = () => {\n return (\n <div\n data-testid={testId}\n data-id=\"countdown-wrapper\"\n data-warn={!!warn}\n ref={wrapperRef}\n inert={inertValue(!isVisible)}\n className={clsx(\n styles.countdown,\n styles.countdownInline,\n !isVisible && styles.countdownHidden\n )}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n const renderFixedCountdown = () => {\n return (\n <div\n ref={fixedCountdownRef}\n data-testid={testId}\n data-id=\"fixed-countdown-wrapper\"\n data-warn={!!warn}\n className={clsx(styles.countdown, styles.countdownFixed)}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n if (!isPlaying && remainingSeconds !== 0) return <></>;\n\n return (\n <div className={clsx(styles.wrapper, className)} {...otherProps}>\n <div ref={stickyRef}></div>\n {renderInlineCountdown()}\n {wrapperRef.current && !isVisible && renderFixedCountdown()}\n </div>\n );\n};\n"],"names":["_a","className","align","timer","timestamp","notifyTimer","offset","mobileOffset","show","fixed","reminderInterval","testId","onFinish","onNotify","onTick","otherProps","__rest","wrapperRef","useRef","fixedCountdownRef","isNotified","offsetY","setOffsetY","useState","clientRectRight","setClientRectRight","clientRectX","setClientRectX","isPlaying","setIsPlaying","announcement","setAnnouncement","isMounted","useIsMounted","remainingSeconds","initialSeconds","useTimer","ref","stickyRef","inView","useInView","threshold","rootMargin","initialInView","isVisible","warn","isMobile","useMaxWidthMediaQuery","useApplyStyle","styles.tokens","fixedCountdown","top","formatUnitValue","left","getOffsetLeft","right","Math","floor","document","body","clientWidth","getOffsetRight","useEffect","performOnFinishHandler","performOnTickHandler","performOnNotifyHandler","y","desktopTop","mobileTop","_b","getOffsetY","current","handleResize","throttle","handleWindowResize","window","addEventListener","removeEventListener","timeElapsed","getAccessibleTimeText","clientRect","getBoundingClientRect","x","handleOnFocus","focusMessage","seconds","minutes","secs","TimeHelper","toMinutesSeconds","minuteText","secondText","renderTimer","m","s","_jsxs","_Fragment","children","_jsx","ClockIcon","styles.timerIcon","styles.timeLeft","styles.timer","String","padStart","VisuallyHidden","Object","assign","clsx","styles.wrapper","inert","inertValue","styles.countdown","styles.countdownInline","styles.countdownHidden","tabIndex","role","onFocus","styles.countdownFixed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"countdown-timer.js","sources":["../../../src/countdown-timer/countdown-timer.tsx"],"sourcesContent":["import { ClockIcon } from \"@lifesg/react-icons\";\nimport clsx from \"clsx\";\nimport throttle from \"lodash/throttle\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useInView } from \"react-intersection-observer\";\n\nimport { inertValue, VisuallyHidden } from \"../shared/accessibility\";\nimport {\n formatUnitValue,\n useApplyStyle,\n useMaxWidthMediaQuery,\n} from \"../theme\";\nimport { useIsMounted } from \"../util\";\nimport { TimeHelper } from \"../util/time-helper\";\nimport * as styles from \"./countdown-timer.styles\";\nimport type { CountdownTimerProps } from \"./types\";\nimport { useTimer } from \"./use-timer\";\n\n// @catalog\n/**\n * Displays a live countdown of time remaining until a deadline.\n *\n * Use when a time-limited interaction — such as a session timeout or a timed\n * form — needs a persistent visual reminder.\n */\nexport const CountdownTimer = ({\n className,\n align = \"right\",\n timer,\n timestamp,\n notifyTimer,\n offset,\n mobileOffset,\n show,\n fixed = true,\n reminderInterval = 120,\n \"data-testid\": testId,\n onFinish,\n onNotify,\n onTick,\n ...otherProps\n}: CountdownTimerProps) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const fixedCountdownRef = useRef<HTMLDivElement>(null);\n\n const isNotified = useRef<boolean>(false);\n const [offsetY, setOffsetY] = useState<number>(0);\n const [clientRectRight, setClientRectRight] = useState<number>(0);\n const [clientRectX, setClientRectX] = useState<number>(0);\n const [isPlaying, setIsPlaying] = useState(false);\n const [announcement, setAnnouncement] = useState<string>(\"\");\n\n const isMounted = useIsMounted();\n const [remainingSeconds, initialSeconds] = useTimer(\n timer,\n timestamp,\n isPlaying\n );\n const { ref: stickyRef, inView } = useInView({\n threshold: 1,\n rootMargin: `${offsetY * -1}px 0px 0px 0px`,\n initialInView: true,\n });\n const isVisible = !fixed || inView;\n const warn =\n typeof notifyTimer === \"number\" && remainingSeconds <= notifyTimer;\n\n const isMobile = useMaxWidthMediaQuery(\"sm\");\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useApplyStyle(fixedCountdownRef, {\n [styles.tokens.fixedCountdown.top]: formatUnitValue(offsetY, \"px\"),\n [styles.tokens.fixedCountdown.left]: formatUnitValue(\n getOffsetLeft(),\n \"px\"\n ),\n [styles.tokens.fixedCountdown.right]: formatUnitValue(\n getOffsetRight(),\n \"px\"\n ),\n });\n\n useEffect(() => {\n setIsPlaying(show);\n }, [show]);\n\n useEffect(() => {\n if (remainingSeconds === 0) {\n performOnFinishHandler();\n } else if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ) {\n performOnTickHandler();\n performOnNotifyHandler();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [remainingSeconds]);\n\n useEffect(() => {\n const y = getOffsetY();\n setOffsetY(y);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isMobile, offset?.top, mobileOffset?.top]);\n\n useEffect(() => {\n if (wrapperRef.current) {\n const handleResize = throttle(handleWindowResize, 300);\n handleWindowResize();\n\n window.addEventListener(\"resize\", handleResize);\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n };\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [wrapperRef.current]);\n\n // Announcements\n useEffect(() => {\n const timeElapsed = initialSeconds - remainingSeconds;\n if (timeElapsed === 0) return;\n // Polite notification\n if (timeElapsed % reminderInterval === 0) {\n if (\n typeof notifyTimer !== \"number\" ||\n remainingSeconds > notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }\n\n // Assertive notification\n if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds === notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }, [remainingSeconds, initialSeconds, reminderInterval, notifyTimer]);\n\n useEffect(() => {\n // reset\n isNotified.current = false;\n }, [initialSeconds, show]);\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handleWindowResize = () => {\n if (!wrapperRef.current) return;\n\n const clientRect = wrapperRef.current.getBoundingClientRect();\n\n setClientRectX(clientRect.x);\n setClientRectRight(clientRect.right);\n };\n\n const handleOnFocus = () => {\n const focusMessage = getAccessibleTimeText(remainingSeconds);\n setAnnouncement(focusMessage);\n };\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n\n const performOnTickHandler = () => {\n if (onTick) {\n onTick(remainingSeconds);\n }\n };\n\n const performOnNotifyHandler = () => {\n if (onNotify && !isNotified.current) {\n isNotified.current = true;\n onNotify();\n }\n };\n\n const performOnFinishHandler = () => {\n setIsPlaying(false);\n\n if (onFinish) onFinish();\n };\n\n function getOffsetY() {\n const desktopTop = offset?.top ?? 168;\n const mobileTop = mobileOffset?.top ?? 80;\n const offsetY = isMobile ? mobileTop : desktopTop;\n\n return offsetY;\n }\n\n function getOffsetLeft() {\n if (!isMounted || align !== \"left\") return undefined;\n return offset?.left ?? clientRectX;\n }\n\n function getOffsetRight() {\n if (!isMounted || align !== \"right\") return undefined;\n return (\n offset?.right ??\n Math.floor(document.body.clientWidth - clientRectRight)\n );\n }\n\n const getAccessibleTimeText = (seconds: number): string => {\n const { minutes, seconds: secs } = TimeHelper.toMinutesSeconds(seconds);\n\n const minuteText = minutes === 1 ? \"minute\" : \"minutes\";\n const secondText = secs === 1 ? \"second\" : \"seconds\";\n\n if (minutes > 0 && secs > 0) {\n return `Time left: ${minutes} ${minuteText} and ${secs} ${secondText}`;\n } else if (minutes > 0) {\n return `Time left: ${minutes} ${minuteText}`;\n } else {\n return `Time left: ${secs} ${secondText}`;\n }\n };\n // =============================================================================\n // RENDER FUNCTION\n // =============================================================================\n\n const renderPeriodicAndWarningAnnouncement = () => (\n <VisuallyHidden\n aria-live={\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ? \"assertive\"\n : \"polite\"\n }\n aria-atomic=\"true\"\n >\n {announcement}\n </VisuallyHidden>\n );\n\n const renderTimer = () => {\n const { minutes, seconds } =\n TimeHelper.toMinutesSeconds(remainingSeconds);\n const m = minutes !== 1 ? \"mins\" : \"min\";\n const s = seconds !== 1 ? \"secs\" : \"sec\";\n return (\n <>\n <ClockIcon className={styles.timerIcon} data-warn={!!warn} />\n <div className={styles.timeLeft}>Time left:</div>\n <div\n className={styles.timer}\n aria-label={getAccessibleTimeText(remainingSeconds)}\n >\n {minutes} {m} {String(seconds).padStart(2, \"0\")} {s}\n </div>\n {renderPeriodicAndWarningAnnouncement()}\n </>\n );\n };\n\n const renderInlineCountdown = () => {\n return (\n <div\n data-testid={testId}\n data-id=\"countdown-wrapper\"\n data-warn={!!warn}\n ref={wrapperRef}\n inert={inertValue(!isVisible)}\n className={clsx(\n styles.countdown,\n styles.countdownInline,\n !isVisible && styles.countdownHidden\n )}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n const renderFixedCountdown = () => {\n return (\n <div\n ref={fixedCountdownRef}\n data-testid={testId}\n data-id=\"fixed-countdown-wrapper\"\n data-warn={!!warn}\n className={clsx(styles.countdown, styles.countdownFixed)}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n if (!isPlaying && remainingSeconds !== 0) return <></>;\n\n return (\n <div className={clsx(styles.wrapper, className)} {...otherProps}>\n <div ref={stickyRef}></div>\n {renderInlineCountdown()}\n {wrapperRef.current && !isVisible && renderFixedCountdown()}\n </div>\n );\n};\n"],"names":["_a","className","align","timer","timestamp","notifyTimer","offset","mobileOffset","show","fixed","reminderInterval","testId","onFinish","onNotify","onTick","otherProps","__rest","wrapperRef","useRef","fixedCountdownRef","isNotified","offsetY","setOffsetY","useState","clientRectRight","setClientRectRight","clientRectX","setClientRectX","isPlaying","setIsPlaying","announcement","setAnnouncement","isMounted","useIsMounted","remainingSeconds","initialSeconds","useTimer","ref","stickyRef","inView","useInView","threshold","rootMargin","initialInView","isVisible","warn","isMobile","useMaxWidthMediaQuery","useApplyStyle","styles.tokens","fixedCountdown","top","formatUnitValue","left","getOffsetLeft","right","Math","floor","document","body","clientWidth","getOffsetRight","useEffect","performOnFinishHandler","performOnTickHandler","performOnNotifyHandler","y","desktopTop","mobileTop","_b","getOffsetY","current","handleResize","throttle","handleWindowResize","window","addEventListener","removeEventListener","timeElapsed","getAccessibleTimeText","clientRect","getBoundingClientRect","x","handleOnFocus","focusMessage","seconds","minutes","secs","TimeHelper","toMinutesSeconds","minuteText","secondText","renderTimer","m","s","_jsxs","_Fragment","children","_jsx","ClockIcon","styles.timerIcon","styles.timeLeft","styles.timer","String","padStart","VisuallyHidden","Object","assign","clsx","styles.wrapper","inert","inertValue","styles.countdown","styles.countdownInline","styles.countdownHidden","tabIndex","role","onFocus","styles.countdownFixed"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var e=require("../_virtual/_tslib.js"),t=require("react/jsx-runtime"),r=require("@lifesg/react-icons"),i=require("clsx"),l=require("react"),n=require("../external/react-resize-detector/build/index.esm.js");require("../animations/loading-dots/loading-dots.js");var a=require("../animations/loading-dots-spinner/loading-dots-spinner.js");require("../animations/loading-spinner/loading-spinner.js"),require("../theme/theme-provider/context.js");var s=require("../checkbox/checkbox.js"),o=require("../error-display/error-display.js"),d=require("../shared/accessibility/index.js"),c=require("../shared/input-wrapper/input-wrapper.js");require("../theme/theme-provider/index.js"),require("../theme/tokens/border.js"),require("../theme/tokens/colour.js"),require("../theme/tokens/font.js"),require("../theme/tokens/media-query.js");var u=require("../theme/utils/use-apply-styles.js");require("../theme/utils/use-media-query.js");var h=require("../typography/index.js");require("../util/calendar-helper.js"),require("../external/dayjs/dayjs.min.js");var p=require("../util/simple-id-generator.js");require("../util/string-helper.js"),require("@react-aria/live-announcer");var b=require("../util/use-event.js"),g=require("../util/use-event-listener.js"),m=require("../util/use-throttle.js"),y=require("./data-table.styles.js");function j(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var x=j(i);exports.DataTable=i=>{var{id:j,headers:f,rows:w,className:v,sortIndicators:S,alternatingRows:C,loadState:k="success",enableMultiSelect:q,selectedIds:N,disabledIds:B,enableActionBar:$,enableActionBarWithoutSelection:H,enableSelectAll:R,enableStickyHeader:W,emptyView:A,actionBarContent:I,renderCustomEmptyView:T,onHeaderClick:D,onSelect:E,onSelectAll:L,onClearSelectionClick:M}=i,V=e.__rest(i,["id","headers","rows","className","sortIndicators","alternatingRows","loadState","enableMultiSelect","selectedIds","disabledIds","enableActionBar","enableActionBarWithoutSelection","enableSelectAll","enableStickyHeader","emptyView","actionBarContent","renderCustomEmptyView","onHeaderClick","onSelect","onSelectAll","onClearSelectionClick"]);const F=l.useRef(null),K=l.useRef(null),_=l.useRef(null),z=l.useRef(null),G=p.useId(),O=f.filter((e=>"string"!=typeof e&&!!e.keyColumn)),[P,U]=l.useState("inline"),[J,Q]=l.useState(!1),[X,Y]=l.useState(!1);u.useApplyStyle(_,{[y.tokens.actionBarWrapperLeft]:X&&F.current?`${F.current.getBoundingClientRect().left}px`:void 0,[y.tokens.actionBarWrapperWidth]:F.current?`${F.current.clientWidth}px`:void 0});const Z=b.useEvent((()=>{he()}));l.useEffect((()=>{Z()}),[Z]);const ee=m.useThrottle((()=>he()),100);n.useResizeDetector({targetRef:z,onResize:()=>ee()}),g.useEventListener("scroll",ee),g.useEventListener("scroll",ee,z.current);const te=()=>!!(w&&w.length&&N)&&N.length===w.length,re=e=>!!(null==N?void 0:N.includes(e)),ie=e=>!!C&&e%2==1,le=e=>!!(null==B?void 0:B.includes(e)),ne=e=>{if(V["data-testid"])return`${V["data-testid"]}-${e}`},ae=()=>f.length+(q?1:0),se=e=>null==S?void 0:S[e],oe=e=>{const t=se(e);if(t)return"asc"===t?"ascending":"descending"},de=e=>` by ${"asc"===se(e)?"descending":"ascending"} order`,ce=(e,t)=>`${G}-row-${e}-${t}-key-column`,ue=e=>{const t=O.map((t=>ce(e,t.fieldKey)));return d.concatIds(...t)},he=()=>{if(!z.current||!K.current||!_.current)return;const e=z.current.getBoundingClientRect(),t=K.current.getBoundingClientRect();let r="inline";const i=z.current.scrollHeight>z.current.clientHeight;if(e.top>window.innerHeight||e.bottom<0?r="inline":i?e.top>0&&e.top<window.innerHeight&&e.bottom>0&&e.bottom<window.innerHeight||e.top<0&&e.bottom>0&&e.bottom<window.innerHeight?r="sticky":e.bottom>window.innerHeight&&Math.max(e.top,t.bottom)+t.height<window.innerHeight&&(r="fixed"):e.bottom>window.innerHeight&&t.bottom+t.height<window.innerHeight&&(r="fixed"),Q(i),U(r),"fixed"===r)Y(!0);else if("sticky"===r){const e=z.current.scrollTop+z.current.clientHeight<z.current.scrollHeight;Y(e)}else Y(!1)},pe=e=>{const{fieldKey:r,label:i,clickable:l=!1,style:n}="string"==typeof e?{fieldKey:e,label:e,style:void 0}:e,a=!!se(r),s=(e=>`${G}-header-${e}`)(r);return t.jsxs("th",{"data-testid":ne(`header-${r}`),className:x.default(y.headerCell,l&&y.headerCellClickable),scope:"col","aria-sort":oe(r),style:n,onClick:()=>(e=>{null==D||D(e)})(r),children:[t.jsxs("div",{className:y.headerCellContent,id:s,children:["string"==typeof i?t.jsx(h.Typography.BodyBL,{weight:"bold",children:i}):i,be(r)]}),(l||a)&&t.jsx(d.VisuallyHidden,{children:t.jsxs("button",{onClick:e=>((e,t)=>{e.stopPropagation(),null==D||D(t)})(e,r),children:[a&&"Sort ",t.jsx("span",{"aria-labelledby":s}),a&&de(r)]})})]},r)},be=e=>{const i=null==S?void 0:S[e];return i?"asc"===i?t.jsx(r.ArrowUpIcon,{"aria-hidden":!0,"data-testid":ne(`header-${e}-arrowup`)}):t.jsx(r.ArrowDownIcon,{"aria-hidden":!0,"data-testid":ne(`header-${e}-arrowdown`)}):t.jsx(t.Fragment,{})},ge=()=>t.jsx("th",{"data-testid":ne("header-selection"),className:x.default(y.headerCell,y.headerCellCheckbox),scope:"col",children:t.jsxs("div",{className:y.selectionCheckboxWrapper,children:[t.jsx(d.VisuallyHidden,{children:"Row selection"}),R&&t.jsx(s.Checkbox,{checked:te(),indeterminate:!!N&&0!==N.length&&!te(),disabled:!w||!w.length
|
|
1
|
+
"use strict";var e=require("../_virtual/_tslib.js"),t=require("react/jsx-runtime"),r=require("@lifesg/react-icons"),i=require("clsx"),l=require("react"),n=require("../external/react-resize-detector/build/index.esm.js");require("../animations/loading-dots/loading-dots.js");var a=require("../animations/loading-dots-spinner/loading-dots-spinner.js");require("../animations/loading-spinner/loading-spinner.js"),require("../theme/theme-provider/context.js");var s=require("../checkbox/checkbox.js"),o=require("../error-display/error-display.js"),d=require("../shared/accessibility/index.js"),c=require("../shared/input-wrapper/input-wrapper.js");require("../theme/theme-provider/index.js"),require("../theme/tokens/border.js"),require("../theme/tokens/colour.js"),require("../theme/tokens/font.js"),require("../theme/tokens/media-query.js");var u=require("../theme/utils/use-apply-styles.js");require("../theme/utils/use-media-query.js");var h=require("../typography/index.js");require("../util/calendar-helper.js"),require("../external/dayjs/dayjs.min.js");var p=require("../util/simple-id-generator.js");require("../util/string-helper.js"),require("@react-aria/live-announcer");var b=require("../util/use-event.js"),g=require("../util/use-event-listener.js"),m=require("../util/use-throttle.js"),y=require("./data-table.styles.js");function j(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var x=j(i);exports.DataTable=i=>{var{id:j,headers:f,rows:w,className:v,sortIndicators:S,alternatingRows:C,loadState:k="success",enableMultiSelect:q,selectedIds:N,disabledIds:B,enableActionBar:$,enableActionBarWithoutSelection:H,enableSelectAll:R,enableStickyHeader:W,emptyView:A,actionBarContent:I,renderCustomEmptyView:T,onHeaderClick:D,onSelect:E,onSelectAll:L,onClearSelectionClick:M}=i,V=e.__rest(i,["id","headers","rows","className","sortIndicators","alternatingRows","loadState","enableMultiSelect","selectedIds","disabledIds","enableActionBar","enableActionBarWithoutSelection","enableSelectAll","enableStickyHeader","emptyView","actionBarContent","renderCustomEmptyView","onHeaderClick","onSelect","onSelectAll","onClearSelectionClick"]);const F=l.useRef(null),K=l.useRef(null),_=l.useRef(null),z=l.useRef(null),G=p.useId(),O=f.filter((e=>"string"!=typeof e&&!!e.keyColumn)),[P,U]=l.useState("inline"),[J,Q]=l.useState(!1),[X,Y]=l.useState(!1);u.useApplyStyle(_,{[y.tokens.actionBarWrapperLeft]:X&&F.current?`${F.current.getBoundingClientRect().left}px`:void 0,[y.tokens.actionBarWrapperWidth]:F.current?`${F.current.clientWidth}px`:void 0});const Z=b.useEvent((()=>{he()}));l.useEffect((()=>{Z()}),[Z]);const ee=m.useThrottle((()=>he()),100);n.useResizeDetector({targetRef:z,onResize:()=>ee()}),g.useEventListener("scroll",ee),g.useEventListener("scroll",ee,z.current);const te=()=>!!(w&&w.length&&N)&&N.length===w.length,re=e=>!!(null==N?void 0:N.includes(e)),ie=e=>!!C&&e%2==1,le=e=>!!(null==B?void 0:B.includes(e)),ne=e=>{if(V["data-testid"])return`${V["data-testid"]}-${e}`},ae=()=>f.length+(q?1:0),se=e=>null==S?void 0:S[e],oe=e=>{const t=se(e);if(t)return"asc"===t?"ascending":"descending"},de=e=>` by ${"asc"===se(e)?"descending":"ascending"} order`,ce=(e,t)=>`${G}-row-${e}-${t}-key-column`,ue=e=>{const t=O.map((t=>ce(e,t.fieldKey)));return d.concatIds(...t)},he=()=>{if(!z.current||!K.current||!_.current)return;const e=z.current.getBoundingClientRect(),t=K.current.getBoundingClientRect();let r="inline";const i=z.current.scrollHeight>z.current.clientHeight;if(e.top>window.innerHeight||e.bottom<0?r="inline":i?e.top>0&&e.top<window.innerHeight&&e.bottom>0&&e.bottom<window.innerHeight||e.top<0&&e.bottom>0&&e.bottom<window.innerHeight?r="sticky":e.bottom>window.innerHeight&&Math.max(e.top,t.bottom)+t.height<window.innerHeight&&(r="fixed"):e.bottom>window.innerHeight&&t.bottom+t.height<window.innerHeight&&(r="fixed"),Q(i),U(r),"fixed"===r)Y(!0);else if("sticky"===r){const e=z.current.scrollTop+z.current.clientHeight<z.current.scrollHeight;Y(e)}else Y(!1)},pe=e=>{const{fieldKey:r,label:i,clickable:l=!1,style:n}="string"==typeof e?{fieldKey:e,label:e,style:void 0}:e,a=!!se(r),s=(e=>`${G}-header-${e}`)(r);return t.jsxs("th",{"data-testid":ne(`header-${r}`),className:x.default(y.headerCell,l&&y.headerCellClickable),scope:"col","aria-sort":oe(r),style:n,onClick:()=>(e=>{null==D||D(e)})(r),children:[t.jsxs("div",{className:y.headerCellContent,id:s,children:["string"==typeof i?t.jsx(h.Typography.BodyBL,{weight:"bold",children:i}):i,be(r)]}),(l||a)&&t.jsx(d.VisuallyHidden,{children:t.jsxs("button",{onClick:e=>((e,t)=>{e.stopPropagation(),null==D||D(t)})(e,r),children:[a&&"Sort ",t.jsx("span",{"aria-labelledby":s}),a&&de(r)]})})]},r)},be=e=>{const i=null==S?void 0:S[e];return i?"asc"===i?t.jsx(r.ArrowUpIcon,{"aria-hidden":!0,"data-testid":ne(`header-${e}-arrowup`)}):t.jsx(r.ArrowDownIcon,{"aria-hidden":!0,"data-testid":ne(`header-${e}-arrowdown`)}):t.jsx(t.Fragment,{})},ge=()=>t.jsx("th",{"data-testid":ne("header-selection"),className:x.default(y.headerCell,y.headerCellCheckbox),scope:"col",children:t.jsxs("div",{className:y.selectionCheckboxWrapper,children:[t.jsx(d.VisuallyHidden,{children:"Row selection"}),R&&t.jsx(s.Checkbox,{checked:te(),indeterminate:!!N&&0!==N.length&&!te(),disabled:!w||!w.length,"aria-label":"Select all rows",onClick:()=>{L&&L(te())}})]})}),me=(e,r)=>{const i="string"==typeof e?void 0:e.style,l="string"==typeof e?e:e.fieldKey,n=r.id.toString(),a=r[l],s=`${n}-${l}`;let o;return o="string"==typeof a||"number"==typeof a?t.jsx(h.Typography.BodyBL,{className:y.bodyCellText,children:a}):"function"==typeof a?a(r,{isSelected:re(n)}):a,t.jsx("td",{"data-testid":ne(`row-${s}`),id:ce(n,l),className:y.bodyCell,style:i,children:o},s)},ye=e=>{const r=e.id.toString();return t.jsx("td",{"data-testid":ne(`row-${r}-selection`),className:x.default(y.bodyCell,y.bodyCellCheckbox),children:t.jsx("div",{className:y.selectionCheckboxWrapper,children:t.jsx(s.Checkbox,{checked:re(r),"aria-labelledby":ue(r),onClick:()=>{E&&E(r,!re(r))},disabled:le(r),focusableWhenDisabled:le(r)})})})},je=()=>{let e;return e=(null==A?void 0:A.title)?"string"==typeof A.title?t.jsx(h.Typography.HeadingSM,{className:y.emptyStateTitle,weight:"bold",children:A.title}):A.title:t.jsx(h.Typography.HeadingSM,{className:y.emptyStateTitle,weight:"bold",children:"No <items> found"}),t.jsx(o.ErrorDisplay,Object.assign({type:"no-item-found"},A,{title:e,description:(null==A?void 0:A.description)?A.description:"No matching rows"}))};return t.jsxs("div",{id:j||"table-wrapper","data-testid":V["data-testid"]||"table",className:x.default(y.tableWrapper,J&&y.tableWrapperScrollable,v),ref:z,tabIndex:0,children:[t.jsx("div",{className:y.tableContainer,children:t.jsxs("table",{ref:F,className:y.table,children:[t.jsx("thead",{ref:K,className:W?y.stickyHeader:void 0,children:t.jsxs("tr",{className:y.headerRow,children:[q&&ge(),f.map(pe)]})}),t.jsx("tbody",{children:"success"===k?!w||w.length<1?t.jsx("tr",{className:y.bodyRow,children:t.jsx("td",{className:y.emptyStateCell,colSpan:ae(),children:T?T():je()})}):t.jsx(t.Fragment,{children:null==w?void 0:w.map(((e,r)=>t.jsxs("tr",{"data-testid":ne(`row-${e.id.toString()}`),className:x.default(y.bodyRow,X&&y.bodyRowWithBottomBorder,ie(r)&&y.bodyRowAlternating,q&&y.bodyRowSelectable,re(e.id.toString())&&y.bodyRowSelected),children:[q&&ye(e),f.map((t=>me(t,e)))]},e.id.toString())))}):t.jsx("tr",{className:y.bodyRow,children:t.jsx("td",{colSpan:ae(),children:t.jsx("div",{className:y.loadingStateWrapper,role:"status","aria-live":"polite","aria-label":"Loading table",children:"loading"===k&&t.jsx(a.LoadingDotsSpinner,{})})})})})]})}),$&&(N&&N.length>0||H)&&(()=>{var e;const r=null!==(e=null==N?void 0:N.length)&&void 0!==e?e:0,i="item"+(1===r?"":"s");return t.jsxs(t.Fragment,{children:[t.jsx("div",{ref:_,className:y.actionBarWrapper,"data-strategy":P,children:t.jsxs("div",{"data-testid":"data-table-action-bar",className:x.default(y.actionBar,X&&y.actionBarFloating),children:[t.jsx(h.Typography.BodyMD,{weight:"semibold",children:`${r} ${i} selected`}),r>0?t.jsx(c.BasicButton,{className:y.actionBarButton,type:"button","aria-label":`Clear selection of ${r} ${i}`,onClick:M,children:"Clear selection"}):t.jsx("div",{className:y.actionBarGap}),I]})}),"sticky"!==P&&t.jsx("div",{className:y.actionBarSpacer})]})})()]})};
|
|
2
2
|
//# sourceMappingURL=data-table.js.map
|