- <%= sage_component SageIcon, { icon: "check-circle", adjacent_type: "body", spacer: { right: "xs" } } %>
-
+ <%= sage_component SageIcon, { icon: "pen", label: "Edit", color: "primary-300" } %>
+ <%= sage_component SageIcon, { icon: "preview-on", label: "Preview", color: "sage-300" } %>
+ <%= sage_component SageIcon, { icon: "trash", label: "Delete", color: "red-300" } %>
- <%= sage_component SageIcon, { icon: "check-circle", adjacent_type: "body-sm", spacer: { right: "xs" } } %>
-
Body Small Lorem ipsum dolor sit
+
+<%= sage_component SageDivider, {} %>
+
+
Size
+
Size can be used to increase or decrease the size of the icon. The default size is md
. See Property tab for available values.
+
+
+ <%= sage_component SageIcon, { icon: "pen", label: "Edit", size: "xs" } %>
+ <%= sage_component SageIcon, { icon: "preview-on", label: "Edit" } %>
+ <%= sage_component SageIcon, { icon: "trash", label: "Edit", size: "xl" } %>
-
Background Colors
-<%= sage_component SageCardList, {} do %>
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "danger" %>
- <%= sage_component SageIcon, { icon: "danger", card_color: "danger" } %>
- <%= sage_component SageIcon, { icon: "danger", card_color: "danger", circular: true } %>
- <% end %>
-
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "draft" %>
- <%= sage_component SageIcon, { icon: "draft", card_color: "draft" } %>
- <%= sage_component SageIcon, { icon: "draft", card_color: "draft", circular: true } %>
- <% end %>
-
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "info" %>
- <%= sage_component SageIcon, { icon: "info-circle", card_color: "info" } %>
- <%= sage_component SageIcon, { icon: "info-circle", card_color: "info", circular: true } %>
- <% end %>
-
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "locked" %>
- <%= sage_component SageIcon, { icon: "lock", card_color: "locked" } %>
- <%= sage_component SageIcon, { icon: "lock", card_color: "locked", circular: true } %>
- <% end %>
-
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "published" %>
- <%= sage_component SageIcon, { icon: "window-paragraph", card_color: "published" } %>
- <%= sage_component SageIcon, { icon: "window-paragraph", card_color: "published", circular: true } %>
- <% end %>
-
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "warning" %>
- <%= sage_component SageIcon, { icon: "warning", card_color: "warning" } %>
- <%= sage_component SageIcon, { icon: "warning", card_color: "warning", circular: true } %>
- <% end %>
-
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= "kajabi" %>
- <%= sage_component SageIcon, { icon: "kajabi-filled", card_color: "primary" } %>
- <%= sage_component SageIcon, { icon: "kajabi-filled", card_color: "primary", circular: true } %>
- <% end %>
-<% end %>
+<%= sage_component SageDivider, {} %>
+
+
Card Color
+
Icons can be placed on a background by setting the card_color
property. See Property tab for available values.
+
+ <%= sage_component SageIcon, { icon: "danger", card_color: "danger" } %>
+ <%= sage_component SageIcon, { icon: "warning", card_color: "warning" } %>
+ <%= sage_component SageIcon, { icon: "check-circle", card_color: "published" } %>
+
+
+<%= sage_component SageDivider, {} %>
Background Sizes
-<%= sage_component SageCardList, {} do %>
- <% SageTokens::ICON_SIZES.each do | size | %>
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
-
<%= size == "md" ? "Default (#{size})" : size %>
- <%= sage_component SageIcon, { icon: "pen", size: (size == "md" ? nil : size), card_color: "draft" } %>
- <%= sage_component SageIcon, { icon: "pen", size: (size == "md" ? nil : size), card_color: "draft", circular: true, } %>
- <% end %>
- <% end %>
-<% end %>
+
Background sizes can be applied by setting the size
property. See Property tab for available values.
+
+
+ <%= sage_component SageIcon, { icon: "pen", size: "sm", card_color: "draft" } %>
+ <%= sage_component SageIcon, { icon: "preview-on", size: "lg", card_color: "draft" } %>
+ <%= sage_component SageIcon, { icon: "trash", size: "xl", card_color: "draft" } %>
+
+
+<%= sage_component SageDivider, {} %>
Custom Background Sizes
-<%= sage_component SageCardList, {} do %>
- <%= sage_component SageCardListItem, { grid_template: "o" } do %>
- <%= sage_component SageIcon, { icon: "pen", size: "2xl", background_width: "128px", background_height: "64px", card_color: "draft" } %>
- <%= sage_component SageIcon, { icon: "pen", size: "xl", background_height: "64px", card_color: "draft", circular: true, } %>
- <% end %>
-<% end %>
+
Custom background sizes can be applied by setting the background_width
and background_height
properties. These values are applied as inline styles, so they can be any valid CSS value. The background_width
property is required when using background_height
.
+
+
+
+ <%= sage_component SageIcon, { icon: "pen", background_width: "128px", background_height: "128px", card_color: "draft" } %>
+ <%= sage_component SageIcon, { icon: "preview-on", background_width: "64px", background_height: "128px", card_color: "warning" } %>
+ <%= sage_component SageIcon, { icon: "trash", background_width: "240px", background_height: "80px", card_color: "danger" } %>
+
+
+<%= sage_component SageDivider, {} %>
+
+
Circular
+
Icons can be made circular by setting the circular
property to true
.
+
+
+ <%= sage_component SageIcon, { icon: "pen", card_color: "draft", circular: true } %>
+ <%= sage_component SageIcon, { icon: "preview-on", card_color: "published", circular: true } %>
+ <%= sage_component SageIcon, { icon: "trash", card_color: "danger", circular: true } %>
+
+
+<%= sage_component SageDivider, {} %>
+
+
Alignment next to Type
+
+
The adjacent_type
property can be used to align an icon next to a heading or paragraph. This is useful for placing an icon next to a heading or paragraph in a variety of forms. The effect is that the icon's height
and line-height
are updated to match the spec responsively. This can be used in combination with the different icon sizes and also be used with a vareity of layout techniques or components such as CSS flexbox (used below) or grid templates within SageCardRow
or SagePanelRow
.
+
+
+
+ <%= sage_component SageIcon, { icon: "check-circle", adjacent_type: "h2", spacer: { right: "xs" } } %>
+
Heading 2 Lorem ipsum dolor sit
+
diff --git a/docs/app/views/examples/components/upload_card/_preview.html.erb b/docs/app/views/examples/components/upload_card/_preview.html.erb
index 7bfa6b0b46..6070416a89 100644
--- a/docs/app/views/examples/components/upload_card/_preview.html.erb
+++ b/docs/app/views/examples/components/upload_card/_preview.html.erb
@@ -1,42 +1,109 @@
-
Initial State
+<%
+ dropdown_menu_items = [
+ {
+ value: "Upload file",
+ }, {
+ value: "Select recent file",
+ }, {
+ style: "danger",
+ modifiers: ["border-before"],
+ value: "Remove image",
+ }
+ ]
+%>
+
+
Default State (no file selected)
+
<%= sage_component SageUploadCard, {
- selection_label: "Select a file",
- selection_subtext: "Recommended dimensions or file size requirements",
-} %>
+ id: "upload-card-default",
+ selection_label: "Select file",
+} do %>
+ <% content_for :sage_upload_card_instructions do %>
+
Upload a JPEG
+ <% end %>
+<% end %>
+
+
Selected File State
-
Selected File State
+
Restrict accepted file types using accepted_file_types
.
<%= sage_component SageUploadCard, {
accepted_files: [
- {name: "contacts.csv"},
+ {name: "my-image-file.jpg"},
],
file_selected: true,
+ accepted_file_types: ["image/jpg"],
+ selection_preview: "https://placekitten.com/360",
selection_label: "Replace file",
- selection_subtext: "Recommended dimensions or file size requirements",
-} %>
+ id: "upload-card-selected",
+} do %>
+ <% content_for :sage_upload_card_instructions do %>
+
Upload a JPEG this is a test
+ <% end %>
+<% end %>
-
Error State
+
+
Vertical (stacked) layout
+
The default layout will adjust from a vertical (stacked) orientation to horizontal depending on available space and/or screen size.
+
+
Setting stack_layout
to true
locks the component to the vertical orientation.
<%= sage_component SageUploadCard, {
- has_error: true,
- selection_label: "Select a file",
- selection_subtext: "Recommended dimensions or file size requirements",
- errors: [
- {text: "This is the error message."},
- ]
-} %>
+ accepted_files: [
+ {name: "my-image-file.jpg"},
+ ],
+ file_selected: true,
+ selection_preview: "https://placekitten.com/360",
+ stack_layout: true,
+ id: "upload-card-stack",
+ selection_label: "Edit file",
+} do %>
+ <% content_for :sage_upload_card_instructions do %>
+
Recommended dimensions or file size requirements
+ <% end %>
+<% end %>
-
Selected Error State
+
Custom content areas
+
Instructions area
+
The sage_upload_card_instructions
slot provides an area for extended instructions and custom markup.
+
Actions area
+
Use the sage_upload_card_actions
slot to replace the default button and display custom components, such as dropdowns.
+
NOTE: a file input field and label are included by default in the base component , as seen in the examples above. When applying a custom file input with `sage_upload_card_actions`, set custom_file_input_field
to true
to remove these defaults.
<%= sage_component SageUploadCard, {
accepted_files: [
- {name: "contacts.csv"},
+ {name: "fluffy-kitteh.jpg"},
],
+ custom_file_input_field: true,
file_selected: true,
+ selection_preview: "https://placekitten.com/360",
+ id: "upload-card-dropdown"
+} do %>
+ <% content_for :sage_upload_card_actions do %>
+ <%= sage_component SageDropdown, { items: dropdown_menu_items } do %>
+ <% content_for :sage_dropdown_trigger, flush: true do %>
+ <%= sage_component SageButton, {
+ style: "secondary",
+ icon: { name: "caret-down", style: "right" },
+ value: "Edit file",
+ } %>
+ <% end %>
+ <% end %>
+ <% end %>
+<% end %>
+
+
+
Error State
+
+<%= sage_component SageUploadCard, {
has_error: true,
- selection_label: "Replace file",
- selection_subtext: "Recommended dimensions or file size requirements",
+ id: "upload-card-error",
+ selection_label: "Select a file",
errors: [
- {text: "This is the error message."},
+ {text: "This is an error message."},
]
-} %>
\ No newline at end of file
+} do %>
+ <% content_for :sage_upload_card_instructions do %>
+
Recommended dimensions or file size requirements
+ <% end %>
+<% end %>
diff --git a/docs/lib/sage_rails/app/sage_components/sage_upload_card.rb b/docs/lib/sage_rails/app/sage_components/sage_upload_card.rb
index 965b0dfacf..44b0bbf6b6 100644
--- a/docs/lib/sage_rails/app/sage_components/sage_upload_card.rb
+++ b/docs/lib/sage_rails/app/sage_components/sage_upload_card.rb
@@ -4,12 +4,20 @@ class SageUploadCard < SageComponent
name: [:optional, NilClass, String],
size: [:optional, NilClass, String],
]]],
+ accepted_file_types: [:optional, NilClass, Array],
+ custom_file_input_field: [:optional, NilClass, TrueClass],
errors: [:optional, NilClass, [[
text: [:optional, NilClass, String],
]]],
file_selected: [:optional, NilClass, TrueClass],
has_error: [:optional, NilClass, TrueClass],
+ id: String,
+ name: [:optional, NilClass, String],
+ selection_preview: [:optional, NilClass, String],
selection_label: [:optional, NilClass, String],
- selection_subtext: [:optional, NilClass, String],
+ stack_layout:[:optional, NilClass, TrueClass],
})
+ def sections
+ %w(upload_card_instructions upload_card_preview upload_card_actions)
+ end
end
diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_upload_card.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_upload_card.html.erb
index 11bc66ec1f..da53152a94 100644
--- a/docs/lib/sage_rails/app/views/sage_components/_sage_upload_card.html.erb
+++ b/docs/lib/sage_rails/app/views/sage_components/_sage_upload_card.html.erb
@@ -1,42 +1,56 @@
-
->
+<%
+ upload_card_classes = ['sage-upload-card']
+ upload_card_classes << 'sage-upload-card--stack-only' if component.stack_layout == true
+ upload_card_classes << 'sage-upload-card--selected' if component.file_selected.present?
+ upload_card_classes << 'sage-upload-card--error' if component.has_error.present?
+ upload_card_classes << component.generated_css_classes
+ upload_card_input_label = component.selection_label.present? ? component.selection_label : "Select a file"
+ upload_card_image_types = component.accepted_file_types.present? ? component.accepted_file_types.join(",") : "image/*"
+
+ upload_card_preview_image_options = {
+ class: "sage-upload-card__preview",
+ alt: ""
+ }
+%>
+
" <%= component.generated_html_attributes.html_safe %>>
-
- <% if component.file_selected.present? %>
+ <% unless component.custom_file_input_field %>
+
+ <% end %>
+ <% if component.selection_preview.present? %>
+ <%= image_tag(component.selection_preview, upload_card_preview_image_options) %>
+ <% elsif content_for? :sage_upload_card_preview %>
+ <%= content_for :sage_upload_card_preview %>
+ <% else %>
<%= sage_component SageIconCard, {
color: "draft",
css_classes: "sage-upload-card__preview",
icon: "image",
- label: "Upload graphic",
size: "2xl",
} %>
-
-
<%= component.accepted_files[0][:name] %>
- <%= sage_component SageButton, {
- style: "primary",
- subtle: true,
- value: component.selection_label
- } %>
-
<%= component.selection_subtext %>
-
- <% else %>
-
-
- <%= sage_component SageButton, {
- style: "primary",
- subtle: true,
- value: component.selection_label
- } %>
-
<%= component.selection_subtext %>
-
<% end %>
+
+
+ <% if component.file_selected.present? || component.selection_preview.present? %>
+
+ <%= component.accepted_files[0][:name] %>
+
+ <% end %>
+ <% if content_for? :sage_upload_card_instructions %>
+
+ <%= content_for :sage_upload_card_instructions %>
+
+ <% end %>
+
+ <% if component.selection_label.present? && !component.custom_file_input_field %>
+
<%= upload_card_input_label %>
+ <% elsif content_for? :sage_upload_card_actions %>
+ <% if !component.custom_file_input_field %>
+
<%= upload_card_input_label %>
+ <% end %>
+ <%= content_for :sage_upload_card_actions %>
+ <% end %>
+
<% if component.errors.present? %>
diff --git a/packages/sage-assets/Dockerfile b/packages/sage-assets/Dockerfile
index 88da272755..0db40ddb58 100644
--- a/packages/sage-assets/Dockerfile
+++ b/packages/sage-assets/Dockerfile
@@ -3,7 +3,7 @@ FROM ruby:3.1.2-alpine
ARG RAILS_ENV="production"
ARG NONROOT_UID="1000"
ARG NONROOT_GID="1000"
-ARG NODE_VERSION="16.20.0"
+ARG NODE_VERSION="16.20.2"
ARG YARN_VERSION="1.22.18"
ARG ARCH="x64"
diff --git a/packages/sage-assets/lib/stylesheets/components/_upload_card.scss b/packages/sage-assets/lib/stylesheets/components/_upload_card.scss
index 377b4a9582..7272556b91 100644
--- a/packages/sage-assets/lib/stylesheets/components/_upload_card.scss
+++ b/packages/sage-assets/lib/stylesheets/components/_upload_card.scss
@@ -7,69 +7,69 @@
$-upload-card-border-radius: sage-border(radius-large);
$-upload-card-border-width: 2;
-$-upload-card-selected-width: rem(200px);
+$-upload-card-error-color: sage-color(red, 300);
+$-upload-card-body-width: rem(200px);
+$-upload-card-body-width-stack: rem(340px);
+$-upload-card-preview-border-radius: sage-border(radius-medium);
$-upload-card-preview-width: rem(32px);
$-upload-card-preview-max-width: rem(190px);
-$-upload-card-preview-max-width-mobile: rem(304px);
+$-upload-card-preview-max-width-stack: rem(292px);
$-upload-card-background: sage-color(white);
+$-upload-card-mobile-breakpoint: 609px;
-.sage-upload-card {
- &:focus {
- outline: none;
- }
+:root {
+ --sage-upload-card-aspect-ratio: 190 / 107;
+ --sage-upload-card-aspect-ratio-stack: 19 / 12;
}
.sage-upload-card__body {
display: flex;
flex-flow: column;
- align-items: center;
- flex: 1;
-
- :not(.sage-upload-card--selected) & {
- justify-content: center;
- align-items: center;
- }
+ align-items: flex-start;
+ justify-content: flex-start;
+ flex: 1 1;
+ gap: sage-spacing();
+ color: sage-color(charcoal, 200);
.sage-upload-card--selected & {
- justify-content: flex-start;
- align-items: flex-start;
+ color: sage-color(charcoal, 300);
}
- .sage-upload-card--selected & {
- flex-basis: $-upload-card-selected-width;
+ .sage-upload-card--error & {
+ color: $-upload-card-error-color;
}
}
.sage-upload-card__dropzone {
display: flex;
+ flex-flow: row wrap;
align-items: center;
justify-content: center;
- gap: sage-spacing(xs);
+ gap: sage-spacing();
padding: sage-spacing(md);
background-color: $-upload-card-background;
border-radius: $-upload-card-border-radius;
border: sage-border(default);
- .sage-upload-card:not(.sage-upload-card--selected) & {
- flex-flow: column;
- }
-
- .sage-upload-card--selected & {
- flex-flow: row wrap;
- gap: sage-spacing();
- }
-
.sage-upload-card.sage-upload-card--error & {
- border-color: sage-color(red, 300);
+ border-color: $-upload-card-error-color;
}
.sage-upload-card.sage-upload-card--error & {
&:hover,
&:focus,
&:focus-within {
- border-color: sage-color(red, 300);
+ border-color: $-upload-card-error-color;
}
}
+
+ .sage-upload-card--stack-only & {
+ flex-flow: column;
+ align-items: flex-start;
+ max-width: $-upload-card-body-width-stack;
+ margin-left: auto;
+ margin-right: auto;
+ }
}
.sage-upload-card__errors {
@@ -78,7 +78,7 @@ $-upload-card-background: sage-color(white);
> p {
@extend %t-sage-body-med;
- color: sage-color(red, 300);
+ color: $-upload-card-error-color;
&:not(:last-child) {
margin-bottom: sage-spacing(2xs);
@@ -91,7 +91,7 @@ $-upload-card-background: sage-color(white);
&:focus,
&:hover {
- color: sage-color(red, 300);
+ color: $-upload-card-error-color;
text-decoration: underline;
outline: 0;
}
@@ -111,32 +111,36 @@ $-upload-card-background: sage-color(white);
@include visually-hidden;
}
+.sage-upload-card__input-label {
+ /* NOTE: label provides keyboard focus but does not allow form interaction */
+ @include sage-focus-ring;
+}
+
.sage-upload-card__preview {
- width: $-upload-card-preview-width;
- max-width: $-upload-card-preview-max-width;
+ width: 100%;
margin-right: 0;
text-align: center;
- border-radius: sage-border(radius-sm);
+ border-radius: $-upload-card-preview-border-radius;
+ aspect-ratio: var(--sage-upload-card-aspect-ratio-stack);
+ object-fit: cover;
- .sage-upload-card--selected & {
- width: 100%;
+ @media (min-width: 610px) {
+ max-width: $-upload-card-preview-max-width;
+ aspect-ratio: var(--sage-upload-card-aspect-ratio);
}
- @media (max-width: 609px) {
- max-width: $-upload-card-preview-max-width-mobile;
+ .sage-upload-card--stack-only & {
+ max-width: $-upload-card-preview-max-width-stack;
+ aspect-ratio: var(--sage-upload-card-aspect-ratio-stack);
}
}
-.sage-upload-card__subtext {
- @extend %t-sage-body-small;
+.sage-upload-card__description {
+ @extend %t-sage-body-med;
- margin-top: sage-spacing(2xs);
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: sage-spacing(sm);
color: sage-color(charcoal, 200);
}
-
-.sage-upload-card__text {
- @extend %t-sage-heading-6;
-
- margin-bottom: sage-spacing(2xs);
- color: sage-color(charcoal, 400);
-}
diff --git a/packages/sage-react/Dockerfile b/packages/sage-react/Dockerfile
index 63394d9b34..3ec4b59a6a 100644
--- a/packages/sage-react/Dockerfile
+++ b/packages/sage-react/Dockerfile
@@ -2,7 +2,7 @@ FROM alpine:3.17.0
ARG NONROOT_UID="1000"
ARG NONROOT_GID="1000"
-ARG NODE_VERSION="16.20.0"
+ARG NODE_VERSION="16.20.2"
ARG YARN_VERSION="1.22.18"
ARG ARCH="x64"
ARG GITHUB_TOKEN
diff --git a/packages/sage-react/lib/UploadCard/UploadCard.jsx b/packages/sage-react/lib/UploadCard/UploadCard.jsx
index 7d0b026b51..296d17f5a4 100644
--- a/packages/sage-react/lib/UploadCard/UploadCard.jsx
+++ b/packages/sage-react/lib/UploadCard/UploadCard.jsx
@@ -1,20 +1,22 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
-import { Button } from '../Button';
-import { Icon } from '../Icon';
import { IconCard } from '../IconCard';
-import { SageTokens } from '../configs';
export const UploadCard = ({
+ actions,
acceptedFiles,
className,
+ customFileInputField,
errors,
+ id,
inputProps,
+ previewImage,
replaceLabel,
rootProps,
selectionLabel,
selectionSubtext,
+ stacked,
...rest
}) => {
const [filesSelected, updateFilesSelected] = useState(acceptedFiles && acceptedFiles.length > 0);
@@ -28,63 +30,87 @@ export const UploadCard = ({
{
'sage-upload-card--selected': filesSelected,
'sage-upload-card--error': errors,
+ 'sage-upload-card--stack-only': stacked,
}
);
+ const renderDefaultInputField = () => (
+ !customFileInputField && (
+
+ )
+ );
+
+ const renderPreviewImage = () => (
+ previewImage ? (
+
+ ) : (
+
+ )
+ );
+
+ const renderLabel = () => {
+ let classnames = 'visually-hidden';
+ const CustomLabel = () => (
+ <>
+
+ {selectionLabel || replaceLabel}
+
+ >
+ );
+ if ((selectionLabel || replaceLabel) && customFileInputField === false) {
+ classnames = 'sage-upload-card__input-label sage-btn sage-btn--secondary';
+ } else if (actions !== null) {
+ if (customFileInputField === false) {
+ classnames = 'visually-hidden';
+ }
+ return actions;
+ }
+ return
;
+ };
+
return (
-
+ {renderDefaultInputField()}
+ {renderPreviewImage()}
{filesSelected ? (
<>
-
- {acceptedFiles.map(({ name, size }, i) => {
- // Limit to one file for now
- if (i > 0) {
- return null;
- }
+
+ {acceptedFiles.map(({ name }, i) => {
+ // Limit to one file for now
+ if (i > 0) {
+ return null;
+ }
- return (
-
- {name}
-
- Replace file
-
-
- {`File size: ${size}B`}
-
-
- );
- })}
+ return (
+
+
+ {name}
+
+ {renderLabel()}
+
+ );
+ })}
+
>
) : (
-
-
-
- {selectionLabel}
-
-
- {selectionSubtext}
-
-
+ <>
+
+
+
+ {selectionSubtext}
+
+ {renderLabel()}
+
+
+ >
)}
{errors && (
@@ -100,25 +126,38 @@ export const UploadCard = ({
UploadCard.defaultProps = {
acceptedFiles: [],
+ actions: null,
className: null,
+ customFileInputField: false,
errors: null,
+ id: null,
inputProps: null,
+ previewImage: null,
replaceLabel: 'Replace file',
rootProps: null,
selectionLabel: 'Select a file',
selectionSubtext: null,
+ stacked: false,
};
UploadCard.propTypes = {
acceptedFiles: PropTypes.arrayOf(PropTypes.shape),
+ actions: PropTypes.node,
className: PropTypes.string,
+ customFileInputField: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string,
message: PropTypes.string,
})),
+ id: PropTypes.string,
inputProps: PropTypes.shape({}),
+ previewImage: PropTypes.shape({
+ alt: PropTypes.string,
+ src: PropTypes.string
+ }),
replaceLabel: PropTypes.string,
rootProps: PropTypes.shape({}),
selectionLabel: PropTypes.string,
selectionSubtext: PropTypes.string,
+ stacked: PropTypes.bool
};
diff --git a/packages/sage-react/lib/UploadCard/UploadCard.spec.jsx b/packages/sage-react/lib/UploadCard/UploadCard.spec.jsx
new file mode 100644
index 0000000000..88eee32573
--- /dev/null
+++ b/packages/sage-react/lib/UploadCard/UploadCard.spec.jsx
@@ -0,0 +1,130 @@
+require('../test/testHelper');
+
+import React from 'react';
+import { render } from '@testing-library/react';
+import { SageTokens } from '../configs';
+import { Button } from '../Button';
+import { UploadCard } from './UploadCard';
+
+describe('Sage Upload Card', () => {
+ it('renders with correct selection subtext when prop is set', () => {
+ const defaultProps = {
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const selectionSubtext = document.querySelector('.sage-upload-card__filename');
+ expect(selectionSubtext).toHaveTextContent('Upload a .csv up to 10KB');
+ });
+
+ it('renders with correct selection label when prop is set', () => {
+ const defaultProps = {
+ selectionLabel: 'Select a file',
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const selectionLabel = document.querySelector('.sage-upload-card__input-label');
+ expect(selectionLabel).toHaveTextContent('Select a file');
+ });
+
+ it('renders with correct replace label when prop is set', () => {
+ const defaultProps = {
+ replaceLabel: 'Replace file',
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const replaceLabel = document.querySelector('.sage-upload-card__input-label');
+ expect(replaceLabel).toHaveTextContent('Select a file');
+ });
+
+ it('renders with replace label when both label props are set', () => {
+ const defaultProps = {
+ replaceLabel: 'Replace file',
+ selectionLabel: 'Select a file',
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const replaceLabel = document.querySelector('.sage-upload-card__input-label');
+ expect(replaceLabel).toHaveTextContent('Select a file');
+ });
+
+ it('renders with preview image when prop is set', () => {
+ const defaultProps = {
+ previewImage: {
+ alt: 'cat',
+ src: 'https://placekitten.com/360',
+ },
+ selectionLabel: 'Select a file',
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const previewImage = document.querySelector('img');
+ expect(previewImage).not.toBeNull();
+ expect(previewImage).toHaveAttribute('alt', 'cat');
+ expect(previewImage).toHaveAttribute('src', 'https://placekitten.com/360');
+ });
+
+ it('renders with actions when prop is set', () => {
+ const defaultProps = {
+ actions: (
+
+ Select a file
+
+ ),
+ customFileInputField: true,
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const actions = document.querySelector('.sage-btn');
+ expect(actions).not.toBeNull();
+ });
+
+ it('renders with errors when prop is set', () => {
+ const defaultProps = {
+ errors: [
+ {
+ code: '1',
+ message: 'This is the error message.',
+ },
+ ],
+ previewImage: {
+ alt: 'cat',
+ src: 'https://placekitten.com/360',
+ },
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const errors = document.querySelector('.sage-upload-card__errors');
+ expect(errors).not.toBeNull();
+ expect(errors).toHaveTextContent('This is the error message.');
+ });
+
+ it('renders in stacked layout when prop is set', () => {
+ const defaultProps = {
+ stacked: true,
+ selectionSubtext: 'Upload a .csv up to 10KB',
+ };
+
+ render(
);
+
+ const uploadCard = document.querySelector('.sage-upload-card');
+ expect(uploadCard).toHaveClass('sage-upload-card--stack-only');
+ });
+});
diff --git a/packages/sage-react/lib/UploadCard/UploadCard.story.jsx b/packages/sage-react/lib/UploadCard/UploadCard.story.jsx
index 2515dc6d28..7e3048b6e6 100644
--- a/packages/sage-react/lib/UploadCard/UploadCard.story.jsx
+++ b/packages/sage-react/lib/UploadCard/UploadCard.story.jsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { Grid } from '..';
+import { Button } from '../Button';
+import { Grid, SageTokens } from '..';
import { UploadCard } from './UploadCard';
export default {
@@ -33,20 +34,54 @@ Default.decorators = [
];
export const DefaultWithError = Template.bind({});
+DefaultWithError.parameters = {
+ docs: {
+ description: {
+ story: 'The Upload Card will display an error message if the `errors` prop is set, with the appropriate styling.'
+ },
+ },
+};
DefaultWithError.args = {
errors: [
{
message: 'This is the error message.'
},
- ]
+ ],
+ previewImage: {
+ alt: 'cat',
+ src: 'https://placekitten.com/360'
+ }
};
-export const SelectedWithError = Template.bind({});
-SelectedWithError.args = {
- acceptedFiles: [{ name: '.csv', size: '1 M' }],
- errors: [
- {
- message: 'This is the error message.'
+export const CustomActions = Template.bind({});
+CustomActions.parameters = {
+ docs: {
+ description: {
+ story: 'The `actions` prop is a slot to allow for custom labels and inputs, buttons, dropdowns, etc. to be used in place of the default input field when the `customFileInputField` prop is set to `true.`'
},
- ]
+ },
+};
+CustomActions.args = {
+ actions: (
+
+ Select a file
+
+ ),
+ customFileInputField: true,
+};
+
+export const Stacked = Template.bind({});
+Stacked.parameters = {
+ docs: {
+ description: {
+ story: 'The default layout will adjust from a vertical (stacked) orientation to horizontal depending on available space and/or screen size. Setting `stack_layout` to `true` locks the component to the vertical orientation.'
+ },
+ },
+};
+Stacked.args = {
+ stacked: true,
};