-
+
{t("dialog.typeLabel")}
setType(value as SubmittableRightsRequestType)}
>
@@ -173,6 +175,7 @@ function NewRequestForm({ onClose, connectionId, viewerEmail, viewerName }: NewR
setName(e.target.value)}
/>
diff --git a/apps/compliance-portal/src/pages/requests/_lib/rightsRequest.ts b/apps/compliance-portal/src/pages/requests/_lib/rightsRequest.ts
index 7ab96b76a..0894362c2 100644
--- a/apps/compliance-portal/src/pages/requests/_lib/rightsRequest.ts
+++ b/apps/compliance-portal/src/pages/requests/_lib/rightsRequest.ts
@@ -98,7 +98,8 @@ export const rightsRequestFormConfig: Record<
// Human-facing reference derived from the created year and a short suffix of the
// opaque id (display-only; not a stored sequential number).
export function formatRightsRequestReference(id: string, createdAt: string): string {
- const year = new Date(createdAt).getFullYear();
+ // Use UTC so the reference year is stable regardless of the viewer's timezone.
+ const year = new Date(createdAt).getUTCFullYear();
const suffix = id.replace(/[^a-zA-Z0-9]/g, "").slice(-6).toUpperCase();
return `REQ-${year}-${suffix}`;
}
diff --git a/apps/compliance-portal/src/pages/requests/_locales/en-US.json b/apps/compliance-portal/src/pages/requests/_locales/en-US.json
index 7bfda2b61..1dabd1722 100644
--- a/apps/compliance-portal/src/pages/requests/_locales/en-US.json
+++ b/apps/compliance-portal/src/pages/requests/_locales/en-US.json
@@ -35,7 +35,7 @@
"submit": "Submit Request",
"success": {
"title": "Request submitted",
- "description": "We'll process your request and respond within 30 days.",
+ "description": "We'll process your request and respond within the timeframe required by applicable law.",
"close": "Close"
}
},
diff --git a/apps/compliance-portal/src/pages/requests/_locales/fr-FR.json b/apps/compliance-portal/src/pages/requests/_locales/fr-FR.json
index d30617379..ca9fc1cd7 100644
--- a/apps/compliance-portal/src/pages/requests/_locales/fr-FR.json
+++ b/apps/compliance-portal/src/pages/requests/_locales/fr-FR.json
@@ -35,7 +35,7 @@
"submit": "Envoyer la demande",
"success": {
"title": "Demande envoyée",
- "description": "Nous traiterons votre demande et vous répondrons sous 30 jours.",
+ "description": "Nous traiterons votre demande et vous répondrons dans le délai prévu par la loi applicable.",
"close": "Fermer"
}
},
diff --git a/packages/ui/src/v2/SegmentedControl/SegmentedControl.tsx b/packages/ui/src/v2/SegmentedControl/SegmentedControl.tsx
index 796441686..c0ddb8783 100644
--- a/packages/ui/src/v2/SegmentedControl/SegmentedControl.tsx
+++ b/packages/ui/src/v2/SegmentedControl/SegmentedControl.tsx
@@ -19,40 +19,66 @@
// SOFTWARE.
import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
-import type { ReactNode } from "react";
+import { type ReactNode, useState } from "react";
import { segmentedControl } from "./variants";
export type SegmentedControlProps = {
// Single selected value (controlled).
- value?: string;
+ "value"?: string;
// Single selected value (uncontrolled).
- defaultValue?: string;
+ "defaultValue"?: string;
// Fired with the newly selected value. Never fired with an empty selection,
// so a value always stays selected (clicking the active item is a no-op).
- onValueChange?: (value: string) => void;
- disabled?: boolean;
- className?: string;
- children?: ReactNode;
+ "onValueChange"?: (value: string) => void;
+ "disabled"?: boolean;
+ "className"?: string;
+ // Accessible name for the group (or reference a visible label via
+ // `aria-labelledby`), since the control has no intrinsic label.
+ "aria-label"?: string;
+ "aria-labelledby"?: string;
+ "children"?: ReactNode;
};
// Single-select pill group. Wraps Base UI's array-based ToggleGroup with a
-// friendlier single-value API.
+// friendlier single-value API. Selection is tracked internally (seeded from
+// `value`/`defaultValue`) so toggling the active item off — which Base UI
+// reports as an empty group value — never clears the selection.
export function SegmentedControl(props: SegmentedControlProps) {
- const { value, defaultValue, onValueChange, disabled, className, children } = props;
+ const {
+ value,
+ defaultValue,
+ onValueChange,
+ disabled,
+ className,
+ children,
+ "aria-label": ariaLabel,
+ "aria-labelledby": ariaLabelledby,
+ } = props;
const { root } = segmentedControl();
+ const isControlled = value !== undefined;
+ const [internalValue, setInternalValue] = useState(defaultValue);
+ const currentValue = isControlled ? value : internalValue;
+
return (
{
const next = groupValue[0];
- if (next != null) {
- onValueChange?.(next);
+ // Ignore the empty value emitted when the active item is toggled off,
+ // preserving the single-selection contract.
+ if (next == null) {
+ return;
}
+ if (!isControlled) {
+ setInternalValue(next);
+ }
+ onValueChange?.(next);
}}
>
{children}
diff --git a/packages/ui/src/v2/SegmentedControl/variants.ts b/packages/ui/src/v2/SegmentedControl/variants.ts
index 2a1d4c905..8d1a20fa5 100644
--- a/packages/ui/src/v2/SegmentedControl/variants.ts
+++ b/packages/ui/src/v2/SegmentedControl/variants.ts
@@ -28,7 +28,7 @@ import { tv } from "tailwind-variants/lite";
// (the pressed state only darkens the border, so selection never shifts layout).
export const segmentedControl = tv({
slots: {
- root: "grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-1",
+ root: "grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-1",
item: [
"min-w-0 cursor-pointer select-none rounded-3 border border-sand-a6 bg-sand-1 px-4 py-3.5",
"text-center text-2 font-medium text-sand-12 outline-none transition-colors",
diff --git a/packages/ui/src/v2/form/Field.tsx b/packages/ui/src/v2/form/Field.tsx
index 8580e567e..a4aaa63d8 100644
--- a/packages/ui/src/v2/form/Field.tsx
+++ b/packages/ui/src/v2/form/Field.tsx
@@ -18,32 +18,60 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-import type { ReactNode } from "react";
+import { cloneElement, isValidElement, type ReactElement, type ReactNode, useId } from "react";
import { field } from "./variants";
export type FieldProps = {
- // Text shown above the control. The control is nested inside the