Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions libs/core/src/components.d.ts

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions libs/core/src/components/pds-textarea/docs/pds-textarea.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ Removes the ability to interact with the text field. If a value is present, that
<pds-textarea name="Readonly" label="Name" readonly="true" value="Readonly Value" component-id="textarea-readonly"></pds-textarea>
</DocCanvas>

### Highlight

Applies accent styling to the textarea field with a purple background, border, and text color to draw attention to important fields.

<DocCanvas client:only
mdxSource={{
react: '<PdsTextarea name="Highlight" label="Message" highlight="true" value="This textarea is highlighted" componentId="textarea-highlight"></PdsTextarea>',
webComponent: '<pds-textarea name="Highlight" label="Message" highlight="true" value="This textarea is highlighted" component-id="textarea-highlight"></pds-textarea>'
}}>
<pds-textarea name="Highlight" label="Message" highlight="true" value="This textarea is highlighted" component-id="textarea-highlight"></pds-textarea>
</DocCanvas>

### Required

Indicates that the user must specify a value for the input before the owning form can be submitted. It's recommended to use `error-message` on any required fields.
Expand Down
21 changes: 21 additions & 0 deletions libs/core/src/components/pds-textarea/pds-textarea.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@
}
}

:host([highlight]:not([disabled]):not([aria-disabled="true"]):not([aria-readonly="true"])) {
.pds-textarea__field:not(.is-invalid) {
background-color: var(--pine-color-accent-disabled);
border-color: var(--pine-color-accent);
color: var(--pine-color-text-accent);

&:hover {
border-color: var(--pine-color-accent-hover);
}

&:focus-visible {
outline-color: var(--pine-color-focus-ring);
}

~ .pds-textarea__character-counter {
background: color-mix(in srgb, var(--pine-color-accent-disabled) 80%, transparent);
color: var(--pine-color-text-accent);
}
}
}

.pds-textarea {
display: flex;
flex-direction: column;
Expand Down
5 changes: 5 additions & 0 deletions libs/core/src/components/pds-textarea/pds-textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ export class PdsTextarea {
*/
@Prop({ reflect: true }) maxLength?: number;

/**
* Applies highlight styling to the textarea field.
*/
@Prop({ reflect: true }) highlight?: boolean;

/**
* The value of the textarea.
*/
Expand Down
1 change: 1 addition & 0 deletions libs/core/src/components/pds-textarea/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
| `errorMessage` | `error-message` | Displays an error message below the textarea field. | `string` | `undefined` |
| `helperMessage` | `helper-message` | Displays a message or hint below the textarea field. | `string` | `undefined` |
| `hideLabel` | `hide-label` | Visually hides the label text for instances where only the textarea should be displayed. Label remains accessible to assistive technology such as screen readers. Note: When true, the action slot is also hidden to maintain a minimal UI. | `boolean` | `undefined` |
| `highlight` | `highlight` | Applies highlight styling to the textarea field. | `boolean` | `undefined` |
| `invalid` | `invalid` | Determines whether or not the textarea is invalid or throws an error. | `boolean` | `false` |
| `label` | `label` | Text to be displayed as the textarea label. | `string` | `undefined` |
| `maxLength` | `max-length` | Specifies the maximum number of characters allowed in the textarea. When set, displays a character counter. | `number` | `undefined` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default {
errorMessage: null,
helperMessage: null,
hideLabel: false,
highlight: false,
invalid: false,
label: null,
maxLength: null,
Expand Down Expand Up @@ -45,6 +46,7 @@ const BaseTemplate = (args) => html`<pds-textarea
error-message="${args.errorMessage}"
helper-message="${args.helperMessage}"
?hide-label=${args.hideLabel}
?highlight=${args.highlight}
?invalid=${args.invalid}
label="${args.label}"
max-length="${args.maxLength}"
Expand Down Expand Up @@ -109,6 +111,15 @@ Readonly.args = {
value: 'Readonly Value'
};

export const Highlight = BaseTemplate.bind({});
Highlight.args = {
componentId: 'pds-textarea-highlight-example',
highlight: true,
label: 'Message',
name: 'Highlight',
value: 'This textarea is highlighted',
};

export const WithMessage = BaseTemplate.bind({});
WithMessage.args = {
componentId: 'pds-textarea-helper-example',
Expand Down Expand Up @@ -144,6 +155,7 @@ export const withActionLink = (args) => html`<pds-textarea
error-message="${args.errorMessage}"
helper-message="${args.helperMessage}"
?hide-label=${args.hideLabel}
?highlight=${args.highlight}
?invalid=${args.invalid}
label="Notes"
name="${args.name}"
Expand All @@ -168,6 +180,7 @@ export const withActionButton = (args) => html`<pds-textarea
error-message="${args.errorMessage}"
helper-message="${args.helperMessage}"
?hide-label=${args.hideLabel}
?highlight=${args.highlight}
?invalid=${args.invalid}
label="Description"
name="${args.name}"
Expand Down
36 changes: 36 additions & 0 deletions libs/core/src/components/pds-textarea/test/pds-textarea.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,40 @@ describe('pds-textarea', () => {
expect(await counter.innerText).toBe('9 / 100');
});
});

it('applies highlight styling when highlight prop is set', async () => {
const page = await newE2EPage();
await page.setContent('<pds-textarea highlight label="Message" name="message" value="test"></pds-textarea>');

const component = await page.find('pds-textarea');
expect(component).toHaveClass('hydrated');
expect(component).toHaveAttribute('highlight');

// Verify highlight attribute is reflected
const hasHighlight = await component.getAttribute('highlight');
expect(hasHighlight).not.toBeNull();
});

it('toggles highlight attribute when property changes', async () => {
const page = await newE2EPage();
await page.setContent('<pds-textarea label="Message" name="message"></pds-textarea>');
const component = await page.find('pds-textarea');

// Initially no highlight
expect(component).not.toHaveAttribute('highlight');

// Set highlight property
component.setProperty('highlight', true);
await page.waitForChanges();

// Should have highlight attribute
expect(component).toHaveAttribute('highlight');

// Unset highlight property
component.setProperty('highlight', false);
await page.waitForChanges();

// Should not have highlight attribute
expect(component).not.toHaveAttribute('highlight');
});
});
57 changes: 56 additions & 1 deletion libs/core/src/components/pds-textarea/test/pds-textarea.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ it('should set focus on the input element when setFocus is called', async() => {
it('renders label wrapper without visually-hidden class when hideLabel is false', async () => {
const { root } = await newSpecPage({
components: [PdsTextarea],
html: `<pds-textarea component-id="textarea-1" label="Description" hide-label="false"></pds-textarea>`,
html: `<pds-textarea component-id="textarea-1" label="Description"></pds-textarea>`,
});

const labelWrapper = root?.shadowRoot?.querySelector('.pds-textarea__label-wrapper');
Expand Down Expand Up @@ -980,4 +980,59 @@ it('should set focus on the input element when setFocus is called', async() => {
expect(mockSetValidity).toHaveBeenCalled();
});
});

describe('highlight', () => {
it('renders with highlight attribute when highlight prop is true', async () => {
const { root } = await newSpecPage({
components: [PdsTextarea],
html: `<pds-textarea component-id="textarea-1" highlight="true" value="test"></pds-textarea>`,
});

expect(root?.hasAttribute('highlight')).toBe(true);
});

it('reflects highlight property to attribute', async () => {
const page = await newSpecPage({
components: [PdsTextarea],
html: `<pds-textarea component-id="textarea-1"></pds-textarea>`,
});

const component = page.rootInstance;
const root = page.root;

// Initially no highlight
expect(root?.hasAttribute('highlight')).toBe(false);

// Set highlight property
component.highlight = true;
await page.waitForChanges();

// Attribute should be reflected
expect(root?.hasAttribute('highlight')).toBe(true);

// Unset highlight property
component.highlight = false;
await page.waitForChanges();

// Attribute should be removed
expect(root?.hasAttribute('highlight')).toBe(false);
});

it('semantic states take precedence over highlight', async () => {
const page = await newSpecPage({
components: [PdsTextarea],
html: `<pds-textarea component-id="textarea-1" highlight="true" disabled="true" value="test"></pds-textarea>`,
});

const root = page.root;

// Both attributes should be present
expect(root?.hasAttribute('highlight')).toBe(true);
expect(root?.hasAttribute('disabled')).toBe(true);

// Disabled state should apply (CSS selector excludes disabled)
const textarea = root?.shadowRoot?.querySelector('textarea');
expect(textarea?.hasAttribute('disabled')).toBe(true);
});
});
});
Loading