Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6d3d043
feat(SFT-2638): Added Solspace AI
thejahid Feb 16, 2026
169cc92
fix: remove stray character from PHP opening tag in SolspaceAIV1.php
thejahid Feb 16, 2026
1d6b1bb
feat(SFT-2638): Add AI form generation feature
thejahid Feb 23, 2026
22bd606
feat(SFT-2638): Enhance FormGenerationService with additional field t…
thejahid Feb 23, 2026
880084b
refactor(SFT-2638): Reorganize field type imports and add documentati…
thejahid Feb 23, 2026
ad1d5d9
feat(SFT-2638): Implement Solspace AI dashboard and usage queries
thejahid Feb 23, 2026
051c54a
refactor(SFT-2638): Update client-side JavaScript files for improved …
thejahid Feb 23, 2026
37fb134
Merge branch 'v5' of https://github.com/solspace/craft-freeform into …
kjmartens Feb 23, 2026
1b138ef
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Feb 26, 2026
a7528a7
feat(SFT-2638): Add AI credit management and daily metrics visualizat…
thejahid Mar 2, 2026
65cbbf1
Merge branch 'feat/SFT-2638-ai-research' of https://github.com/solspa…
thejahid Mar 2, 2026
00f4ecd
feat(SFT-2638): Building Scripts
thejahid Mar 2, 2026
f6441ec
feat(SFT-2638): Enhance AI dashboard with usage chart and metrics tab…
thejahid Mar 2, 2026
24b1120
feat(SFT-2638): Building Scripts
thejahid Mar 2, 2026
35fbded
feat(SFT-2647, SFT-2648): improved Table UI, add support for File Upl…
gustavs-gutmanis Mar 5, 2026
cb61e08
fix(SFT-2665): table file column required adjustments (#2451)
gustavs-gutmanis Mar 10, 2026
2ca6503
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Mar 10, 2026
43b6e8e
fix: table field submission in demo templates
kjmartens Mar 10, 2026
33c9619
fix: table field submission in demo templates
kjmartens Mar 10, 2026
80da9d4
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Mar 10, 2026
2f13f9b
feat(SFT-2638): Review Fix
thejahid Mar 11, 2026
974a87e
Merge branch 'feat/SFT-2638-ai-research' of https://github.com/solspa…
thejahid Mar 11, 2026
13fb145
Merge branch 'v5' of https://github.com/solspace/craft-freeform into …
kjmartens Mar 12, 2026
8bb4eda
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Mar 12, 2026
e40fd65
feat(SFT-2675): Solspace AI Service Pricing
thejahid Mar 15, 2026
faa1dbf
Merge branch 'feat/SFT-2638-ai-research' of https://github.com/solspa…
thejahid Mar 15, 2026
9516513
fix: Remove fallback character in date formatting on AI dashboard
thejahid Mar 16, 2026
1fbc48a
feat(SFT-270): A/B Testing (#2436)
kjmartens Mar 16, 2026
12591ec
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Mar 16, 2026
c40e5fc
fix: correct translations for A/B tests
kjmartens Mar 16, 2026
3fcfbc9
chore: fix unused import Fragment
kjmartens Mar 16, 2026
e072fd9
Merge branch 'v5' of https://github.com/solspace/craft-freeform into …
kjmartens Mar 16, 2026
c333960
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Mar 16, 2026
b0f588a
Merge branch 'v5' of https://github.com/solspace/craft-freeform into …
kjmartens Mar 17, 2026
74db833
Merge branch 'feat/v5.15' of https://github.com/solspace/craft-freefo…
kjmartens Mar 17, 2026
5ed48b2
feat(SFT-2638): Refactor AI dashboard and plans components for improv…
thejahid Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ packages/client/src/request-mocks/http-client.env.json
cghooks.lock

WARP.md
.docs/
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "solspace/craft-freeform",
"description": "The most flexible and user-friendly form building plugin!",
"version": "5.14.21",
"version": "5.14.21.1",
"type": "craft-plugin",
"authors": [
{
Expand Down
2,099 changes: 1,067 additions & 1,032 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
borderRadius,
colors,
shadows,
spacings,
} from '@ff-client/styles/variables';
import styled from 'styled-components';

export const ActionMenuWrapper = styled.div`
position: relative;
`;

export const ActionMenuButton = styled.button`
cursor: pointer;

display: flex;
justify-content: center;
align-items: center;

width: var(--ui-control-height);
height: var(--ui-control-height);
padding: 0;

border: 1px solid ${colors.gray250};
border-radius: ${borderRadius.md};
background: ${colors.white};
color: ${colors.gray700};

svg {
width: 18px;
height: 18px;
stroke: ${colors.gray500};
}

&:hover,
&.open {
background: rgba(96, 125, 159, 0.3);
}
`;

export const ActionMenuDropdown = styled.div`
position: absolute;
right: 0;
top: 100%;
z-index: 100;

min-width: 120px;

background: ${colors.white};
box-shadow: ${shadows.boxSubtle};

border: 1px solid ${colors.gray200};
border-radius: ${borderRadius.md};
`;

export const ActionMenuItem = styled.button<{ $destructive?: boolean }>`
cursor: pointer;

display: flex;
align-items: center;
gap: ${spacings.sm};

width: 100%;
padding: ${spacings.sm} ${spacings.md};

background: transparent;
color: ${({ $destructive }) =>
$destructive ? colors.red600 : colors.gray700};

border: 0;
border-top: 1px solid ${colors.gray200};

font-size: 12px;
text-align: left;

&:first-child {
border-top: 0;
}

&:hover {
background: ${colors.gray050};
}

svg {
width: 16px;
height: 16px;
}
`;
79 changes: 79 additions & 0 deletions packages/client/src/app/components/action-menu/action-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { FC } from 'react';
import React, { useState } from 'react';
import { useClickOutside } from '@ff-client/hooks/use-click-outside';
import { useOnKeypress } from '@ff-client/hooks/use-on-keypress';
import classes from '@ff-client/utils/classes';
import translate from '@ff-client/utils/translations';
import EllipsisIcon from '@ff-icons/actions/ellipsis.svg';

import {
ActionMenuButton,
ActionMenuDropdown,
ActionMenuItem,
ActionMenuWrapper,
} from './action-menu.styles';
import type { ActionMenuChoice } from './action-menu.types';

type Props = {
ariaLabel?: string;
choices: ActionMenuChoice[];
};

export const ActionMenu: FC<Props> = ({
choices,
ariaLabel = translate('Actions'),
}) => {
const [open, setOpen] = useState(false);

// Close menu on Esc key press
useOnKeypress({
callback: (event) => {
if (event.key === 'Escape') {
setOpen(false);
}
},
meetsCondition: open,
type: 'keyup',
});

// Close menu on click outside
const wrapperRef = useClickOutside<HTMLDivElement>({
isEnabled: open,
callback: () => setOpen(false),
});

return (
<ActionMenuWrapper ref={wrapperRef}>
<ActionMenuButton
type="button"
className={classes(open && 'open')}
onClick={() => setOpen((prev) => !prev)}
aria-label={ariaLabel}
aria-expanded={open}
title={ariaLabel}
>
<EllipsisIcon />
</ActionMenuButton>

{open && (
<ActionMenuDropdown>
{choices.map((choice) => (
<ActionMenuItem
key={choice.label}
type="button"
className={choice.className}
$destructive={choice.destructive}
onClick={() => {
setOpen(false);
choice.onClick();
}}
>
{choice.icon}
<span>{choice.label}</span>
</ActionMenuItem>
))}
</ActionMenuDropdown>
)}
</ActionMenuWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ReactNode } from 'react';

export type ActionMenuChoice = {
destructive?: boolean;
icon?: ReactNode;
label: string;
className?: string;
onClick: () => void;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
LabelValueDisplay,
List,
} from './dropdown.options.styles';
import { OptionIcon } from './dropdown.styles';

type Props = DropdownProps & {
focusIndex: number;
Expand Down Expand Up @@ -94,7 +95,7 @@ export const Options: React.FC<Props> = ({
)}

<LabelContainer>
{option.icon && option.icon}
{option.icon && <OptionIcon>{option.icon}</OptionIcon>}
<div>
<span
dangerouslySetInnerHTML={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,26 @@ export const DropdownWrapper = styled.div`
export const Icon = styled.span`
display: flex;
align-items: center;

width: 16px;
height: 16px;

svg {
width: 16px !important;
height: 16px !important;
}
`;

export const OptionIcon = styled.div`
display: flex;
align-items: center;
justify-content: center;

width: 16px;
height: 16px;

svg {
width: 16px !important;
height: 16px !important;
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type DropdownProps = {
showHints?: boolean;
showSelectedIcon?: boolean;
onChange?: (value: string) => void;
className?: string;
};

export const Dropdown: React.FC<DropdownProps> = ({
Expand All @@ -55,6 +56,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
showHints,
showSelectedIcon,
onChange,
className,
loading = false,
}) => {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -157,7 +159,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
return (
<DropdownWrapper
ref={containerRef}
className={classes(open && 'open')}
className={classes(open && 'open', className)}
onClick={toggleOpen}
>
<CurrentValue
Expand All @@ -174,7 +176,6 @@ export const Dropdown: React.FC<DropdownProps> = ({
),
}}
/>

{loading && (
<SpinnerWrapper>
<SpinnerIcon />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export const EditNotificationModal: React.FC<
</button>
<button className="btn submit" onClick={handleSave}>
<LoadingText
loadingText={translate('Saving')}
loadingText={translate('Saving...')}
loading={mutation.isPending}
spinner
>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading