Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tag component + InputField story using it #28

Merged
merged 21 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
2 changes: 1 addition & 1 deletion src/components/forms/controls/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type RadioProps = ComponentProps<'input'> & {
unstyled?: undefined | boolean,
};
/**
* A simple Radio control, just the &lt;input type="radio"&gt; and nothing else..
* A simple Radio control, just the &lt;input type="radio"&gt; and nothing else.
*/
export const Radio = (props: RadioProps) => {
const {
Expand Down
24 changes: 21 additions & 3 deletions src/components/forms/fields/InputField/InputField.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,36 @@
@layer baklava.components {
.bk-input-field {
@include bk.component-base(bk-input-field);

display: flex;
flex-direction: column;
gap: 6px;

.bk-input-field__label {
@include bk.font(bk.$font-family-body, bk.$font-weight-semibold);
cursor: default;
}

.bk-input-field__control {
--empty: ; // Prevent empty class from being removed
}
}

.bk-input-field__tags-and-input {
display: flex;
flex-direction: row;
gap: bk.$spacing-2;
}

.bk-input-field--with-tags {
border-bottom: 1px solid bk.$theme-form-rule-default;

&:focus-within {
border-bottom-color: bk.$theme-form-rule-focused;

input {
outline: none !important;
}
}
}
}
31 changes: 31 additions & 0 deletions src/components/forms/fields/InputField/InputField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,34 @@ export const InvalidInput: Story = {
await fireEvent.submit(input.closest('form')!);
},
};

export const InputWithTags: Story = {
name: 'Input with tags (enter creates new tag, backspace erases tags)',
render: () => {
const [tags, setTags] = React.useState<Array<string>>(['Tag Title', 'Tag Title 2']);
const [inputText, setInputText] = React.useState<string>('Example');

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputText(e.target.value);
};
const onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Backspace' && inputText === '') {
setTags(tags.slice(0,-1));
}
if (e.key === 'Enter' && inputText !== '') {
setTags([...tags, inputText]);
setInputText('');
}
};

return (
<InputField
tags={tags}
value={inputText}
onKeyUp={onKeyUp}
onChange={onChange}
placeholder=""
/>
);
}
};
40 changes: 27 additions & 13 deletions src/components/forms/fields/InputField/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

import { classNames as cx, type ComponentProps } from '../../../../util/componentUtil.ts';
import * as React from 'react';
import { useFormStatus } from 'react-dom';

import { useFormContext } from '../../context/Form/Form.tsx';
import { Input } from '../../controls/Input/Input.tsx';
import { Tag } from '../../../text/Tag/Tag.tsx';

import cl from './InputField.module.scss';

Expand All @@ -17,15 +17,18 @@ export { cl as InputFieldClassNames };
export type InputFieldProps = ComponentProps<'input'> & {
/** Whether this component should be unstyled. */
unstyled?: undefined | boolean,

/** Label for the input. */
label?: undefined | React.ReactNode,

/** Props for the `<label>` element, if `label` is defined. */
labelProps?: undefined | ComponentProps<'label'>,

/** Props for the wrapper element. */
wrapperProps?: undefined | ComponentProps<'div'>,

/** Tags to be displayed inside the input field */
tags?: undefined | string[],
};
/**
* Input field.
Expand All @@ -36,19 +39,25 @@ export const InputField = (props: InputFieldProps) => {
label,
labelProps = {},
wrapperProps = {},
tags = [],
...inputProps
} = props;

const controlId = React.useId();
const formContext = useFormContext();
//const formStatus = useFormStatus();


const injectedInputProps = {
...inputProps,
unstyled: tags && tags.length > 0,
};

return (
<div
{...wrapperProps}
className={cx(
'bk',
{ [cl['bk-input-field']]: !unstyled },
{ [cl['bk-input-field--with-tags']]: tags && tags.length > 0 },
wrapperProps.className,
)}
>
Expand All @@ -61,12 +70,17 @@ export const InputField = (props: InputFieldProps) => {
{label}
</label>
}
<Input
{...inputProps}
id={controlId}
form={formContext.formId}
className={cx(cl['bk-input-field__control'], inputProps.className)}
/>
<div className={cl['bk-input-field__tags-and-input']}>
{tags && (
tags.map((tag, idx) => <Tag key={idx} content={tag}/>)
)}
<Input
{...injectedInputProps}
id={controlId}
form={formContext.formId}
className={cx(cl['bk-input-field__control'], inputProps.className)}
/>
</div>
</div>
);
};
29 changes: 29 additions & 0 deletions src/components/text/Tag/Tag.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Copyright (c) Fortanix, Inc.
|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@use '../../../styling/defs.scss' as bk;

@layer baklava.components {
.bk-tag {
@include bk.component-base(bk-tag);

background: bk.$theme-tag-background-default;
border-radius: bk.$size-2;
color: bk.$theme-tag-text-default;
display: flex;
align-items: center;
font-size: bk.$font-size-xs;
padding: bk.$size-2 0 bk.$size-3 bk.$spacing-2;

.bk-tag__icon {
--icon-size: 7px;

width: var(--icon-size);
height: var(--icon-size);
color: bk.$theme-tag-icon-default;
cursor: pointer;
padding: bk.$size-2 bk.$spacing-2 bk.$size-2 bk.$spacing-2;
}
}
}
32 changes: 32 additions & 0 deletions src/components/text/Tag/Tag.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* Copyright (c) Fortanix, Inc.
|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import type { Meta, StoryObj } from '@storybook/react';

import * as React from 'react';

import { Tag } from './Tag.tsx';


type TagArgs = React.ComponentProps<typeof Tag>;
type Story = StoryObj<TagArgs>;

export default {
component: Tag,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
},
args: {
content: 'Tag Title',
},
render: (args) => <Tag {...args}/>,
} satisfies Meta<TagArgs>;


export const TagStory: Story = {
name: 'Tag',
};
46 changes: 46 additions & 0 deletions src/components/text/Tag/Tag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* Copyright (c) Fortanix, Inc.
|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { classNames as cx, type ComponentProps } from '../../../util/componentUtil.ts';
import * as React from 'react';

import { Icon } from '../../graphics/Icon/Icon.tsx';

import cl from './Tag.module.scss';


export { cl as TagClassNames };

export type TagProps = ComponentProps<'div'> & {
/** Whether this component should be unstyled. */
unstyled?: undefined | boolean,

/** Some content to be displayed inside the tag, either as string or JSX. */
content: string | React.ReactNode,
nighto marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* A tag component.
*/
export const Tag = (props: TagProps) => {
const {
unstyled = false,
content = '',
nighto marked this conversation as resolved.
Show resolved Hide resolved
...propsRest
} = props;

return (
<div
{...propsRest}
className={cx(
'bk',
{ [cl['bk-tag']]: !unstyled },
propsRest.className,
)}
>
{content}
<Icon icon="cross" className={cl['bk-tag__icon']} />
nighto marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};
2 changes: 2 additions & 0 deletions src/styling/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,10 @@ $spacing-16: math.div(192, 14) * 1rem !default; // ~192px
$spacing-17: math.div(224, 14) * 1rem !default; // ~224px
$spacing-18: math.div(256, 14) * 1rem !default; // ~256px

// these sizes do not match Figma variables
$size-1: math.div(1, 14) * 1rem !default; // ~1px
$size-2: math.div(2, 14) * 1rem !default; // ~2px
$size-3: math.div(3, 14) * 1rem !default; // ~3px


//
Expand Down