@workday/canvas-kit-react
Version:
The parent module that contains all Workday Canvas Kit React components
245 lines (244 loc) • 12 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputGroup = exports.inputGroupStencil = exports.useClearButton = exports.inputGroupInputStencil = exports.useInputGroupInput = exports.inputGroupInnerStencil = exports.useInputGroupModel = void 0;
const react_1 = __importDefault(require("react"));
const canvas_kit_styling_1 = require("@workday/canvas-kit-styling");
const canvas_tokens_web_1 = require("@workday/canvas-tokens-web");
const common_1 = require("@workday/canvas-kit-react/common");
const layout_1 = require("@workday/canvas-kit-react/layout");
const button_1 = require("@workday/canvas-kit-react/button");
const canvas_system_icons_web_1 = require("@workday/canvas-system-icons-web");
const TextInput_1 = require("./TextInput");
exports.useInputGroupModel = (0, common_1.createModelHook)({})(() => {
const inputRef = react_1.default.useRef(null);
return {
state: {
inputRef,
},
events: {},
};
});
exports.inputGroupInnerStencil = (0, canvas_kit_styling_1.createStencil)({
vars: {
/**
* Offset of the inner item. Set by the `InputGroup` and depends on siblings. Do not change this
* on your own.
*/
insetInlineStart: 'initial',
/**
* Offset of the inner item. Set by the `InputGroup` and depends on siblings. Do not change this
* on your own.
*/
insetInlineEnd: 'initial',
width: canvas_tokens_web_1.system.space.x10,
height: canvas_tokens_web_1.system.space.x10,
/**
* Some inner input group elements are decoration only and should not have pointer events
*/
pointerEvents: '',
},
base: { name: "d63q44", styles: "--insetInlineStart-input-group-inner-42c96b:initial;--insetInlineEnd-input-group-inner-42c96b:initial;--width-input-group-inner-42c96b:var(--cnvs-sys-space-x10);--height-input-group-inner-42c96b:var(--cnvs-sys-space-x10);box-sizing:border-box;display:flex;position:absolute;align-items:center;justify-content:center;width:var(--width-input-group-inner-42c96b);height:var(--height-input-group-inner-42c96b);inset-inline-start:var(--insetInlineStart-input-group-inner-42c96b);inset-inline-end:var(--insetInlineEnd-input-group-inner-42c96b);" },
modifiers: {
pointerEvents: {
_: { name: "d63q45", styles: "pointer-events:var(--pointerEvents-input-group-inner-42c96b);" }
}
}
}, "input-group-inner-42c96b");
const InputGroupInnerStart = (0, common_1.createSubcomponent)('div')({
modelHook: exports.useInputGroupModel,
})(({ pointerEvents, insetInlineStart, insetInlineEnd, width, height, ...elemProps }, Element) => {
return (react_1.default.createElement(Element, { ...(0, layout_1.mergeStyles)(elemProps, (0, exports.inputGroupInnerStencil)({
pointerEvents,
insetInlineStart: toPx(insetInlineStart),
insetInlineEnd: toPx(insetInlineEnd),
width: toPx(width),
height: toPx(height),
})) }));
});
const InputGroupInnerEnd = (0, common_1.createSubcomponent)('div')({
modelHook: exports.useInputGroupModel,
})(({ pointerEvents, insetInlineStart, insetInlineEnd, width, height, ...elemProps }, Element) => {
return (react_1.default.createElement(Element, { ...(0, layout_1.mergeStyles)(elemProps, (0, exports.inputGroupInnerStencil)({
pointerEvents,
insetInlineStart: insetInlineStart,
insetInlineEnd: insetInlineEnd,
width: toPx(width),
height: toPx(height),
})) }));
});
exports.useInputGroupInput = (0, common_1.createElemPropsHook)(exports.useInputGroupModel)((model, ref) => {
const elementRef = (0, common_1.useForkRef)(ref, model.state.inputRef);
return {
ref: elementRef,
placeholder: '', // Make sure a placeholder attribute always exists for `:placeholder-shown`
};
});
exports.inputGroupInputStencil = (0, canvas_kit_styling_1.createStencil)({
vars: {
paddingInlineStart: '',
paddingInlineEnd: '',
},
base: { name: "d63q46", styles: "box-sizing:border-box;display:flex;width:100%;" },
modifiers: {
paddingInlineStart: {
_: { name: "d63q47", styles: "padding-inline-start:var(--paddingInlineStart-input-group-input-9155da);" }
},
paddingInlineEnd: {
_: { name: "d63q48", styles: "padding-inline-end:var(--paddingInlineEnd-input-group-input-9155da);" }
}
}
}, "input-group-input-9155da");
const InputGroupInput = (0, common_1.createSubcomponent)(TextInput_1.TextInput)({
modelHook: exports.useInputGroupModel,
elemPropsHook: exports.useInputGroupInput,
})(({ paddingInlineStart, paddingInlineEnd, ...elemProps }, Element) => {
return (react_1.default.createElement(Element, { as: Element, ...(0, layout_1.mergeStyles)(elemProps, (0, exports.inputGroupInputStencil)({
paddingInlineStart: toPx(paddingInlineStart),
paddingInlineEnd: toPx(paddingInlineEnd),
})) }));
});
exports.useClearButton = (0, common_1.createElemPropsHook)(exports.useInputGroupModel)(model => {
return {
// This element does not need to be accessible via screen reader. The user can already clear
// an input
role: 'presentation',
// A clear input button doesn't need focus. There's already keyboard keys to clear an input
tabIndex: -1,
icon: canvas_system_icons_web_1.xSmallIcon,
// "small" is needed to render correctly within a `TextInput`
size: 'small',
// prevent a focus change to the button. Focus should stay in the input.
onMouseDown(event) {
event.preventDefault();
},
onClick() {
// This will clear the input's value
(0, common_1.dispatchInputEvent)(model.state.inputRef.current, '');
},
};
});
/**
* A clear input button. This can be a component later.
*/
const ClearButton = (0, common_1.createSubcomponent)(button_1.TertiaryButton)({
modelHook: exports.useInputGroupModel,
elemPropsHook: exports.useClearButton,
})((elemProps, Element) => {
return react_1.default.createElement(Element, { "data-part": "input-group-clear-button", ...(0, canvas_kit_styling_1.handleCsProp)(elemProps) });
});
// make sure we always use pixels if the input is a number - this is required for `calc`
const toPx = (input) => {
return typeof input === 'number' ? `${input}px` : input;
};
// wrap an array of widths into something the browser can understand, including `calc` for multiple
// values
const wrapInCalc = (values) => {
if (values.length === 0) {
return undefined;
}
if (values.length === 1) {
return values[0];
}
return `calc(${values.map(toPx).join(' + ')})`;
};
exports.inputGroupStencil = (0, canvas_kit_styling_1.createStencil)({
base: { name: "d63q49", styles: "box-sizing:border-box;display:flex;position:relative;& :has([data-part=\"input-group-clear-button\"]){transition:opacity 300ms ease;}&:where(:has(input:placeholder-shown)) :has([data-part=\"input-group-clear-button\"]){opacity:0;pointer-events:none;}" }
}, "input-group-27e30b");
/**
* An `InputGroup` is a container around a {@link TextInput} with optional inner start and end
* elements. The inner start and end elements are usually icons or icon buttons visually represented
* inside the input. The `InputGroup` will add padding to the input so the icons/buttons display
* correctly. This component uses `React.Children.map` and `React.cloneElement` from the
* [React.Children](https://react.dev/reference/react/Children) API. This means all children must be
* `InputGroup.*` components. Any other direct children will cause issues. You can add different
* elements/components inside the {@link InputGroupInnerStart InputGroup.InnerStart} and
* {@link InputGroupInnerEnd InputGroup.InnerEnd} subcomponents.
*
* ```tsx
* <InputGroup>
* <InputGroup.InnerStart as={SystemIcon} pointerEvents="none" icon={searchIcon} />
* <InputGroup.Input />
* <InputGroup.InnerEnd>
* <TertiaryButton tabIndex={-1} icon={xIcon} size="small" />
* </InputGroup.InnerEnd>
* </InputGroup>
* ```
*/
exports.InputGroup = (0, common_1.createContainer)('div')({
displayName: 'InputGroup',
modelHook: exports.useInputGroupModel,
subComponents: {
/**
* A component to show inside and at the start of the input. The input's padding will be
* adjusted by the `InputGroup` to not overlap with this element. Use `width` to adjust the
* width offset. The width defaults to 40px which is the correct width for icons or icon
* buttons.
*/
InnerStart: InputGroupInnerStart,
/**
* The input to render. By default, this is a {@link TextInput}. Use the `as` prop to change the
* component to be rendered.
*/
Input: InputGroupInput,
/**
* A component to show inside and at the end of the input. The input's padding will be adjusted
* by the `InputGroup` to not overlap with this element. Use `width` to adjust the width offset.
* The width defaults to 40px which is the correct width for icons or icon buttons within the
* input.
*/
InnerEnd: InputGroupInnerEnd,
/**
* A component that can be added to an input group that will clear the input. It will only render
* when the input has a value and will fade when a value is entered.
*/
ClearButton: ClearButton,
},
})(({ children, ...elemProps }, Element) => {
const offsetsStart = [];
const offsetsEnd = [];
// Collect the widths of the `InnerStart` and `InnerEnd` components into `offsetStart` and
// `offsetEnd` arrays
react_1.default.Children.forEach(children, child => {
if (react_1.default.isValidElement(child) && child.type === InputGroupInnerStart) {
const width = (0, canvas_kit_styling_1.wrapProperty)(child.props.width || canvas_tokens_web_1.system.space.x10);
offsetsStart.push(width);
}
if (react_1.default.isValidElement(child) && child.type === InputGroupInnerEnd) {
const width = (0, canvas_kit_styling_1.wrapProperty)(child.props.width || canvas_tokens_web_1.system.space.x10);
offsetsEnd.push(width);
}
});
// keep track of the index offsets to make sure we calculate the correct position offset
let indexStart = 0;
let indexEnd = 0;
// Loop over all the children and set the correct padding and positions
const mappedChildren = react_1.default.Children.map(children, child => {
if (react_1.default.isValidElement(child)) {
if (child.type === InputGroupInput) {
return react_1.default.cloneElement(child, {
paddingInlineStart: wrapInCalc(offsetsStart),
paddingInlineEnd: wrapInCalc(offsetsEnd),
});
}
if (child.type === InputGroupInnerStart) {
const offset = wrapInCalc(offsetsStart.slice(0, indexStart)) || '0px';
indexStart++;
return react_1.default.cloneElement(child, {
insetInlineStart: offset,
});
}
if (child.type === InputGroupInnerEnd) {
const offset = wrapInCalc(offsetsEnd.slice(indexEnd + 1, offsetsEnd.length)) || '0px';
indexEnd++;
return react_1.default.cloneElement(child, {
insetInlineEnd: offset,
});
}
}
return child;
});
return react_1.default.createElement(Element, { ...(0, layout_1.mergeStyles)(elemProps, (0, exports.inputGroupStencil)()) }, mappedChildren);
});