From b548ada9d956448f8966ca60f12e890fa09700d7 Mon Sep 17 00:00:00 2001 From: Phillip Lovelace Date: Fri, 1 Aug 2025 12:54:29 -0700 Subject: [PATCH 01/10] feat: add new builder base for site builder --- docs/assets/builder-base/Accordion.tsx | 38 ++ docs/assets/builder-base/Alert.tsx | 48 +++ docs/assets/builder-base/Avatar.tsx | 47 +++ docs/assets/builder-base/Box.tsx | 155 ++++++++ docs/assets/builder-base/Button.tsx | 80 ++++ docs/assets/builder-base/Checkbox.tsx | 80 ++++ docs/assets/builder-base/Chip.tsx | 70 ++++ docs/assets/builder-base/Combobox.tsx | 80 ++++ docs/assets/builder-base/Copytext.tsx | 39 ++ docs/assets/builder-base/Divider.tsx | 22 ++ docs/assets/builder-base/DropdownMenu.tsx | 84 +++++ docs/assets/builder-base/Icon.tsx | 411 +++++++++++++++++++++ docs/assets/builder-base/Image.tsx | 34 ++ docs/assets/builder-base/Input.tsx | 147 ++++++++ docs/assets/builder-base/Link.tsx | 38 ++ docs/assets/builder-base/Loader.tsx | 32 ++ docs/assets/builder-base/Modal.tsx | 75 ++++ docs/assets/builder-base/ModalContent.tsx | 23 ++ docs/assets/builder-base/ModalFooter.tsx | 18 + docs/assets/builder-base/ModalHeader.tsx | 18 + docs/assets/builder-base/Popover.tsx | 44 +++ docs/assets/builder-base/Progress.tsx | 36 ++ docs/assets/builder-base/Radio.tsx | 59 +++ docs/assets/builder-base/Select.tsx | 71 ++++ docs/assets/builder-base/Switch.tsx | 59 +++ docs/assets/builder-base/Table.tsx | 45 +++ docs/assets/builder-base/TableBody.tsx | 18 + docs/assets/builder-base/TableCell.tsx | 23 ++ docs/assets/builder-base/TableHead.tsx | 36 ++ docs/assets/builder-base/TableHeadCell.tsx | 33 ++ docs/assets/builder-base/TableRow.tsx | 36 ++ docs/assets/builder-base/Tabs.tsx | 44 +++ docs/assets/builder-base/Text.tsx | 47 +++ docs/assets/builder-base/Textarea.tsx | 114 ++++++ docs/assets/builder-base/Toast.tsx | 45 +++ docs/assets/builder-base/Tooltip.tsx | 38 ++ 36 files changed, 2287 insertions(+) create mode 100644 docs/assets/builder-base/Accordion.tsx create mode 100644 docs/assets/builder-base/Alert.tsx create mode 100644 docs/assets/builder-base/Avatar.tsx create mode 100644 docs/assets/builder-base/Box.tsx create mode 100644 docs/assets/builder-base/Button.tsx create mode 100644 docs/assets/builder-base/Checkbox.tsx create mode 100644 docs/assets/builder-base/Chip.tsx create mode 100644 docs/assets/builder-base/Combobox.tsx create mode 100644 docs/assets/builder-base/Copytext.tsx create mode 100644 docs/assets/builder-base/Divider.tsx create mode 100644 docs/assets/builder-base/DropdownMenu.tsx create mode 100644 docs/assets/builder-base/Icon.tsx create mode 100644 docs/assets/builder-base/Image.tsx create mode 100644 docs/assets/builder-base/Input.tsx create mode 100644 docs/assets/builder-base/Link.tsx create mode 100644 docs/assets/builder-base/Loader.tsx create mode 100644 docs/assets/builder-base/Modal.tsx create mode 100644 docs/assets/builder-base/ModalContent.tsx create mode 100644 docs/assets/builder-base/ModalFooter.tsx create mode 100644 docs/assets/builder-base/ModalHeader.tsx create mode 100644 docs/assets/builder-base/Popover.tsx create mode 100644 docs/assets/builder-base/Progress.tsx create mode 100644 docs/assets/builder-base/Radio.tsx create mode 100644 docs/assets/builder-base/Select.tsx create mode 100644 docs/assets/builder-base/Switch.tsx create mode 100644 docs/assets/builder-base/Table.tsx create mode 100644 docs/assets/builder-base/TableBody.tsx create mode 100644 docs/assets/builder-base/TableCell.tsx create mode 100644 docs/assets/builder-base/TableHead.tsx create mode 100644 docs/assets/builder-base/TableHeadCell.tsx create mode 100644 docs/assets/builder-base/TableRow.tsx create mode 100644 docs/assets/builder-base/Tabs.tsx create mode 100644 docs/assets/builder-base/Text.tsx create mode 100644 docs/assets/builder-base/Textarea.tsx create mode 100644 docs/assets/builder-base/Toast.tsx create mode 100644 docs/assets/builder-base/Tooltip.tsx diff --git a/docs/assets/builder-base/Accordion.tsx b/docs/assets/builder-base/Accordion.tsx new file mode 100644 index 000000000..fdd5fe1df --- /dev/null +++ b/docs/assets/builder-base/Accordion.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { PdsAccordion } from '@pine-ds/react'; + +interface AccordionProps { + label?: React.ReactNode; + children: React.ReactNode; + isOpen?: boolean; + onToggle?: (isOpen: boolean) => void; + className?: string; + componentId?: string; +} + +export const Accordion: React.FC = ({ + label = 'Details', + children, + isOpen = false, + onToggle, + className = '', + componentId +}) => { + // Handle toggle event (if needed for controlled behavior) + const handleToggle = React.useCallback(() => { + if (onToggle) { + onToggle(!isOpen); + } + }, [isOpen, onToggle]); + + return ( + + {label} + {children} + + ); +}; diff --git a/docs/assets/builder-base/Alert.tsx b/docs/assets/builder-base/Alert.tsx new file mode 100644 index 000000000..6c2d44c42 --- /dev/null +++ b/docs/assets/builder-base/Alert.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { PdsAlert } from '@pine-ds/react'; + +interface AlertProps { + variant?: 'default' | 'danger' | 'info' | 'success' | 'warning'; + heading?: string; + children: React.ReactNode; + onDismiss?: () => void; + className?: string; + dismissible?: boolean; + small?: boolean; + componentId?: string; + actions?: React.ReactNode; +} + +export const Alert: React.FC = ({ + variant = 'default', + heading, + children, + onDismiss, + className = '', + dismissible = false, + small = false, + componentId, + actions +}) => { + // Handle dismiss event + const handleDismiss = () => { + if (onDismiss) { + onDismiss(); + } + }; + + return ( + + {children} + {actions &&
{actions}
} +
+ ); +}; diff --git a/docs/assets/builder-base/Avatar.tsx b/docs/assets/builder-base/Avatar.tsx new file mode 100644 index 000000000..cfc0cbd07 --- /dev/null +++ b/docs/assets/builder-base/Avatar.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { PdsAvatar } from '@pine-ds/react'; + +interface AvatarProps { + image?: string; + alt?: string; + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | string; + variant?: 'customer' | 'admin'; + badge?: boolean; + dropdown?: boolean; + onClick?: () => void; + className?: string; + componentId?: string; +} + +export const Avatar: React.FC = ({ + image, + alt, + size = 'lg', + variant = 'customer', + badge = false, + dropdown = false, + onClick, + className = '', + componentId +}) => { + // Handle click event for dropdown functionality + const handleClick = () => { + if (dropdown && onClick) { + onClick(); + } + }; + + return ( + + ); +}; diff --git a/docs/assets/builder-base/Box.tsx b/docs/assets/builder-base/Box.tsx new file mode 100644 index 000000000..1ed3d01c5 --- /dev/null +++ b/docs/assets/builder-base/Box.tsx @@ -0,0 +1,155 @@ +import React from 'react'; +import { PdsBox } from '@pine-ds/react'; + + +/** + * PdsBox - A flexible layout container component + * + * **⚠️ CRITICAL LAYOUT BEHAVIOR:** + * - **Default Direction**: Items flow HORIZONTALLY by default (`direction="row"`) + * - **For Vertical Stacking**: You MUST explicitly set `direction="column"` + * - **Common Mistake**: Expecting vertical stacking without setting direction + * + * **Layout Patterns:** + * - **Horizontal Flow (Default)**: Items are placed side by side + * - **Vertical Stacking**: Set `direction="column"` for items to stack vertically + * - **Main Containers**: Always use `direction="column"` and `fit="true"` for page sections and main content areas + * - **Grid Layout**: Use inside `pds-row` with `size-*` props for responsive grid + * + * **Key Props for Layout:** + * - `direction`: **CRITICAL** - Controls item orientation (`row` = horizontal, `column` = vertical) + * - `size`: Sets column width (1-12 grid system, works best inside pds-row) + * - `gap`: Controls spacing between items + * - `justifyContent`: Horizontal alignment of items + * - `alignItems`: Vertical alignment of items + * + * **Usage Examples:** + * ```tsx + * // ⚠️ HORIZONTAL flow (default behavior - items side by side) + * + *
Item 1
+ *
Item 2
// These will be HORIZONTAL + *
+ * + * // ✅ VERTICAL stacking (explicit - items stacked) + * + *
Item 1
+ *
Item 2
// These will be VERTICAL + *
+ * + * // ✅ MAIN CONTAINERS should use direction="column" and fit="true" + * + *

Page Title

+ *

Page description

+ * + * Action 1 + * Action 2 + * + *
+ * + * // Grid layout inside pds-row + * + * Half width + * Half width + * + * ``` + */ + +interface BoxProps { + children?: React.ReactNode; + display?: 'block' | 'flex' | 'inline' | 'inline-block' | 'inline-flex' | 'none'; + direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse'; + wrap?: 'nowrap' | 'wrap' | 'wrap-reverse'; + justifyContent?: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; + alignItems?: 'start' | 'end' | 'center' | 'baseline' | 'stretch'; + alignContent?: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'stretch'; + gap?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + padding?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingTop?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingBottom?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingLeft?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingRight?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + margin?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginTop?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginBottom?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginLeft?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginRight?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + border?: boolean; + borderTop?: boolean; + borderBottom?: boolean; + borderLeft?: boolean; + borderRight?: boolean; + borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; + backgroundColor?: string; + borderColor?: string; + flex?: 'none' | 'auto' | 'initial' | 'grow' | 'shrink'; + className?: string; + componentId?: string; +} + +export const Box: React.FC = ({ + children, + display, + direction, + wrap, + justifyContent, + alignItems, + alignContent, + gap, + padding, + paddingTop, + paddingBottom, + paddingLeft, + paddingRight, + margin, + marginTop, + marginBottom, + marginLeft, + marginRight, + border, + borderTop, + borderBottom, + borderLeft, + borderRight, + borderRadius, + backgroundColor, + borderColor, + flex, + className = '', + componentId +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Button.tsx b/docs/assets/builder-base/Button.tsx new file mode 100644 index 000000000..cc7dd5cd4 --- /dev/null +++ b/docs/assets/builder-base/Button.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { PdsButton } from '@pine-ds/react'; + +interface ButtonProps { + children: React.ReactNode; + variant?: 'primary' | 'secondary' | 'accent' | 'disclosure' | 'destructive' | 'unstyled'; + type?: 'button' | 'submit' | 'reset'; + disabled?: boolean; + loading?: boolean; + fullWidth?: boolean; + iconOnly?: boolean; + href?: string; + target?: '_blank' | '_self' | '_parent' | '_top'; + name?: string; + value?: string; + startIcon?: React.ReactNode; // For start slot + endIcon?: React.ReactNode; // For end slot + onClick?: (event: Event) => void; + className?: string; + componentId?: string; + // Deprecated prop for backward compatibility + icon?: string; +} + +export const Button: React.FC = ({ + children, + variant = 'primary', + type = 'button', + disabled = false, + loading = false, + fullWidth = false, + iconOnly = false, + href, + target, + name, + value, + startIcon, + endIcon, + onClick, + className = '', + componentId, + icon // deprecated +}) => { + // Handle click event + const handleClick = (event: Event) => { + if (onClick) { + onClick(event); + } + }; + + // Warn about deprecated icon prop + React.useEffect(() => { + if (icon) { + console.warn('Button: The "icon" prop is deprecated. Use "startIcon" instead.'); + } + }, [icon]); + + return ( + + {startIcon && {startIcon}} + {children} + {endIcon && {endIcon}} + + ); +}; diff --git a/docs/assets/builder-base/Checkbox.tsx b/docs/assets/builder-base/Checkbox.tsx new file mode 100644 index 000000000..f985da02e --- /dev/null +++ b/docs/assets/builder-base/Checkbox.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { PdsCheckbox } from '@pine-ds/react'; + +interface CheckboxChangeEventDetail { + checked: boolean; + value: string; +} + +interface CheckboxProps { + label: string; + checked?: boolean; + disabled?: boolean; + indeterminate?: boolean; + invalid?: boolean; + required?: boolean; + hideLabel?: boolean; + name?: string; + value?: string; + helperMessage?: string; + errorMessage?: string; + onChange?: (detail: CheckboxChangeEventDetail) => void; + onInput?: (detail: CheckboxChangeEventDetail) => void; + className?: string; + componentId: string; // Required for accessibility +} + +export const Checkbox: React.FC = ({ + label, + checked = false, + disabled = false, + indeterminate = false, + invalid = false, + required = false, + hideLabel = false, + name, + value, + helperMessage, + errorMessage, + onChange, + onInput, + className = '', + componentId +}) => { + // Handle checkbox change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + // Handle input event + const handleInput = (event: CustomEvent) => { + if (onInput) { + onInput(event.detail); + } + }; + + // Generate unique componentId if not provided + const checkboxId = componentId || React.useId(); + + return ( + + ); +}; diff --git a/docs/assets/builder-base/Chip.tsx b/docs/assets/builder-base/Chip.tsx new file mode 100644 index 000000000..b30798118 --- /dev/null +++ b/docs/assets/builder-base/Chip.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { PdsChip } from '@pine-ds/react'; + +interface ChipProps { + children: React.ReactNode; + sentiment?: 'accent' | 'brand' | 'danger' | 'info' | 'neutral' | 'success' | 'warning'; + variant?: 'text' | 'tag' | 'dropdown'; + icon?: string; + dot?: boolean; + large?: boolean; + onTagClose?: () => void; + className?: string; + componentId?: string; +} + +export const Chip: React.FC = ({ + children, + sentiment = 'neutral', + variant = 'text', + icon, + dot = false, + large = false, + onTagClose, + className = '', + componentId +}) => { + // Handle tag close event + const handleTagClose = () => { + if (onTagClose) { + onTagClose(); + } + }; + + // Validate props for brand sentiment + const effectiveProps = React.useMemo(() => { + if (sentiment === 'brand') { + // Brand sentiment only supports icon and large, ignore others + return { + sentiment, + variant: 'text', // Brand always uses text variant + icon, + large, + dot: false, // Brand doesn't support dot + }; + } + + return { + sentiment, + variant, + icon, + large, + dot, + }; + }, [sentiment, variant, icon, large, dot]); + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Combobox.tsx b/docs/assets/builder-base/Combobox.tsx new file mode 100644 index 000000000..dcd3d8797 --- /dev/null +++ b/docs/assets/builder-base/Combobox.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { PdsCombobox } from '@pine-ds/react'; + +interface ComboboxProps { + label?: string; + value?: string; + placeholder?: string; + disabled?: boolean; + required?: boolean; + invalid?: boolean; + name?: string; + helperMessage?: string; + errorMessage?: string; + options: Array<{ label: string; value: string; disabled?: boolean }>; + filterable?: boolean; + multiSelect?: boolean; + onChange?: (value: string | string[]) => void; + onFilter?: (query: string) => void; + className?: string; + componentId: string; // Required +} + +export const Combobox: React.FC = ({ + label, + value, + placeholder, + disabled = false, + required = false, + invalid = false, + name, + helperMessage, + errorMessage, + options, + filterable = true, + multiSelect = false, + onChange, + onFilter, + className = '', + componentId +}) => { + // Handle change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + // Handle filter event + const handleFilter = (event: CustomEvent) => { + if (onFilter) { + onFilter(event.detail); + } + }; + + return ( + + {options.map((option) => ( + + ))} + + ); +}; diff --git a/docs/assets/builder-base/Copytext.tsx b/docs/assets/builder-base/Copytext.tsx new file mode 100644 index 000000000..ccd35fd7d --- /dev/null +++ b/docs/assets/builder-base/Copytext.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { PdsCopytext } from '@pine-ds/react'; + +interface CopytextProps { + children: React.ReactNode; + copyable?: boolean; + value?: string; + onCopy?: (value: string) => void; + className?: string; + componentId?: string; +} + +export const Copytext: React.FC = ({ + children, + copyable = true, + value, + onCopy, + className = '', + componentId +}) => { + // Handle copy event + const handleCopy = (event: CustomEvent) => { + if (onCopy) { + onCopy(event.detail); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Divider.tsx b/docs/assets/builder-base/Divider.tsx new file mode 100644 index 000000000..59b563029 --- /dev/null +++ b/docs/assets/builder-base/Divider.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { PdsDivider } from '@pine-ds/react'; + +interface DividerProps { + direction?: 'horizontal' | 'vertical'; + className?: string; + componentId?: string; +} + +export const Divider: React.FC = ({ + direction = 'horizontal', + className = '', + componentId +}) => { + return ( + + ); +}; diff --git a/docs/assets/builder-base/DropdownMenu.tsx b/docs/assets/builder-base/DropdownMenu.tsx new file mode 100644 index 000000000..ea720ff4c --- /dev/null +++ b/docs/assets/builder-base/DropdownMenu.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { PdsDropdownMenu } from '@pine-ds/react'; + +interface DropdownMenuItem { + label: string; + value: string; + disabled?: boolean; + separator?: boolean; + onClick?: () => void; +} + +interface DropdownMenuProps { + trigger: React.ReactNode; + items: DropdownMenuItem[]; + placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'; + open?: boolean; + onItemClick?: (value: string) => void; + onOpen?: () => void; + onClose?: () => void; + className?: string; + componentId?: string; +} + +export const DropdownMenu: React.FC = ({ + trigger, + items, + placement = 'bottom-start', + open = false, + onItemClick, + onOpen, + onClose, + className = '', + componentId +}) => { + // Handle item click event + const handleItemClick = (event: CustomEvent) => { + if (onItemClick) { + onItemClick(event.detail); + } + }; + + // Handle open event + const handleOpen = () => { + if (onOpen) { + onOpen(); + } + }; + + // Handle close event + const handleClose = () => { + if (onClose) { + onClose(); + } + }; + + return ( + +
{trigger}
+ {items.map((item, index) => ( + + {item.separator ? ( + + ) : ( + + {item.label} + + )} + + ))} +
+ ); +}; diff --git a/docs/assets/builder-base/Icon.tsx b/docs/assets/builder-base/Icon.tsx new file mode 100644 index 000000000..bf8053fa3 --- /dev/null +++ b/docs/assets/builder-base/Icon.tsx @@ -0,0 +1,411 @@ +import React from 'react'; +import { PdsIcon } from '@pine-ds/react'; + +/** Available Icon Names: + +property1Default +accessKey +activity +add +addCircle +addImage +addSmall +addSquare +advanced +aiSparkle +aiWriter +aiWriterFilled +alignCenter +alignHorizontalBottom +alignHorizontalCenter +alignHorizontalTop +alignJustify +alignLeft +alignRight +alignVerticalCenter +alignVerticalLeft +alignVerticalRight +appStore +archive +arrowCorner +arrowDown +arrowLeft +arrowRight +arrowUp +assessment +atSign +attach +automations +ban +bank +barChartSquare +barChartTone +bell +bellTone +block +blog +blogFilled +bold +bookmark +brackets +broadcast +bulb +button +calendarDate +calendarSchedule +calendarSimple +cardAmex +cardAndroid +cardApple +cardDinersClub +cardDiscover +cardGeneric +cardGpay +cardMastercard +cardPaypal +cardStripe +cardUpdate +cardVisa +caretDown +caretLeft +caretRight +caretUp +cart +cartAdd +certificate +chart +chartFilled +check +checkCircle +checkCircleFilled +circle1 +circle2 +circle3 +circle4 +circle5 +circle6 +circle7 +circle8 +circle9 +circleA +circleB +circleDashed +clapperboardTone +clock +closedCaptions +cloudUpload +code +codeBlock +color +columns +comment +commentNo +connect +contact +conversation +copy +copy07 +coupon +course +courseFilled +creatorStudio +creatorStudioFilled +creditCardTone +cubeTone +cursor +cursorPointer +customField +customize +danger +dangerFilled +dataflow01 +deleteCircle +deleteKey +deleteX +dollar +dotMenuHorizontal +dotsHorizontalTone +downSmall +download +downsell +draft +drawerCollapse +drawerExpand +drop +duplicate +editor +editorFilled +emailActivity +emoji +enlarge +enlargeVertical +expand +facebookMessenger +favorite +feedback +file +fileAdd +fileDownload +fileLock +fileMoney +fileSearch +filter +flag +flash +flashFilled +floppyDisk +folder +folderGroup +form +formField +formFilled +fullscreen +funnel +gear +gearFilled +giftTone +gitPullRequest +grantOffer +handle +handle2 +handle2Vertical +hashtag +hdVideo +heading1 +heading2 +heading3 +heading4 +heading5 +heading6 +headingLarge +headingSmall +headphones +headset +headsetTone +help +helpFilled +home +homeAlt +homeFilled +homeTone +horizontalLine +image +infoCircle +infoCircleFilled +iosBattery +iosData +iosWifi +italic +kajabi +kajabiFilled +keyboard01 +klarna +lab +launch +layoutAlt03 +layoutBottom +layoutGrid +layoutGrid02 +layoutList +layoutTop +leftSmall +linkBroken +listBullet +listDetails +listNumbers +location +lock +lockAlt +logoAfterpay +logoFacebook +logoFacebookRound +logoInstagram +logoLinkedin +logoOpenai +logoSnapchat +logoTiktok +logoTwitter +logoTwitterX +logoYoutube +loop +mail +mailFilled +mailOpen +mailTone +map +mapped +marginLeft +marginRight +marker +markerFilled +megaphone +megaphoneFilled +menu +menuAlt +menuBordered +merge +messageTextSquare02 +microphone +microphoneOff +monitor +monitorFilled +monitorTone +moreMenu +moreMenuFilled +moveLeft +moveRight +multiPay +musicNote01 +newsletter +newsletter2 +oneOffSession +oneTime +packages +pause +pauseCircle +payout +pen +percent02 +phonePortrait +phoneTone +phoneToolbar +phoneToolbarFilled +pieChartFilled +play +playCircle +playOutline +playStore +plug +plusSquare +premium +present +previewOff +previewOn +product +productFilled +questionCircle +quote +redo +refresh +remove +removeCircle +rename +resetPassword +restore +rightSmall +rocket +rocketFilled +rocketTone +roundDollar +rows +scissor +screenShareOff +screenShareOn +search +searchMdTone +searchSmall +sendMessage +sequences +settingsTone +share +shoppingCartFilled +skipped +slashDivider +speaker +split +squareDollar +stack +star +star03 +starFilled +starsTone +stop +stopwatch +strikethrough +subscript +subscriptions +superAdmin +superscript +switchVertical +sync +tabletLandscape +tabletPortrait +tag +tagFilled +textStyles +themeStore +themeStoreFilled +thumbDown +thumbDownFilled +thumbUp +thumbUpFilled +trash +trendDown +trendNone +trendUp +trophy +trophyFilled +type +underline +undo +unlock +unmapped +upSmall +upload +url +user +userCircle +userCircleFilled +userFilled +userStar +userStarFilled +users +usersAlt +usersFilled +usersTone +videoOff +videoOn +warning +warningFilled +windowParagraph +world +wrench +zoomIn +zoomOut +**/ + +interface IconProps { + name?: string; // For named icons (web components only - deprecated for React) + icon?: any; // For imported icon objects (React preferred) + size?: string | number; + color?: string; + className?: string; + componentId?: string; +} + +export const Icon: React.FC = ({ + name, + icon, + size, + color, + className = '', + componentId +}) => { + // Warn about deprecated name prop in React + React.useEffect(() => { + if (name && !icon) { + console.warn('Icon: The "name" prop is deprecated for React. Import the icon and use the "icon" prop instead.'); + } + }, [name, icon]); + + return ( + + ); +}; diff --git a/docs/assets/builder-base/Image.tsx b/docs/assets/builder-base/Image.tsx new file mode 100644 index 000000000..1fc2d113f --- /dev/null +++ b/docs/assets/builder-base/Image.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { PdsImage } from '@pine-ds/react'; + +interface ImageProps { + src: string; // Required + alt: string; // Required for accessibility + width?: string | number; + height?: string | number; + lazy?: boolean; + className?: string; + componentId?: string; +} + +export const Image: React.FC = ({ + src, + alt, + width, + height, + lazy = true, + className = '', + componentId +}) => { + return ( + + ); +}; diff --git a/docs/assets/builder-base/Input.tsx b/docs/assets/builder-base/Input.tsx new file mode 100644 index 000000000..e261c4601 --- /dev/null +++ b/docs/assets/builder-base/Input.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { PdsInput } from '@pine-ds/react'; + +interface InputChangeEventDetail { + value: string | null; + event: Event; +} + +interface InputInputEventDetail { + value: string | null; + event: Event; +} + +interface InputProps { + label?: string; + value?: string | number | null; + type?: 'email' | 'number' | 'password' | 'tel' | 'text' | 'url'; + placeholder?: string; + disabled?: boolean; + readonly?: boolean; + required?: boolean; + invalid?: boolean; + fullWidth?: boolean; + name?: string; + autocomplete?: string; + min?: string; + max?: string; + minlength?: string; + maxlength?: string; + pattern?: string; + step?: string; + debounce?: number; + helperMessage?: string; + errorMessage?: string; + prefixContent?: React.ReactNode; + suffixContent?: React.ReactNode; + prependContent?: React.ReactNode; + appendContent?: React.ReactNode; + actionContent?: React.ReactNode; + onChange?: (detail: InputChangeEventDetail) => void; + onInput?: (detail: InputInputEventDetail) => void; + onFocus?: (event: FocusEvent) => void; + onBlur?: (event: FocusEvent) => void; + className?: string; + componentId?: string; // Optional with fallback +} + +export const Input: React.FC = ({ + label, + value = '', + type = 'text', + placeholder, + disabled = false, + readonly = false, + required = false, + invalid = false, + fullWidth = false, + name, + autocomplete, + min, + max, + minlength, + maxlength, + pattern, + step, + debounce, + helperMessage, + errorMessage, + prefixContent, + suffixContent, + prependContent, + appendContent, + actionContent, + onChange, + onInput, + onFocus, + onBlur, + className = '', + componentId +}) => { + // Generate unique componentId if not provided + const inputId = componentId || React.useId(); + // Handle change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + // Handle input event + const handleInput = (event: CustomEvent) => { + if (onInput) { + onInput(event.detail); + } + }; + + // Handle focus event + const handleFocus = (event: CustomEvent) => { + if (onFocus) { + onFocus(event.detail); + } + }; + + // Handle blur event + const handleBlur = (event: CustomEvent) => { + if (onBlur) { + onBlur(event.detail); + } + }; + + return ( + + {prefixContent &&
{prefixContent}
} + {suffixContent &&
{suffixContent}
} + {prependContent &&
{prependContent}
} + {appendContent &&
{appendContent}
} + {actionContent &&
{actionContent}
} +
+ ); +}; diff --git a/docs/assets/builder-base/Link.tsx b/docs/assets/builder-base/Link.tsx new file mode 100644 index 000000000..3560a3763 --- /dev/null +++ b/docs/assets/builder-base/Link.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { PdsLink } from '@pine-ds/react'; + +interface LinkProps { + href: string; // Required + children?: React.ReactNode; + variant?: 'inline' | 'plain'; + fontSize?: 'sm' | 'md' | 'lg'; + color?: string; + external?: boolean; + className?: string; + componentId?: string; +} + +export const Link: React.FC = ({ + href, + children, + variant = 'inline', + fontSize = 'lg', + color, + external = false, + className = '', + componentId +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Loader.tsx b/docs/assets/builder-base/Loader.tsx new file mode 100644 index 000000000..0b88c8e3b --- /dev/null +++ b/docs/assets/builder-base/Loader.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { PdsLoader } from '@pine-ds/react'; + +interface LoaderProps { + children?: React.ReactNode; + isLoading?: boolean; + variant?: 'spinner' | 'ellipsis'; + size?: string; + className?: string; + componentId?: string; +} + +export const Loader: React.FC = ({ + children, + isLoading = true, + variant = 'spinner', + size, + className = '', + componentId +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Modal.tsx b/docs/assets/builder-base/Modal.tsx new file mode 100644 index 000000000..3878f294a --- /dev/null +++ b/docs/assets/builder-base/Modal.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { PdsModal } from '@pine-ds/react'; + +interface ModalProps { + children: React.ReactNode; + open?: boolean; + size?: 'sm' | 'md' | 'lg' | 'fullscreen'; + backdropDismiss?: boolean; + onOpen?: () => void; + onClose?: () => void; + className?: string; + componentId?: string; +} + +export const Modal: React.FC = ({ + children, + open = false, + size = 'md', + backdropDismiss = true, + onOpen, + onClose, + className = '', + componentId +}) => { + // Handle modal open event + const handleOpen = () => { + if (onOpen) { + onOpen(); + } + }; + + // Handle modal close event + const handleClose = () => { + if (onClose) { + onClose(); + } + }; + + // Ref to access modal methods + const modalRef = React.useRef(null); + + // Expose modal methods + const showModal = React.useCallback(() => { + if (modalRef.current) { + modalRef.current.showModal(); + } + }, []); + + const hideModal = React.useCallback(() => { + if (modalRef.current) { + modalRef.current.hideModal(); + } + }, []); + + // Attach methods to ref for external access + React.useImperativeHandle(modalRef, () => ({ + showModal, + hideModal + })); + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/ModalContent.tsx b/docs/assets/builder-base/ModalContent.tsx new file mode 100644 index 000000000..14237ac50 --- /dev/null +++ b/docs/assets/builder-base/ModalContent.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { PdsModalContent } from '@pine-ds/react'; + +interface ModalContentProps { + children: React.ReactNode; + border?: 'none' | 'both' | 'top' | 'bottom'; + className?: string; +} + +export const ModalContent: React.FC = ({ + children, + border = 'none', + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/ModalFooter.tsx b/docs/assets/builder-base/ModalFooter.tsx new file mode 100644 index 000000000..1e0623f76 --- /dev/null +++ b/docs/assets/builder-base/ModalFooter.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { PdsModalFooter } from '@pine-ds/react'; + +interface ModalFooterProps { + children: React.ReactNode; + className?: string; +} + +export const ModalFooter: React.FC = ({ + children, + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/ModalHeader.tsx b/docs/assets/builder-base/ModalHeader.tsx new file mode 100644 index 000000000..a97e96c64 --- /dev/null +++ b/docs/assets/builder-base/ModalHeader.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { PdsModalHeader } from '@pine-ds/react'; + +interface ModalHeaderProps { + children: React.ReactNode; + className?: string; +} + +export const ModalHeader: React.FC = ({ + children, + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Popover.tsx b/docs/assets/builder-base/Popover.tsx new file mode 100644 index 000000000..1eb4cd9dd --- /dev/null +++ b/docs/assets/builder-base/Popover.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { PdsPopover } from '@pine-ds/react'; + +type PlacementType = + | 'top' | 'top-start' | 'top-end' + | 'right' | 'right-start' | 'right-end' + | 'bottom' | 'bottom-start' | 'bottom-end' + | 'left' | 'left-start' | 'left-end'; + +interface PopoverProps { + children: React.ReactNode; + componentId: string; + text: string; + popoverTargetAction?: 'show' | 'toggle' | 'hide'; + popoverType?: 'auto' | 'manual'; + maxWidth?: number; + placement?: PlacementType; + className?: string; +} + +export const Popover: React.FC = ({ + children, + componentId, + text, + popoverTargetAction = 'show', + popoverType = 'auto', + maxWidth = 352, + placement = 'right', + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Progress.tsx b/docs/assets/builder-base/Progress.tsx new file mode 100644 index 000000000..ab313fbb1 --- /dev/null +++ b/docs/assets/builder-base/Progress.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { PdsProgress } from '@pine-ds/react'; + +interface ProgressProps { + label: string; // Required + percent?: number; + animated?: boolean; + showPercent?: boolean; + fillColor?: string; + className?: string; + componentId?: string; // Optional with fallback +} + +export const Progress: React.FC = ({ + label, + percent = 0, + animated = false, + showPercent = false, + fillColor, + className = '', + componentId +}) => { + // Generate unique componentId if not provided + const progressId = componentId || React.useId(); + return ( + + ); +}; diff --git a/docs/assets/builder-base/Radio.tsx b/docs/assets/builder-base/Radio.tsx new file mode 100644 index 000000000..746fdca2f --- /dev/null +++ b/docs/assets/builder-base/Radio.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { PdsRadio } from '@pine-ds/react'; + +interface RadioProps { + label: string; + checked?: boolean; + disabled?: boolean; + invalid?: boolean; + required?: boolean; + hideLabel?: boolean; + name?: string; + value?: string; + helperMessage?: string; + errorMessage?: string; + onChange?: (checked: boolean) => void; + className?: string; + componentId: string; // Required for accessibility +} + +export const Radio: React.FC = ({ + label, + checked = false, + disabled = false, + invalid = false, + required = false, + hideLabel = false, + name, + value, + helperMessage, + errorMessage, + onChange, + className = '', + componentId +}) => { + // Handle radio change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + return ( + + ); +}; diff --git a/docs/assets/builder-base/Select.tsx b/docs/assets/builder-base/Select.tsx new file mode 100644 index 000000000..f8391cad0 --- /dev/null +++ b/docs/assets/builder-base/Select.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { PdsSelect } from '@pine-ds/react'; + +interface SelectProps { + label?: string; + value?: string | string[]; + name: string; // Required + disabled?: boolean; + required?: boolean; + invalid?: boolean; + multiple?: boolean; + hideLabel?: boolean; + autocomplete?: string; // Optional for React-friendliness + helperMessage?: string; // Optional for React-friendliness + errorMessage?: string; // Optional for React-friendliness + actionContent?: React.ReactNode; + children: React.ReactNode; // option and optgroup elements + onChange?: (event: InputEvent) => void; + className?: string; + componentId?: string; // Optional with fallback +} + +export const Select: React.FC = ({ + label, + value, + name, + disabled = false, + required = false, + invalid = false, + multiple = false, + hideLabel = false, + autocomplete, + helperMessage, + errorMessage, + actionContent, + children, + onChange, + className = '', + componentId +}) => { + // Generate unique componentId if not provided + const selectId = componentId || React.useId(); + // Handle change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + return ( + + {actionContent &&
{actionContent}
} + {children} +
+ ); +}; diff --git a/docs/assets/builder-base/Switch.tsx b/docs/assets/builder-base/Switch.tsx new file mode 100644 index 000000000..8c3850e46 --- /dev/null +++ b/docs/assets/builder-base/Switch.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { PdsSwitch } from '@pine-ds/react'; + +interface SwitchProps { + label: string; // Required + checked?: boolean; + disabled?: boolean; + invalid?: boolean; + required?: boolean; + hideLabel?: boolean; + name?: string; + value?: string; + helperMessage?: string; + errorMessage?: string; + onChange?: (event: InputEvent) => void; + className?: string; + componentId: string; // Required for accessibility +} + +export const Switch: React.FC = ({ + label, + checked = false, + disabled = false, + invalid = false, + required = false, + hideLabel = false, + name, + value, + helperMessage, + errorMessage, + onChange, + className = '', + componentId +}) => { + // Handle switch change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + return ( + + ); +}; diff --git a/docs/assets/builder-base/Table.tsx b/docs/assets/builder-base/Table.tsx new file mode 100644 index 000000000..7e8bf0711 --- /dev/null +++ b/docs/assets/builder-base/Table.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { PdsTable } from '@pine-ds/react'; + +interface TableProps { + children: React.ReactNode; + compact?: boolean; + responsive?: boolean; + fixedColumn?: boolean; + selectable?: boolean; + onRowSelect?: (detail: { rowIndex: number; selected: boolean }) => void; + className?: string; + componentId: string; // Required +} + +export const Table: React.FC = ({ + children, + compact = false, + responsive = false, + fixedColumn = false, + selectable = false, + onRowSelect, + className = '', + componentId +}) => { + // Handle row selection event + const handleRowSelect = (event: CustomEvent<{ rowIndex: number; selected: boolean }>) => { + if (onRowSelect) { + onRowSelect(event.detail); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/TableBody.tsx b/docs/assets/builder-base/TableBody.tsx new file mode 100644 index 000000000..b7af820b0 --- /dev/null +++ b/docs/assets/builder-base/TableBody.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { PdsTableBody } from '@pine-ds/react'; + +interface TableBodyProps { + children: React.ReactNode; + className?: string; +} + +export const TableBody: React.FC = ({ + children, + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/TableCell.tsx b/docs/assets/builder-base/TableCell.tsx new file mode 100644 index 000000000..f89db8f49 --- /dev/null +++ b/docs/assets/builder-base/TableCell.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { PdsTableCell } from '@pine-ds/react'; + +interface TableCellProps { + children: React.ReactNode; + truncate?: boolean; + className?: string; +} + +export const TableCell: React.FC = ({ + children, + truncate = false, + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/TableHead.tsx b/docs/assets/builder-base/TableHead.tsx new file mode 100644 index 000000000..1cd401b83 --- /dev/null +++ b/docs/assets/builder-base/TableHead.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { PdsTableHead } from '@pine-ds/react'; + +interface TableHeadProps { + children: React.ReactNode; + indeterminate?: boolean; + isSelected?: boolean; + onSelectAll?: (detail: { isSelected: boolean }) => void; + className?: string; +} + +export const TableHead: React.FC = ({ + children, + indeterminate = false, + isSelected = false, + onSelectAll, + className = '' +}) => { + // Handle select all event + const handleSelectAll = (event: CustomEvent<{ isSelected: boolean }>) => { + if (onSelectAll) { + onSelectAll(event.detail); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/TableHeadCell.tsx b/docs/assets/builder-base/TableHeadCell.tsx new file mode 100644 index 000000000..ea3736faa --- /dev/null +++ b/docs/assets/builder-base/TableHeadCell.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { PdsTableHeadCell } from '@pine-ds/react'; + +interface TableHeadCellProps { + children: React.ReactNode; + sortable?: boolean; + onSort?: (detail: { column: string; direction: string }) => void; + className?: string; +} + +export const TableHeadCell: React.FC = ({ + children, + sortable = false, + onSort, + className = '' +}) => { + // Handle sort event + const handleSort = (event: CustomEvent<{ column: string; direction: string }>) => { + if (onSort) { + onSort(event.detail); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/TableRow.tsx b/docs/assets/builder-base/TableRow.tsx new file mode 100644 index 000000000..ae51e4a0c --- /dev/null +++ b/docs/assets/builder-base/TableRow.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { PdsTableRow } from '@pine-ds/react'; + +interface TableRowProps { + children: React.ReactNode; + indeterminate?: boolean; + isSelected?: boolean; + onRowSelected?: (detail: { rowIndex: number; isSelected: boolean }) => void; + className?: string; +} + +export const TableRow: React.FC = ({ + children, + indeterminate = false, + isSelected = false, + onRowSelected, + className = '' +}) => { + // Handle row selection event + const handleRowSelected = (event: CustomEvent<{ rowIndex: number; isSelected: boolean }>) => { + if (onRowSelected) { + onRowSelected(event.detail); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Tabs.tsx b/docs/assets/builder-base/Tabs.tsx new file mode 100644 index 000000000..ef60f70d8 --- /dev/null +++ b/docs/assets/builder-base/Tabs.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { PdsTabs } from '@pine-ds/react'; + +interface TabsProps { + children: React.ReactNode; + tablistLabel: string; // Required + variant: 'primary' | 'availability' | 'filter' | 'pill'; // Required + activeTabName: string; // Required + onTabClick?: (detail: [number, string]) => void; + className?: string; + componentId?: string; // Optional with fallback +} + +export const Tabs: React.FC = ({ + children, + tablistLabel, + variant, + activeTabName, + onTabClick, + className = '', + componentId +}) => { + // Generate unique componentId if not provided + const tabsId = componentId || React.useId(); + // Handle tab click event + const handleTabClick = (event: CustomEvent<[number, string]>) => { + if (onTabClick) { + onTabClick(event.detail); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Text.tsx b/docs/assets/builder-base/Text.tsx new file mode 100644 index 000000000..eae3c0d18 --- /dev/null +++ b/docs/assets/builder-base/Text.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { PdsText } from '@pine-ds/react'; + +interface TextProps { + children: React.ReactNode; + tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'code' | 'pre' | 'strong' | 'em'; + size?: '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | '2xs' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + weight?: 'extra-light' | 'light' | 'regular' | 'medium' | 'semibold' | 'bold'; + align?: 'start' | 'center' | 'end' | 'justify'; + color?: string; + decoration?: 'strikethrough' | 'underline-dotted'; + gutter?: '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | '2xs'; + italic?: boolean; + truncate?: boolean; + className?: string; +} + +export const Text: React.FC = ({ + children, + tag = 'p', + size, + weight, + align, + color, + decoration, + gutter, + italic = false, + truncate = false, + className = '' +}) => { + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Textarea.tsx b/docs/assets/builder-base/Textarea.tsx new file mode 100644 index 000000000..38e9718d8 --- /dev/null +++ b/docs/assets/builder-base/Textarea.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { PdsTextarea } from '@pine-ds/react'; + +interface TextareaChangeEventDetail { + value: string | null; + event: Event; +} + +interface TextareaInputEventDetail { + value: string | null; + event: Event; +} + +interface TextareaProps { + label?: string; + value?: string | null; + placeholder?: string; + disabled?: boolean; + readonly?: boolean; + required?: boolean; + invalid?: boolean; + name?: string; + autocomplete?: string; + rows?: number; + debounce?: number; + helperMessage?: string; + errorMessage?: string; + actionContent?: React.ReactNode; + onChange?: (detail: TextareaChangeEventDetail) => void; + onInput?: (detail: TextareaInputEventDetail) => void; + onFocus?: (event: FocusEvent) => void; + onBlur?: (event: FocusEvent) => void; + className?: string; + componentId?: string; // Optional with fallback +} + +export const Textarea: React.FC = ({ + label, + value = '', + placeholder, + disabled = false, + readonly = false, + required = false, + invalid = false, + name, + autocomplete, + rows, + debounce, + helperMessage, + errorMessage, + actionContent, + onChange, + onInput, + onFocus, + onBlur, + className = '', + componentId +}) => { + // Generate unique componentId if not provided + const textareaId = componentId || React.useId(); + // Handle change event + const handleChange = (event: CustomEvent) => { + if (onChange) { + onChange(event.detail); + } + }; + + // Handle input event + const handleInput = (event: CustomEvent) => { + if (onInput) { + onInput(event.detail); + } + }; + + // Handle focus event + const handleFocus = (event: CustomEvent) => { + if (onFocus) { + onFocus(event.detail); + } + }; + + // Handle blur event + const handleBlur = (event: CustomEvent) => { + if (onBlur) { + onBlur(event.detail); + } + }; + + return ( + + {actionContent &&
{actionContent}
} +
+ ); +}; diff --git a/docs/assets/builder-base/Toast.tsx b/docs/assets/builder-base/Toast.tsx new file mode 100644 index 000000000..0b0e3f4ca --- /dev/null +++ b/docs/assets/builder-base/Toast.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { PdsToast } from '@pine-ds/react'; + +interface ToastProps { + children: React.ReactNode; + variant?: 'default' | 'success' | 'warning' | 'error' | 'info'; + duration?: number; + dismissible?: boolean; + position?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'; + onDismiss?: () => void; + className?: string; + componentId?: string; +} + +export const Toast: React.FC = ({ + children, + variant = 'default', + duration = 5000, + dismissible = true, + position = 'top-right', + onDismiss, + className = '', + componentId +}) => { + // Handle dismiss event + const handleDismiss = () => { + if (onDismiss) { + onDismiss(); + } + }; + + return ( + + {children} + + ); +}; diff --git a/docs/assets/builder-base/Tooltip.tsx b/docs/assets/builder-base/Tooltip.tsx new file mode 100644 index 000000000..9a7c048c6 --- /dev/null +++ b/docs/assets/builder-base/Tooltip.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { PdsTooltip } from '@pine-ds/react'; + +interface TooltipProps { + children: React.ReactNode; + content: React.ReactNode; + placement?: 'top' | 'bottom' | 'left' | 'right' | 'auto'; + trigger?: 'hover' | 'click' | 'focus'; + disabled?: boolean; + delay?: number; + className?: string; + componentId?: string; +} + +export const Tooltip: React.FC = ({ + children, + content, + placement = 'top', + trigger = 'hover', + disabled = false, + delay = 0, + className = '', + componentId +}) => { + return ( + +
{children}
+
{content}
+
+ ); +}; From 9a7555f815d6c5a85de85325d82fe8924548e5ed Mon Sep 17 00:00:00 2001 From: Quinton Jason Date: Mon, 4 Aug 2025 09:52:48 -0500 Subject: [PATCH 02/10] fix: resolve box props --- docs/assets/builder-base/Box.tsx | 166 +++++++++++++++++++------------ 1 file changed, 101 insertions(+), 65 deletions(-) diff --git a/docs/assets/builder-base/Box.tsx b/docs/assets/builder-base/Box.tsx index 1ed3d01c5..0834d057f 100644 --- a/docs/assets/builder-base/Box.tsx +++ b/docs/assets/builder-base/Box.tsx @@ -56,98 +56,134 @@ import { PdsBox } from '@pine-ds/react'; */ interface BoxProps { + alignItems?: 'start' | 'end' | 'center' | 'baseline' | 'stretch'; + alignContent?: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'stretch'; + backgroundColor?: string; + border?: boolean; + borderColor?: string; + borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; children?: React.ReactNode; - display?: 'block' | 'flex' | 'inline' | 'inline-block' | 'inline-flex' | 'none'; + className?: string; + componentId?: string; direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse'; - wrap?: 'nowrap' | 'wrap' | 'wrap-reverse'; + display?: 'block' | 'flex' | 'inline' | 'inline-block' | 'inline-flex' | 'none'; justifyContent?: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; - alignItems?: 'start' | 'end' | 'center' | 'baseline' | 'stretch'; - alignContent?: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'stretch'; gap?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + fit?: boolean; + flex?: 'none' | 'auto' | 'initial' | 'grow' | 'shrink'; padding?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - paddingTop?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingBlockStart?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingBlockEnd?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingInlineStart?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + paddingInlineEnd?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; paddingBottom?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; paddingLeft?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; paddingRight?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - margin?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - marginTop?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - marginBottom?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - marginLeft?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - marginRight?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - border?: boolean; - borderTop?: boolean; - borderBottom?: boolean; - borderLeft?: boolean; - borderRight?: boolean; - borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; - backgroundColor?: string; - borderColor?: string; - flex?: 'none' | 'auto' | 'initial' | 'grow' | 'shrink'; - className?: string; - componentId?: string; + marginBlockStart?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginBlockEnd?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginInlineStart?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + marginInlineEnd?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + minHeight?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + minWidth?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + offset?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + offsetXs?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + offsetSm?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + offsetMd?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + offsetLg?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + offsetXl?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + shadow?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + size?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + sizeXs?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + sizeSm?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + sizeMd?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + sizeLg?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + sizeXl?: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'; + wrap?: 'nowrap' | 'wrap' | 'wrap-reverse'; } export const Box: React.FC = ({ - children, - display, - direction, - wrap, - justifyContent, alignItems, alignContent, - gap, - padding, - paddingTop, - paddingBottom, - paddingLeft, - paddingRight, - margin, - marginTop, - marginBottom, - marginLeft, - marginRight, - border, - borderTop, - borderBottom, - borderLeft, - borderRight, - borderRadius, backgroundColor, + border, borderColor, - flex, + borderRadius, + children, className = '', - componentId + componentId, + direction, + display, + fit, + flex, + gap, + justifyContent, + marginBlockStart, + marginBlockEnd, + marginInlineStart, + marginInlineEnd, + minHeight, + minWidth, + offset, + offsetXs, + offsetSm, + offsetMd, + offsetLg, + offsetXl, + padding, + paddingBlockStart, + paddingBlockEnd, + paddingInlineStart, + paddingInlineEnd, + shadow, + size, + sizeXs, + sizeSm, + sizeMd, + sizeLg, + sizeXl, + wrap, }) => { return ( {children} From a9295ff2b0805c604da55f0c053857b360b4cea7 Mon Sep 17 00:00:00 2001 From: Quinton Jason Date: Mon, 4 Aug 2025 09:53:00 -0500 Subject: [PATCH 03/10] fix: add app page --- docs/assets/builder-base/app/App.tsx | 887 +++++++++++++++++++++++++++ 1 file changed, 887 insertions(+) create mode 100644 docs/assets/builder-base/app/App.tsx diff --git a/docs/assets/builder-base/app/App.tsx b/docs/assets/builder-base/app/App.tsx new file mode 100644 index 000000000..a1e5bdf2e --- /dev/null +++ b/docs/assets/builder-base/app/App.tsx @@ -0,0 +1,887 @@ +import React from 'react'; +import { + Box, + Text, + Button, + Input, + Alert, + Table, + TableHead, + TableBody, + TableRow, + TableCell, + TableHeadCell, + Icon, + Checkbox, + Radio, + Switch, + Select, + Textarea, + Combobox, + Chip, + Avatar, + Divider, + Progress, + Loader, + Accordion, + Modal, + ModalHeader, + ModalContent, + ModalFooter, + Tooltip, + Popover, + Toast, + Link, + Image, + Copytext, + Tabs, + DropdownMenu +} from './components'; + +function App() { + const [modalOpen, setModalOpen] = React.useState(false); + const [activeTab, setActiveTab] = React.useState('tab1'); + const [checkboxChecked, setCheckboxChecked] = React.useState(false); + const [radioValue, setRadioValue] = React.useState(''); + const [switchChecked, setSwitchChecked] = React.useState(false); + const [inputValue, setInputValue] = React.useState(''); + const [textareaValue, setTextareaValue] = React.useState(''); + const [selectValue, setSelectValue] = React.useState(''); + const [comboboxValue, setComboboxValue] = React.useState(''); + const [showSuccessToast, setShowSuccessToast] = React.useState(false); + const [showWarningToast, setShowWarningToast] = React.useState(false); + + return ( + + + {/* Header */} + + + Pine Design System - Complete Component Showcase + + + All available Pine DS components with working examples + + + + {/* Accordion */} + + Accordion + + + This is the accordion content that can be expanded or collapsed. It's useful for organizing content in a space-efficient way. + + + + + {/* Alert */} + + Alert + + + This is a default alert message. + + + This is a success alert message. + + + This is a warning alert message. + + + This is a danger alert message. + + + This is an info alert message. + + + This alert has a heading and can be dismissed. + + + + + {/* Avatar */} + + Avatar + + + + + + + + + + + + {/* Button */} + + Button + + + + + + + + + + + + + + + + + + + {/* Checkbox */} + + Checkbox + + setCheckboxChecked(detail.checked)} + /> + + + + + + + + {/* Chip */} + + Chip + + + Neutral + Accent + Brand + Danger + + + Info + Success + Warning + + + Tag Variant + With Dot + Large + + + + + {/* Combobox */} + + Combobox + + setComboboxValue(value as string)} + options={[ + { label: 'Apple', value: 'apple' }, + { label: 'Banana', value: 'banana' }, + { label: 'Cherry', value: 'cherry' }, + { label: 'Date', value: 'date' }, + { label: 'Elderberry', value: 'elderberry' } + ]} + /> + + + + + + {/* Copytext */} + + Copytext + + + Click the copy icon to copy this text + + + API Key: https://example.com/api/key/12345 + + + + + {/* Divider */} + + Divider + + Content above divider + + Content below divider + + + + {/* DropdownMenu */} + + DropdownMenu + + Open Menu + + } + items={[ + { label: 'Edit', value: 'edit' }, + { label: 'Duplicate', value: 'duplicate' }, + { label: 'Archive', value: 'archive', separator: true }, + { label: 'Delete', value: 'delete' } + ]} + componentId="dropdown-menu-basic" + /> + + + {/* Icon */} + + Icon + + + + + + + + + + + + + + + + + + + + + {/* Image */} + + Image + + Sample image + Small image + + + + {/* Input */} + + Input + + setInputValue(detail.value || '')} + /> + + + + + + + + + + {/* Link */} + + Link + + + This is an inline link + + + This is a plain link + + + This is an external link + + + Small link + + + Large link + + + + + {/* Loader */} + + Loader + + + + + + + + {/* Modal */} + + Modal + + setModalOpen(false)} + componentId="modal-basic" + size="md" + > + + + + Simple Modal + + + + + + + + This is a simple modal with basic content. The modal uses proper sub-components for header, content, and footer sections. + + + + + + + + + + + + + {/* Popover */} + + Popover + +

Clicks outside will close this popover

+
+
+ + {/* Progress */} + + Progress + + + + + + + + + + + + + {/* Radio */} + + Radio + + setRadioValue('option1')} + /> + setRadioValue('option2')} + /> + setRadioValue('option3')} + /> + + + + + {/* Select */} + + Select + + + + + + + + {/* Switch */} + + Switch + + setSwitchChecked(!switchChecked)} + /> + + + + + + + {/* Table */} + + Table + + + + Name + Email + Role + Department + Join Date + Status + + + + + John Doe + john@example.com + Admin + Engineering + 2023-01-15 + + Active + + + + Jane Smith + jane@example.com + User + Marketing + 2023-03-22 + + Pending + + + + Bob Johnson + bob@example.com + Editor + Content + 2023-02-10 + + Active + + + + Sarah Wilson + sarah@example.com + Manager + Sales + 2022-11-08 + + Active + + + + Mike Davis + mike@example.com + Developer + Engineering + 2023-04-05 + + Inactive + + + + Lisa Chen + lisa@example.com + Designer + Design + 2023-01-30 + + Active + + + +
+
+ + {/* Tabs */} + + Tabs + setActiveTab(name)} + > + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1. This is the first tab panel. + + + + + Content for Tab 2. This is the second tab panel. + + + + + Content for Tab 3. This is the third tab panel. + + + + + + {/* Text */} + + Text + + Heading 1 + Heading 2 + Heading 3 + Heading 4 + Heading 5 + Heading 6 + Extra large text + Large text + Large text + Medium text (default) + Small text + Extra small text + Bold text + Semibold text + Medium weight text + Regular text + Light text + Italic text + Underlined text + Strikethrough text + Centered text + Right aligned text + Blue text + This is a very long text that will be truncated when it exceeds the container width + + + + {/* Textarea */} + + Textarea + +