Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | 62x 62x 8906x 8906x 47416x 47416x 47416x 62x 5413x 5413x 5413x 62x 62x 6795x 6795x 6795x 62x 62x 62x 62x 2330x 2330x 2330x 62x 62x 1398x 1398x 1398x 62x | import * as React from "react";
import { useFlowsheetAccess } from "@/hooks/flowsheetAccess";
import { Button, ButtonProps } from "./button";
import { Checkbox } from "./checkbox";
import { Input, InputProps } from "./input";
import { TabsTrigger, TabsTriggerProps } from "./tabs";
import { Textarea, TextareaProps } from "./textarea";
export type FlowsheetAccessValue = ReturnType<typeof useFlowsheetAccess>;
const MISSING_ACCESS_CONTEXT = Symbol("missing-flowsheet-access-context");
const FlowsheetAccessContext = React.createContext<
FlowsheetAccessValue | null | typeof MISSING_ACCESS_CONTEXT
>(MISSING_ACCESS_CONTEXT);
export interface AccessControlledProps {
/**
* When true, read-only users cannot interact with the control.
* Set to false for allowed read-only actions such as download or copy.
*/
writeAccessOnly?: boolean;
/**
* When true, hide the control entirely instead of rendering it disabled.
*/
hideIfNoAccess?: boolean;
}
export function FlowsheetAccessProvider({
children,
}: {
children: React.ReactNode;
}) {
const access = useFlowsheetAccess();
return (
// `null` means the flowsheet access query is still loading inside a valid
// provider scope; mutation controls should not fail closed in that state.
<FlowsheetAccessContext.Provider value={access ?? null}>
{children}
</FlowsheetAccessContext.Provider>
);
}
export function FlowsheetAccessTestProvider({
access,
children,
}: {
access?: FlowsheetAccessValue;
children: React.ReactNode;
}) {
return (
// Tests can omit `access` to simulate the same in-scope loading state.
<FlowsheetAccessContext.Provider value={access ?? null}>
{children}
</FlowsheetAccessContext.Provider>
);
}
export function useAccessControlledState({
writeAccessOnly = true,
hideIfNoAccess = false,
}: AccessControlledProps) {
const access = React.useContext(FlowsheetAccessContext);
// Fail closed only when a mutation control is rendered outside the flowsheet
// access scope entirely. When the provider is present but the access payload
// is still loading, keep writable flows interactive instead of disabling
// buttons that Playwright and users expect to become clickable immediately.
const lacksWriteAccess = Boolean(
writeAccessOnly &&
(access === MISSING_ACCESS_CONTEXT || access?.can_edit === false),
);
return {
lacksWriteAccess,
shouldHide: hideIfNoAccess && lacksWriteAccess,
};
}
export const AccessControlledButton = React.forwardRef<
HTMLButtonElement,
ButtonProps & AccessControlledProps
>(
(
{ writeAccessOnly = true, hideIfNoAccess = false, disabled, ...props },
ref,
) => {
const { lacksWriteAccess, shouldHide } = useAccessControlledState({
writeAccessOnly,
hideIfNoAccess,
});
Iif (shouldHide) return null;
return (
<Button ref={ref} disabled={disabled || lacksWriteAccess} {...props} />
);
},
);
AccessControlledButton.displayName = "AccessControlledButton";
export const AccessControlledInput = React.forwardRef<
HTMLInputElement,
InputProps & AccessControlledProps
>(
(
{
writeAccessOnly = true,
hideIfNoAccess = false,
disabled,
readOnly,
...props
},
ref,
) => {
const { lacksWriteAccess, shouldHide } = useAccessControlledState({
writeAccessOnly,
hideIfNoAccess,
});
Iif (shouldHide) return null;
return (
<Input
ref={ref}
disabled={disabled || lacksWriteAccess}
readOnly={readOnly || lacksWriteAccess}
{...props}
/>
);
},
);
AccessControlledInput.displayName = "AccessControlledInput";
export const AccessControlledTextarea = React.forwardRef<
HTMLTextAreaElement,
TextareaProps & AccessControlledProps
>(
(
{
writeAccessOnly = true,
hideIfNoAccess = false,
disabled,
readOnly,
...props
},
ref,
) => {
const { lacksWriteAccess, shouldHide } = useAccessControlledState({
writeAccessOnly,
hideIfNoAccess,
});
Iif (shouldHide) return null;
return (
<Textarea
ref={ref}
disabled={disabled || lacksWriteAccess}
readOnly={readOnly || lacksWriteAccess}
{...props}
/>
);
},
);
AccessControlledTextarea.displayName = "AccessControlledTextarea";
export const AccessControlledCheckbox = React.forwardRef<
React.ElementRef<typeof Checkbox>,
React.ComponentPropsWithoutRef<typeof Checkbox> & AccessControlledProps
>(
(
{ writeAccessOnly = true, hideIfNoAccess = false, disabled, ...props },
ref,
) => {
const { lacksWriteAccess, shouldHide } = useAccessControlledState({
writeAccessOnly,
hideIfNoAccess,
});
Iif (shouldHide) return null;
return (
<Checkbox ref={ref} disabled={disabled || lacksWriteAccess} {...props} />
);
},
);
AccessControlledCheckbox.displayName = "AccessControlledCheckbox";
export const AccessControlledTabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsTrigger>,
TabsTriggerProps & AccessControlledProps
>(
(
{ writeAccessOnly = true, hideIfNoAccess = false, disabled, ...props },
ref,
) => {
const { lacksWriteAccess, shouldHide } = useAccessControlledState({
writeAccessOnly,
hideIfNoAccess,
});
Iif (shouldHide) return null;
return (
<TabsTrigger
ref={ref}
disabled={disabled || lacksWriteAccess}
{...props}
/>
);
},
);
AccessControlledTabsTrigger.displayName = "AccessControlledTabsTrigger";
|