From 35fd51dc33ebca8f37ef8cd752d5cf0dbfab5e94 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 23:06:34 +0100 Subject: [PATCH] Add new FieldLabel component Signed-off-by: Charles de Dreuille --- .changeset/khaki-waves-lick.md | 5 + packages/canon/css/components.css | 1053 ++++++++--------- packages/canon/css/fieldlabel.css | 27 + packages/canon/css/styles.css | 1053 ++++++++--------- packages/canon/css/textfield.css | 149 +-- packages/canon/report.api.md | 40 +- .../FieldLabel/FieldLabel.stories.tsx | 65 + .../FieldLabel/FieldLabel.styles.css | 27 + .../src/components/FieldLabel/FieldLabel.tsx | 50 + .../{FormInput => FieldLabel}/index.ts | 4 +- .../{FormInput => FieldLabel}/types.ts | 20 +- .../FormInput/FormInput.stories.tsx | 87 -- .../components/FormInput/FormInput.styles.css | 91 -- .../src/components/FormInput/FormInput.tsx | 48 - .../components/TextField/TextField.styles.css | 28 - .../src/components/TextField/TextField.tsx | 27 +- .../canon/src/components/TextField/index.ts | 2 +- .../canon/src/components/TextField/types.ts | 25 +- packages/canon/src/css/components.css | 14 +- packages/canon/src/index.ts | 1 + packages/canon/src/stories/Form.stories.tsx | 195 --- 21 files changed, 1258 insertions(+), 1753 deletions(-) create mode 100644 .changeset/khaki-waves-lick.md create mode 100644 packages/canon/css/fieldlabel.css create mode 100644 packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx create mode 100644 packages/canon/src/components/FieldLabel/FieldLabel.styles.css create mode 100644 packages/canon/src/components/FieldLabel/FieldLabel.tsx rename packages/canon/src/components/{FormInput => FieldLabel}/index.ts (88%) rename packages/canon/src/components/{FormInput => FieldLabel}/types.ts (61%) delete mode 100644 packages/canon/src/components/FormInput/FormInput.stories.tsx delete mode 100644 packages/canon/src/components/FormInput/FormInput.styles.css delete mode 100644 packages/canon/src/components/FormInput/FormInput.tsx delete mode 100644 packages/canon/src/stories/Form.stories.tsx diff --git a/.changeset/khaki-waves-lick.md b/.changeset/khaki-waves-lick.md new file mode 100644 index 0000000000..a299424170 --- /dev/null +++ b/.changeset/khaki-waves-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +Move TextField component to use react Aria under the hood. Introducing a new FieldLabel component to help build custom fields. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 1b60bf74ed..8d133abfb0 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -144,80 +144,6 @@ height: 1.25rem; } -.canon-CollapsiblePanel { - height: var(--collapsible-panel-height); - transition: all .15s ease-out; - display: flex; - overflow: hidden; - - &[data-starting-style], &[data-ending-style] { - height: 0; - } -} - -.canon-DataTableRoot { - gap: var(--canon-space-3); - flex-direction: column; - display: flex; -} - -.canon-DataTablePagination { - padding-top: var(--canon-space-5); - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--left { - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--right { - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - display: flex; -} - -.canon-DataTablePagination--select { - min-width: 10.5rem; -} - -.canon-Flex { - min-width: 0; - display: flex; -} - -.canon-Grid { - display: grid; -} - -.canon-Container { - max-width: 120rem; - padding-inline: var(--canon-space-4); - margin-inline: auto; - transition: padding .2s ease-in-out; -} - -@media (width >= 640px) { - .canon-Container { - padding-inline: var(--canon-space-8); - } -} - -@media (width >= 1024px) { - .canon-Container { - padding-inline: var(--canon-space-12); - } -} - -.canon-Icon { - width: 1rem; - height: 1rem; -} - .canon-CheckboxRoot { width: 1rem; height: 1rem; @@ -271,6 +197,474 @@ display: flex; } +.canon-CollapsiblePanel { + height: var(--collapsible-panel-height); + transition: all .15s ease-out; + display: flex; + overflow: hidden; + + &[data-starting-style], &[data-ending-style] { + height: 0; + } +} + +.canon-Container { + max-width: 120rem; + padding-inline: var(--canon-space-4); + margin-inline: auto; + transition: padding .2s ease-in-out; +} + +@media (width >= 640px) { + .canon-Container { + padding-inline: var(--canon-space-8); + } +} + +@media (width >= 1024px) { + .canon-Container { + padding-inline: var(--canon-space-12); + } +} + +.canon-DataTableRoot { + gap: var(--canon-space-3); + flex-direction: column; + display: flex; +} + +.canon-DataTablePagination { + padding-top: var(--canon-space-5); + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--left { + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--right { + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + display: flex; +} + +.canon-DataTablePagination--select { + min-width: 10.5rem; +} + +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + margin-right: auto; +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} + +.canon-Flex { + min-width: 0; + display: flex; +} + +.canon-Grid { + display: grid; +} + +.canon-Heading { + font-family: var(--canon-font-regular); + color: var(--canon-fg-primary); + margin: 0; + padding: 0; + line-height: 100%; +} + +.canon-Heading[data-variant="display"] { + font-size: var(--canon-font-size-10); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title1"] { + font-size: var(--canon-font-size-9); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title2"] { + font-size: var(--canon-font-size-8); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title3"] { + font-size: var(--canon-font-size-7); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title4"] { + font-size: var(--canon-font-size-6); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title5"] { + font-size: var(--canon-font-size-5); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-truncate] { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.canon-Icon { + width: 1rem; + height: 1rem; +} + +.canon-IconButton { + user-select: none; + font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-bold); + cursor: pointer; + border-radius: var(--canon-radius-2); + justify-content: center; + align-items: center; + gap: var(--canon-space-1_5); + border: none; + padding: 0; + display: inline-flex; + + &:disabled { + cursor: not-allowed; + } +} + +.canon-IconButton[data-variant="primary"] { + background-color: var(--canon-bg-solid); + color: var(--canon-fg-solid); + transition: background-color .15s, box-shadow .15s; + + &:hover { + background-color: var(--canon-bg-solid-hover); + } + + &:active { + background-color: var(--canon-bg-solid-pressed); + } + + &:focus-visible { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &:disabled { + background-color: var(--canon-bg-solid-disabled); + color: var(--canon-fg-solid-disabled); + } +} + +.canon-IconButton[data-variant="secondary"] { + background-color: var(--canon-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--canon-border); + color: var(--canon-fg-primary); + transition: box-shadow .15s; + + &:hover { + box-shadow: inset 0 0 0 1px var(--canon-border-hover); + } + + &:active { + box-shadow: inset 0 0 0 1px var(--canon-border-pressed); + } + + &:focus-visible { + box-shadow: inset 0 0 0 2px var(--canon-ring); + outline: none; + transition: none; + } + + &:disabled { + box-shadow: inset 0 0 0 1px var(--canon-border-disabled); + color: var(--canon-fg-disabled); + } +} + +.canon-IconButton[data-size="medium"] { + font-size: var(--canon-font-size-4); + width: 40px; + height: 40px; +} + +.canon-IconButton[data-size="small"] { + font-size: var(--canon-font-size-3); + width: 32px; + height: 32px; +} + +.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { + width: 1rem; + height: 1rem; +} + +.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { + width: 1.25rem; + height: 1.25rem; +} + +.canon-Link { + font-family: var(--canon-font-regular); + color: var(--canon-fg-link); + cursor: pointer; + margin: 0; + padding: 0; + text-decoration-line: none; + + &:hover { + color: var(--canon-fg-link-hover); + text-underline-offset: calc(.025em + 2px); + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, .05em)); + text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); + } +} + +.canon-Link[data-variant="body"] { + font-size: var(--canon-font-size-3); + line-height: 140%; +} + +.canon-Link[data-variant="subtitle"] { + font-size: var(--canon-font-size-4); + line-height: 140%; +} + +.canon-Link[data-variant="caption"] { + font-size: var(--canon-font-size-2); + line-height: 140%; +} + +.canon-Link[data-variant="label"] { + font-size: var(--canon-font-size-1); + line-height: 140%; +} + +.canon-Link[data-weight="regular"] { + font-weight: var(--canon-font-weight-regular); +} + +.canon-Link[data-weight="bold"] { + font-weight: var(--canon-font-weight-bold); +} + +.canon-MenuPositioner { + outline: 0; +} + +.canon-MenuPopup { + background-color: var(--canon-bg-surface-1); + border: 1px solid var(--canon-border); + color: var(--canon-fg-primary); + transform-origin: var(--transform-origin); + max-width: min(var(--available-width), 340px); + max-height: min(var(--available-height), 500px); + padding-bottom: var(--canon-space-1); + border-radius: .375rem; + outline: none; + flex-direction: column; + transition: transform .15s, opacity .15s; + display: flex; + position: relative; + overflow: auto; + + &[data-starting-style], &[data-ending-style] { + opacity: 0; + transform: scale(.9); + } +} + +.canon-MenuItem { + user-select: none; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } +} + +.canon-MenuSubmenuTrigger { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + & .canon-Icon { + color: var(--canon-fg-secondary); + } + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-popup-open], &[data-highlighted] { + background-color: var(--canon-gray-3); + + & .canon-Icon { + color: var(--canon-fg-primary); + } + } +} + +.canon-MenuSeparator { + background-color: var(--color-gray-200); + height: 1px; + margin: .375rem 1rem; +} + +.canon-SubmenuComboboxSearch { + padding-inline: var(--canon-space-3); + border: none; + border-bottom: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); + height: 32px; + color: var(--canon-fg-primary); + line-height: 140%; + font-size: var(--canon-font-size-3); + z-index: 1; + outline: none; + position: sticky; + top: 0; + + &::placeholder { + color: var(--canon-fg-secondary); + } + + &:disabled { + opacity: .6; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItems { + padding-top: var(--canon-space-2); + outline: none; + flex-direction: column; + display: flex; + overflow-y: auto; +} + +.canon-SubmenuComboboxNoResults { + padding-inline: var(--canon-space-3); + padding-top: var(--canon-space-2); + padding-bottom: var(--canon-space-4); + color: var(--canon-fg-secondary); + font-size: var(--canon-font-size-3); +} + +.canon-SubmenuComboboxItem { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } + + &[data-disabled] { + opacity: .5; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItemCheckbox { + width: 16px; + height: 16px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + border: 1px solid var(--canon-border); + background: var(--canon-bg-surface-1); + flex-shrink: 0; + justify-content: center; + align-items: center; + display: flex; +} + +.canon-SubmenuComboboxItemLabel { + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + overflow: hidden; +} + .canon-TableRoot { caption-side: bottom; border-collapse: collapse; @@ -487,148 +881,6 @@ overflow: hidden; } -.canon-Heading { - font-family: var(--canon-font-regular); - color: var(--canon-fg-primary); - margin: 0; - padding: 0; - line-height: 100%; -} - -.canon-Heading[data-variant="display"] { - font-size: var(--canon-font-size-10); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title1"] { - font-size: var(--canon-font-size-9); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title2"] { - font-size: var(--canon-font-size-8); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title3"] { - font-size: var(--canon-font-size-7); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title4"] { - font-size: var(--canon-font-size-6); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title5"] { - font-size: var(--canon-font-size-5); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-color="primary"] { - color: var(--canon-fg-primary); -} - -.canon-Heading[data-color="secondary"] { - color: var(--canon-fg-secondary); -} - -.canon-Heading[data-truncate] { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.canon-IconButton { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size="medium"] { - font-size: var(--canon-font-size-4); - width: 40px; - height: 40px; -} - -.canon-IconButton[data-size="small"] { - font-size: var(--canon-font-size-3); - width: 32px; - height: 32px; -} - -.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - .canon-TextField { font-family: var(--canon-font-regular); flex-direction: column; @@ -636,86 +888,26 @@ display: flex; } -.canon-TextFieldLabelWrapper { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-TextFieldLabelWrapper[data-hidden] { - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - position: absolute; - overflow: hidden; -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - cursor: pointer; - margin-right: auto; -} - -.canon-TextFieldLabel[data-size="small"] { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldLabel[data-size="medium"] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-TextFieldDescription[data-size="small"] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size="medium"] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - .canon-TextFieldInputWrapper { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - align-items: center; - display: flex; + position: relative; +} + +.canon-TextFieldInputWrapper[data-size="small"] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size="medium"] { + height: 2.5rem; } .canon-TextFieldIcon { + left: var(--canon-space-3); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); } .canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { @@ -729,6 +921,10 @@ } .canon-TextFieldInput { + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -736,299 +932,48 @@ width: 100%; height: 100%; cursor: inherit; - background: none; - border: none; - padding: 0; + align-items: center; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; -} - -.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - margin-left: var(--canon-space-1); - vertical-align: middle; - color: var(--canon-fg-primary); - background: none; - border: none; - padding: 0; - display: none; -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; + display: flex; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; -} - -.canon-MenuPositioner { - outline: 0; -} - -.canon-MenuPopup { - background-color: var(--canon-bg-surface-1); - border: 1px solid var(--canon-border); - color: var(--canon-fg-primary); - transform-origin: var(--transform-origin); - max-width: min(var(--available-width), 340px); - max-height: min(var(--available-height), 500px); - padding-bottom: var(--canon-space-1); - border-radius: .375rem; - outline: none; - flex-direction: column; - transition: transform .15s, opacity .15s; - display: flex; - position: relative; - overflow: auto; - - &[data-starting-style], &[data-ending-style] { - opacity: 0; - transform: scale(.9); - } -} - -.canon-MenuItem { - user-select: none; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } -} - -.canon-MenuSubmenuTrigger { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - & .canon-Icon { - color: var(--canon-fg-secondary); - } - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-popup-open], &[data-highlighted] { - background-color: var(--canon-gray-3); - - & .canon-Icon { - color: var(--canon-fg-primary); - } - } -} - -.canon-MenuSeparator { - background-color: var(--color-gray-200); - height: 1px; - margin: .375rem 1rem; -} - -.canon-SubmenuComboboxSearch { - padding-inline: var(--canon-space-3); - border: none; - border-bottom: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - height: 32px; - color: var(--canon-fg-primary); - line-height: 140%; - font-size: var(--canon-font-size-3); - z-index: 1; - outline: none; - position: sticky; - top: 0; - - &::placeholder { - color: var(--canon-fg-secondary); - } - - &:disabled { - opacity: .6; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItems { - padding-top: var(--canon-space-2); - outline: none; - flex-direction: column; - display: flex; - overflow-y: auto; -} - -.canon-SubmenuComboboxNoResults { - padding-inline: var(--canon-space-3); - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-4); - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-3); -} - -.canon-SubmenuComboboxItem { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } - - &[data-disabled] { - opacity: .5; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItemCheckbox { - width: 16px; - height: 16px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - border: 1px solid var(--canon-border); - background: var(--canon-bg-surface-1); - flex-shrink: 0; - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SubmenuComboboxItemLabel { - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - overflow: hidden; -} - -.canon-Link { - font-family: var(--canon-font-regular); - color: var(--canon-fg-link); - cursor: pointer; - margin: 0; - padding: 0; - text-decoration-line: none; - - &:hover { - color: var(--canon-fg-link-hover); - text-underline-offset: calc(.025em + 2px); - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-thickness: min(2px, max(1px, .05em)); - text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); - } -} - -.canon-Link[data-variant="body"] { - font-size: var(--canon-font-size-3); - line-height: 140%; -} - -.canon-Link[data-variant="subtitle"] { - font-size: var(--canon-font-size-4); - line-height: 140%; -} - -.canon-Link[data-variant="caption"] { +.canon-TextFieldError { + color: var(--canon-fg-danger); font-size: var(--canon-font-size-2); - line-height: 140%; -} - -.canon-Link[data-variant="label"] { - font-size: var(--canon-font-size-1); - line-height: 140%; -} - -.canon-Link[data-weight="regular"] { font-weight: var(--canon-font-weight-regular); -} - -.canon-Link[data-weight="bold"] { - font-weight: var(--canon-font-weight-bold); + margin-top: var(--canon-space-2); } .canon-TooltipPopup { diff --git a/packages/canon/css/fieldlabel.css b/packages/canon/css/fieldlabel.css new file mode 100644 index 0000000000..e8369ae92e --- /dev/null +++ b/packages/canon/css/fieldlabel.css @@ -0,0 +1,27 @@ +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + margin-right: auto; +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 83329e817c..d6576ddb42 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9368,80 +9368,6 @@ height: 1.25rem; } -.canon-CollapsiblePanel { - height: var(--collapsible-panel-height); - transition: all .15s ease-out; - display: flex; - overflow: hidden; - - &[data-starting-style], &[data-ending-style] { - height: 0; - } -} - -.canon-DataTableRoot { - gap: var(--canon-space-3); - flex-direction: column; - display: flex; -} - -.canon-DataTablePagination { - padding-top: var(--canon-space-5); - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--left { - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--right { - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - display: flex; -} - -.canon-DataTablePagination--select { - min-width: 10.5rem; -} - -.canon-Flex { - min-width: 0; - display: flex; -} - -.canon-Grid { - display: grid; -} - -.canon-Container { - max-width: 120rem; - padding-inline: var(--canon-space-4); - margin-inline: auto; - transition: padding .2s ease-in-out; -} - -@media (width >= 640px) { - .canon-Container { - padding-inline: var(--canon-space-8); - } -} - -@media (width >= 1024px) { - .canon-Container { - padding-inline: var(--canon-space-12); - } -} - -.canon-Icon { - width: 1rem; - height: 1rem; -} - .canon-CheckboxRoot { width: 1rem; height: 1rem; @@ -9495,6 +9421,474 @@ display: flex; } +.canon-CollapsiblePanel { + height: var(--collapsible-panel-height); + transition: all .15s ease-out; + display: flex; + overflow: hidden; + + &[data-starting-style], &[data-ending-style] { + height: 0; + } +} + +.canon-Container { + max-width: 120rem; + padding-inline: var(--canon-space-4); + margin-inline: auto; + transition: padding .2s ease-in-out; +} + +@media (width >= 640px) { + .canon-Container { + padding-inline: var(--canon-space-8); + } +} + +@media (width >= 1024px) { + .canon-Container { + padding-inline: var(--canon-space-12); + } +} + +.canon-DataTableRoot { + gap: var(--canon-space-3); + flex-direction: column; + display: flex; +} + +.canon-DataTablePagination { + padding-top: var(--canon-space-5); + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--left { + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--right { + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + display: flex; +} + +.canon-DataTablePagination--select { + min-width: 10.5rem; +} + +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + margin-right: auto; +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} + +.canon-Flex { + min-width: 0; + display: flex; +} + +.canon-Grid { + display: grid; +} + +.canon-Heading { + font-family: var(--canon-font-regular); + color: var(--canon-fg-primary); + margin: 0; + padding: 0; + line-height: 100%; +} + +.canon-Heading[data-variant="display"] { + font-size: var(--canon-font-size-10); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title1"] { + font-size: var(--canon-font-size-9); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title2"] { + font-size: var(--canon-font-size-8); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title3"] { + font-size: var(--canon-font-size-7); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title4"] { + font-size: var(--canon-font-size-6); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title5"] { + font-size: var(--canon-font-size-5); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-truncate] { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.canon-Icon { + width: 1rem; + height: 1rem; +} + +.canon-IconButton { + user-select: none; + font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-bold); + cursor: pointer; + border-radius: var(--canon-radius-2); + justify-content: center; + align-items: center; + gap: var(--canon-space-1_5); + border: none; + padding: 0; + display: inline-flex; + + &:disabled { + cursor: not-allowed; + } +} + +.canon-IconButton[data-variant="primary"] { + background-color: var(--canon-bg-solid); + color: var(--canon-fg-solid); + transition: background-color .15s, box-shadow .15s; + + &:hover { + background-color: var(--canon-bg-solid-hover); + } + + &:active { + background-color: var(--canon-bg-solid-pressed); + } + + &:focus-visible { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &:disabled { + background-color: var(--canon-bg-solid-disabled); + color: var(--canon-fg-solid-disabled); + } +} + +.canon-IconButton[data-variant="secondary"] { + background-color: var(--canon-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--canon-border); + color: var(--canon-fg-primary); + transition: box-shadow .15s; + + &:hover { + box-shadow: inset 0 0 0 1px var(--canon-border-hover); + } + + &:active { + box-shadow: inset 0 0 0 1px var(--canon-border-pressed); + } + + &:focus-visible { + box-shadow: inset 0 0 0 2px var(--canon-ring); + outline: none; + transition: none; + } + + &:disabled { + box-shadow: inset 0 0 0 1px var(--canon-border-disabled); + color: var(--canon-fg-disabled); + } +} + +.canon-IconButton[data-size="medium"] { + font-size: var(--canon-font-size-4); + width: 40px; + height: 40px; +} + +.canon-IconButton[data-size="small"] { + font-size: var(--canon-font-size-3); + width: 32px; + height: 32px; +} + +.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { + width: 1rem; + height: 1rem; +} + +.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { + width: 1.25rem; + height: 1.25rem; +} + +.canon-Link { + font-family: var(--canon-font-regular); + color: var(--canon-fg-link); + cursor: pointer; + margin: 0; + padding: 0; + text-decoration-line: none; + + &:hover { + color: var(--canon-fg-link-hover); + text-underline-offset: calc(.025em + 2px); + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, .05em)); + text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); + } +} + +.canon-Link[data-variant="body"] { + font-size: var(--canon-font-size-3); + line-height: 140%; +} + +.canon-Link[data-variant="subtitle"] { + font-size: var(--canon-font-size-4); + line-height: 140%; +} + +.canon-Link[data-variant="caption"] { + font-size: var(--canon-font-size-2); + line-height: 140%; +} + +.canon-Link[data-variant="label"] { + font-size: var(--canon-font-size-1); + line-height: 140%; +} + +.canon-Link[data-weight="regular"] { + font-weight: var(--canon-font-weight-regular); +} + +.canon-Link[data-weight="bold"] { + font-weight: var(--canon-font-weight-bold); +} + +.canon-MenuPositioner { + outline: 0; +} + +.canon-MenuPopup { + background-color: var(--canon-bg-surface-1); + border: 1px solid var(--canon-border); + color: var(--canon-fg-primary); + transform-origin: var(--transform-origin); + max-width: min(var(--available-width), 340px); + max-height: min(var(--available-height), 500px); + padding-bottom: var(--canon-space-1); + border-radius: .375rem; + outline: none; + flex-direction: column; + transition: transform .15s, opacity .15s; + display: flex; + position: relative; + overflow: auto; + + &[data-starting-style], &[data-ending-style] { + opacity: 0; + transform: scale(.9); + } +} + +.canon-MenuItem { + user-select: none; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } +} + +.canon-MenuSubmenuTrigger { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + & .canon-Icon { + color: var(--canon-fg-secondary); + } + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-popup-open], &[data-highlighted] { + background-color: var(--canon-gray-3); + + & .canon-Icon { + color: var(--canon-fg-primary); + } + } +} + +.canon-MenuSeparator { + background-color: var(--color-gray-200); + height: 1px; + margin: .375rem 1rem; +} + +.canon-SubmenuComboboxSearch { + padding-inline: var(--canon-space-3); + border: none; + border-bottom: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); + height: 32px; + color: var(--canon-fg-primary); + line-height: 140%; + font-size: var(--canon-font-size-3); + z-index: 1; + outline: none; + position: sticky; + top: 0; + + &::placeholder { + color: var(--canon-fg-secondary); + } + + &:disabled { + opacity: .6; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItems { + padding-top: var(--canon-space-2); + outline: none; + flex-direction: column; + display: flex; + overflow-y: auto; +} + +.canon-SubmenuComboboxNoResults { + padding-inline: var(--canon-space-3); + padding-top: var(--canon-space-2); + padding-bottom: var(--canon-space-4); + color: var(--canon-fg-secondary); + font-size: var(--canon-font-size-3); +} + +.canon-SubmenuComboboxItem { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } + + &[data-disabled] { + opacity: .5; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItemCheckbox { + width: 16px; + height: 16px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + border: 1px solid var(--canon-border); + background: var(--canon-bg-surface-1); + flex-shrink: 0; + justify-content: center; + align-items: center; + display: flex; +} + +.canon-SubmenuComboboxItemLabel { + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + overflow: hidden; +} + .canon-TableRoot { caption-side: bottom; border-collapse: collapse; @@ -9711,148 +10105,6 @@ overflow: hidden; } -.canon-Heading { - font-family: var(--canon-font-regular); - color: var(--canon-fg-primary); - margin: 0; - padding: 0; - line-height: 100%; -} - -.canon-Heading[data-variant="display"] { - font-size: var(--canon-font-size-10); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title1"] { - font-size: var(--canon-font-size-9); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title2"] { - font-size: var(--canon-font-size-8); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title3"] { - font-size: var(--canon-font-size-7); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title4"] { - font-size: var(--canon-font-size-6); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title5"] { - font-size: var(--canon-font-size-5); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-color="primary"] { - color: var(--canon-fg-primary); -} - -.canon-Heading[data-color="secondary"] { - color: var(--canon-fg-secondary); -} - -.canon-Heading[data-truncate] { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.canon-IconButton { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size="medium"] { - font-size: var(--canon-font-size-4); - width: 40px; - height: 40px; -} - -.canon-IconButton[data-size="small"] { - font-size: var(--canon-font-size-3); - width: 32px; - height: 32px; -} - -.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - .canon-TextField { font-family: var(--canon-font-regular); flex-direction: column; @@ -9860,86 +10112,26 @@ display: flex; } -.canon-TextFieldLabelWrapper { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-TextFieldLabelWrapper[data-hidden] { - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - position: absolute; - overflow: hidden; -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - cursor: pointer; - margin-right: auto; -} - -.canon-TextFieldLabel[data-size="small"] { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldLabel[data-size="medium"] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-TextFieldDescription[data-size="small"] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size="medium"] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - .canon-TextFieldInputWrapper { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - align-items: center; - display: flex; + position: relative; +} + +.canon-TextFieldInputWrapper[data-size="small"] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size="medium"] { + height: 2.5rem; } .canon-TextFieldIcon { + left: var(--canon-space-3); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); } .canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { @@ -9953,6 +10145,10 @@ } .canon-TextFieldInput { + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -9960,299 +10156,48 @@ width: 100%; height: 100%; cursor: inherit; - background: none; - border: none; - padding: 0; + align-items: center; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; -} - -.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - margin-left: var(--canon-space-1); - vertical-align: middle; - color: var(--canon-fg-primary); - background: none; - border: none; - padding: 0; - display: none; -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; + display: flex; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; -} - -.canon-MenuPositioner { - outline: 0; -} - -.canon-MenuPopup { - background-color: var(--canon-bg-surface-1); - border: 1px solid var(--canon-border); - color: var(--canon-fg-primary); - transform-origin: var(--transform-origin); - max-width: min(var(--available-width), 340px); - max-height: min(var(--available-height), 500px); - padding-bottom: var(--canon-space-1); - border-radius: .375rem; - outline: none; - flex-direction: column; - transition: transform .15s, opacity .15s; - display: flex; - position: relative; - overflow: auto; - - &[data-starting-style], &[data-ending-style] { - opacity: 0; - transform: scale(.9); - } -} - -.canon-MenuItem { - user-select: none; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } -} - -.canon-MenuSubmenuTrigger { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - & .canon-Icon { - color: var(--canon-fg-secondary); - } - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-popup-open], &[data-highlighted] { - background-color: var(--canon-gray-3); - - & .canon-Icon { - color: var(--canon-fg-primary); - } - } -} - -.canon-MenuSeparator { - background-color: var(--color-gray-200); - height: 1px; - margin: .375rem 1rem; -} - -.canon-SubmenuComboboxSearch { - padding-inline: var(--canon-space-3); - border: none; - border-bottom: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - height: 32px; - color: var(--canon-fg-primary); - line-height: 140%; - font-size: var(--canon-font-size-3); - z-index: 1; - outline: none; - position: sticky; - top: 0; - - &::placeholder { - color: var(--canon-fg-secondary); - } - - &:disabled { - opacity: .6; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItems { - padding-top: var(--canon-space-2); - outline: none; - flex-direction: column; - display: flex; - overflow-y: auto; -} - -.canon-SubmenuComboboxNoResults { - padding-inline: var(--canon-space-3); - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-4); - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-3); -} - -.canon-SubmenuComboboxItem { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } - - &[data-disabled] { - opacity: .5; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItemCheckbox { - width: 16px; - height: 16px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - border: 1px solid var(--canon-border); - background: var(--canon-bg-surface-1); - flex-shrink: 0; - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SubmenuComboboxItemLabel { - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - overflow: hidden; -} - -.canon-Link { - font-family: var(--canon-font-regular); - color: var(--canon-fg-link); - cursor: pointer; - margin: 0; - padding: 0; - text-decoration-line: none; - - &:hover { - color: var(--canon-fg-link-hover); - text-underline-offset: calc(.025em + 2px); - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-thickness: min(2px, max(1px, .05em)); - text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); - } -} - -.canon-Link[data-variant="body"] { - font-size: var(--canon-font-size-3); - line-height: 140%; -} - -.canon-Link[data-variant="subtitle"] { - font-size: var(--canon-font-size-4); - line-height: 140%; -} - -.canon-Link[data-variant="caption"] { +.canon-TextFieldError { + color: var(--canon-fg-danger); font-size: var(--canon-font-size-2); - line-height: 140%; -} - -.canon-Link[data-variant="label"] { - font-size: var(--canon-font-size-1); - line-height: 140%; -} - -.canon-Link[data-weight="regular"] { font-weight: var(--canon-font-weight-regular); -} - -.canon-Link[data-weight="bold"] { - font-weight: var(--canon-font-weight-bold); + margin-top: var(--canon-space-2); } .canon-TooltipPopup { diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index bdc5a23dbd..fdb3621016 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -5,86 +5,26 @@ display: flex; } -.canon-TextFieldLabelWrapper { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-TextFieldLabelWrapper[data-hidden] { - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - position: absolute; - overflow: hidden; -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - cursor: pointer; - margin-right: auto; -} - -.canon-TextFieldLabel[data-size="small"] { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldLabel[data-size="medium"] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-TextFieldDescription[data-size="small"] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size="medium"] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - .canon-TextFieldInputWrapper { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - align-items: center; - display: flex; + position: relative; +} + +.canon-TextFieldInputWrapper[data-size="small"] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size="medium"] { + height: 2.5rem; } .canon-TextFieldIcon { + left: var(--canon-space-3); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); } .canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { @@ -98,6 +38,10 @@ } .canon-TextFieldInput { + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -105,73 +49,46 @@ width: 100%; height: 100%; cursor: inherit; - background: none; - border: none; - padding: 0; + align-items: center; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; -} - -.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - margin-left: var(--canon-space-1); - vertical-align: middle; - color: var(--canon-fg-primary); - background: none; - border: none; - padding: 0; - display: none; -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; + display: flex; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; +.canon-TextFieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); } diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index bcc32ef651..21304fdaa7 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -16,7 +16,6 @@ import { ForwardRefExoticComponent } from 'react'; import { HTMLAttributes } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { Menu as Menu_2 } from '@base-ui-components/react/menu'; -import type { MouseEventHandler } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RefAttributes } from 'react'; @@ -26,6 +25,7 @@ import type { SwitchProps as SwitchProps_2 } from 'react-aria-components'; import { Table as Table_2 } from '@tanstack/react-table'; import { Tabs as Tabs_2 } from '@base-ui-components/react/tabs'; import { TdHTMLAttributes } from 'react'; +import type { TextFieldProps } from 'react-aria-components'; import { ThHTMLAttributes } from 'react'; import { Tooltip as Tooltip_2 } from '@base-ui-components/react/tooltip'; import type { useRender } from '@base-ui-components/react/use-render'; @@ -354,6 +354,18 @@ export type EnumPropDef = { required?: boolean; }; +// @public (undocumented) +export const FieldLabel: ForwardRefExoticComponent< + FieldLabelProps & RefAttributes +>; + +// @public (undocumented) +export interface FieldLabelProps { + description?: string | null; + label?: string | null; + secondaryLabel?: string | null; +} + // @public (undocumented) export const Flex: ForwardRefExoticComponent< FlexProps & RefAttributes @@ -408,6 +420,14 @@ export interface FlexProps extends SpaceProps { // @public (undocumented) export type FlexWrap = 'wrap' | 'nowrap' | 'wrap-reverse'; +// @public (undocumented) +export interface FormInputProps + extends Omit, + FieldLabelProps { + icon?: ReactNode; + size?: 'small' | 'medium' | Partial>; +} + // @public (undocumented) export const gapPropDefs: { gap: { @@ -1276,25 +1296,9 @@ export { Text_2 as Text }; // @public (undocumented) export const TextField: ForwardRefExoticComponent< - TextFieldProps & RefAttributes + FormInputProps & RefAttributes >; -// @public (undocumented) -export interface TextFieldProps - extends Omit, 'size'> { - className?: string; - description?: string; - error?: string | null; - hideLabelAndDescription?: boolean; - icon?: ReactNode; - label?: string; - labelSize?: 'small' | 'medium'; - name: string; - onClear?: MouseEventHandler; - secondaryLabel?: string; - size?: 'small' | 'medium' | Partial>; -} - // @public (undocumented) export interface TextProps extends Omit, 'color'> { diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx b/packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx new file mode 100644 index 0000000000..a82639eca0 --- /dev/null +++ b/packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { FieldLabel } from './FieldLabel'; + +const meta = { + title: 'Forms/FieldLabel', + component: FieldLabel, + argTypes: { + label: { + control: 'text', + }, + secondaryLabel: { + control: 'text', + }, + description: { + control: 'text', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Label', + }, +}; + +export const WithSecondaryLabel: Story = { + args: { + ...Default.args, + secondaryLabel: 'Secondary Label', + }, +}; + +export const WithDescription: Story = { + args: { + ...Default.args, + description: 'Description', + }, +}; + +export const WithAllFields: Story = { + args: { + ...Default.args, + secondaryLabel: 'Secondary Label', + description: 'Description', + }, +}; diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.styles.css b/packages/canon/src/components/FieldLabel/FieldLabel.styles.css new file mode 100644 index 0000000000..5fee3d76a4 --- /dev/null +++ b/packages/canon/src/components/FieldLabel/FieldLabel.styles.css @@ -0,0 +1,27 @@ +.canon-TextFieldLabelWrapper { + display: flex; + flex-direction: column; + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + margin-right: auto; + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.tsx b/packages/canon/src/components/FieldLabel/FieldLabel.tsx new file mode 100644 index 0000000000..713f7fb78b --- /dev/null +++ b/packages/canon/src/components/FieldLabel/FieldLabel.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Label } from 'react-aria-components'; +import { forwardRef } from 'react'; +import type { FieldLabelProps } from './types'; + +/** @public */ +export const FieldLabel = forwardRef( + (props: FieldLabelProps, ref) => { + const { label, secondaryLabel, description } = props; + + if (!label) return null; + + return ( +
+ {label && ( + + )} + {description && ( +
{description}
+ )} +
+ ); + }, +); + +FieldLabel.displayName = 'FieldLabel'; diff --git a/packages/canon/src/components/FormInput/index.ts b/packages/canon/src/components/FieldLabel/index.ts similarity index 88% rename from packages/canon/src/components/FormInput/index.ts rename to packages/canon/src/components/FieldLabel/index.ts index 29df00ccaf..b9cebb1bf7 100644 --- a/packages/canon/src/components/FormInput/index.ts +++ b/packages/canon/src/components/FieldLabel/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export * from './TextField'; -export type { TextFieldProps } from './types'; +export * from './FieldLabel'; +export * from './types'; diff --git a/packages/canon/src/components/FormInput/types.ts b/packages/canon/src/components/FieldLabel/types.ts similarity index 61% rename from packages/canon/src/components/FormInput/types.ts rename to packages/canon/src/components/FieldLabel/types.ts index 1f28436259..41c6f390d5 100644 --- a/packages/canon/src/components/FormInput/types.ts +++ b/packages/canon/src/components/FieldLabel/types.ts @@ -14,20 +14,20 @@ * limitations under the License. */ -import { InputProps } from 'react-aria-components'; -import { ReactNode } from 'react'; -import type { Breakpoint } from '../../types'; - /** @public */ -export interface FormInputProps extends Omit { +export interface FieldLabelProps { /** - * An icon to render before the input + * The label of the text field */ - icon?: ReactNode; + label?: string | null; /** - * The size of the text field - * @defaultValue 'medium' + * The secondary label of the text field */ - size?: 'small' | 'medium' | Partial>; + secondaryLabel?: string | null; + + /** + * The description of the text field + */ + description?: string | null; } diff --git a/packages/canon/src/components/FormInput/FormInput.stories.tsx b/packages/canon/src/components/FormInput/FormInput.stories.tsx deleted file mode 100644 index 8210bedb7c..0000000000 --- a/packages/canon/src/components/FormInput/FormInput.stories.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Meta, StoryObj } from '@storybook/react'; -import { FormInput } from './FormInput'; -import { Icon } from '../Icon'; -import { Flex } from '../Flex'; - -const meta = { - title: 'Forms/FormInput', - component: FormInput, - argTypes: { - required: { - control: 'boolean', - }, - icon: { - control: 'object', - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - name: 'url', - placeholder: 'Enter a URL', - style: { - maxWidth: '300px', - }, - }, -}; - -export const Sizes: Story = { - args: { - ...Default.args, - }, - render: args => ( - - } /> - } /> - - ), -}; - -export const Filled: Story = { - args: { - ...Default.args, - defaultValue: 'https://example.com', - }, -}; - -export const Disabled: Story = { - args: { - ...Default.args, - disabled: true, - }, -}; - -export const WithIcon: Story = { - args: { - ...Default.args, - placeholder: 'Search...', - icon: , - }, -}; - -export const DisabledWithIcon: Story = { - args: { - ...WithIcon.args, - disabled: true, - }, -}; diff --git a/packages/canon/src/components/FormInput/FormInput.styles.css b/packages/canon/src/components/FormInput/FormInput.styles.css deleted file mode 100644 index 4415247799..0000000000 --- a/packages/canon/src/components/FormInput/FormInput.styles.css +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.canon-FormInput { - display: flex; - align-items: center; - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); -} - -.canon-FormInputIcon { - margin-right: var(--canon-space-1); - color: var(--canon-fg-primary); - flex-shrink: 0; -} - -.canon-FormInputIcon[data-size='small'], -.canon-FormInputIcon[data-size='small'] svg { - width: 1rem; - height: 1rem; -} - -.canon-FormInputIcon[data-size='medium'], -.canon-FormInputIcon[data-size='medium'] svg { - width: 1.25rem; - height: 1.25rem; -} - -.canon-Input { - border: none; - background: none; - font-size: var(--canon-font-size-3); - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; - width: 100%; - height: 100%; - cursor: inherit; - padding: 0; -} - -.canon-Input::placeholder { - color: var(--canon-fg-secondary); -} - -.canon-FormInput[data-size='small'] { - height: 2rem; -} - -.canon-FormInput[data-size='medium'] { - height: 2.5rem; -} - -.canon-Input[data-focused] { - outline-color: var(--canon-border-pressed); - outline-width: 0px; -} - -.canon-FormInput:has(> .canon-Input:hover) { - border-color: var(--canon-border-hover); -} - -.canon-FormInput[data-focused] { - border-color: var(--canon-border-pressed); -} - -.canon-FormInput[data-invalid] { - border-color: var(--canon-fg-danger); -} - -.canon-FormInput[data-disabled] { - opacity: 0.5; - cursor: not-allowed; - border: 1px solid var(--canon-border-disabled); -} diff --git a/packages/canon/src/components/FormInput/FormInput.tsx b/packages/canon/src/components/FormInput/FormInput.tsx deleted file mode 100644 index 9e9ca9855f..0000000000 --- a/packages/canon/src/components/FormInput/FormInput.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { forwardRef } from 'react'; -import { Input } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; -import clsx from 'clsx'; - -import type { FormInputProps } from './types'; - -/** @public */ -export const FormInput = forwardRef( - (props: FormInputProps, ref) => { - const { className, icon, size = 'small', ...rest } = props; - - // Get the responsive value for the variant - const responsiveSize = useResponsiveValue(size); - - return ( -
- {icon && ( - - )} - -
- ); - }, -); - -FormInput.displayName = 'FormInput'; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index aeb49cb8fc..9f2039b3af 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -21,34 +21,6 @@ width: 100%; } -.canon-TextFieldLabelWrapper { - display: flex; - flex-direction: column; - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - margin-right: auto; - cursor: pointer; - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); - color: var(--canon-fg-secondary); - margin: 0; -} - .canon-TextFieldInputWrapper { position: relative; } diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 62bc5633cd..bab47c6434 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -18,11 +18,11 @@ import { forwardRef, useEffect } from 'react'; import { Input, TextField as AriaTextField, - Label, FieldError, } from 'react-aria-components'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; +import { FieldLabel } from '../FieldLabel'; import type { FormInputProps } from './types'; @@ -64,26 +64,11 @@ export const TextField = forwardRef( {...rest} ref={ref} > - {label && ( -
- {label && ( - - )} - {description && ( -
{description}
- )} -
- )} +
{icon && (
{ +export interface FormInputProps + extends Omit, + FieldLabelProps { /** * An icon to render before the input */ @@ -30,24 +33,4 @@ export interface FormInputProps extends Omit { * @defaultValue 'medium' */ size?: 'small' | 'medium' | Partial>; - - /** - * The label of the text field - */ - label?: string; - - /** - * The secondary label of the text field - */ - secondaryLabel?: string; - - /** - * The description of the text field - */ - description?: string; - - /** - * Whether the field is required - */ - isRequired?: boolean; } diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 1f4c6bad99..c85c1bbdd9 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -17,15 +17,19 @@ @import '../components/Avatar/Avatar.styles.css'; @import '../components/Box/styles.css'; @import '../components/Button/styles.css'; +@import '../components/Checkbox/styles.css'; @import '../components/Collapsible/Collapsible.styles.css'; +@import '../components/Container/styles.css'; @import '../components/DataTable/Root/DataTableRoot.styles.css'; @import '../components/DataTable/Pagination/DataTablePagination.styles.css'; +@import '../components/FieldLabel/FieldLabel.styles.css'; @import '../components/Flex/styles.css'; -@import '../components/FormInput/FormInput.styles.css'; @import '../components/Grid/styles.css'; -@import '../components/Container/styles.css'; +@import '../components/Heading/styles.css'; @import '../components/Icon/styles.css'; -@import '../components/Checkbox/styles.css'; +@import '../components/IconButton/styles.css'; +@import '../components/Link/styles.css'; +@import '../components/Menu/Menu.styles.css'; @import '../components/Table/styles.css'; @import '../components/Table/TableCell/TableCell.styles.css'; @import '../components/Table/TableCellText/TableCellText.styles.css'; @@ -33,11 +37,7 @@ @import '../components/Table/TableCellProfile/TableCellProfile.styles.css'; @import '../components/Tabs/Tabs.styles.css'; @import '../components/Text/styles.css'; -@import '../components/Heading/styles.css'; -@import '../components/IconButton/styles.css'; @import '../components/TextField/TextField.styles.css'; -@import '../components/Menu/Menu.styles.css'; -@import '../components/Link/styles.css'; @import '../components/Tooltip/Tooltip.styles.css'; @import '../components/ScrollArea/ScrollArea.styles.css'; @import '../components/Select/Select.styles.css'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 310a7896c8..9b5a454254 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -36,6 +36,7 @@ export * from './components/Avatar'; export * from './components/Button'; export * from './components/Collapsible'; export * from './components/DataTable'; +export * from './components/FieldLabel'; export * from './components/Icon'; export * from './components/IconButton'; export * from './components/Checkbox'; diff --git a/packages/canon/src/stories/Form.stories.tsx b/packages/canon/src/stories/Form.stories.tsx deleted file mode 100644 index 97e28faaee..0000000000 --- a/packages/canon/src/stories/Form.stories.tsx +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { useForm, SubmitHandler, Controller } from 'react-hook-form'; -import { TextField } from '../components/TextField'; -import { Button } from '../components/Button'; -import { Select } from '../components/Select'; - -const meta = { - title: 'Forms/Form', -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -type Inputs = { - firstname: string; - lastname: string; - city: string; -}; - -export const Uncontrolled: Story = { - render: () => { - const { - register, - handleSubmit, - control, - formState: { errors }, - } = useForm(); - - const onSubmit: SubmitHandler = data => console.log(data); - - return ( -
- - - { - return ( - - ); - }} - /> - - - ); - }, -};